diff --git a/api/types.go b/api/types.go index ed0a1a2d..529dad48 100644 --- a/api/types.go +++ b/api/types.go @@ -2,6 +2,7 @@ package api import ( "blockbook/bchain" + "blockbook/db" "math/big" ) @@ -90,3 +91,10 @@ type Address struct { TotalPages int `json:"totalPages"` TxsOnPage int `json:"txsOnPage"` } + +type Blocks struct { + Blocks []db.BlockInfo `json:"blocks"` + Page int `json:"page"` + TotalPages int `json:"totalPages"` + BlocksOnPage int `json:"blocksOnPage"` +} diff --git a/api/worker.go b/api/worker.go index 7320134d..da349855 100644 --- a/api/worker.go +++ b/api/worker.go @@ -436,3 +436,49 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids b glog.Info("GetAddress ", address, " finished in ", time.Since(start)) return r, nil } + +// GetBlocks returns BlockInfo for blocks on given page +func (w *Worker) GetBlocks(page int, blocksOnPage int) (*Blocks, error) { + start := time.Now() + page-- + if page < 0 { + page = 0 + } + b, _, err := w.db.GetBestBlock() + bestheight := int(b) + if err != nil { + return nil, errors.Annotatef(err, "GetBestBlock") + } + // paging + from := page * blocksOnPage + totalPages := (bestheight - 1) / blocksOnPage + if totalPages < 0 { + totalPages = 0 + } + if from >= bestheight { + page = totalPages - 1 + if page < 0 { + page = 0 + } + } + from = page * blocksOnPage + to := (page + 1) * blocksOnPage + if to > bestheight { + to = bestheight + } + r := &Blocks{ + Page: page + 1, + TotalPages: totalPages + 1, + BlocksOnPage: blocksOnPage, + } + r.Blocks = make([]db.BlockInfo, to-from) + for i := from; i < to; i++ { + bi, err := w.db.GetBlockInfo(uint32(bestheight - i)) + if err != nil { + return nil, err + } + r.Blocks[i-from] = *bi + } + glog.Info("GetBlocks page ", page, " finished in ", time.Since(start)) + return r, nil +} diff --git a/db/bulkconnect.go b/db/bulkconnect.go index eeefd234..60ca7a64 100644 --- a/db/bulkconnect.go +++ b/db/bulkconnect.go @@ -15,7 +15,6 @@ import ( // 2) rocksdb seems to handle better fewer larger batches than continuous stream of smaller batches type bulkAddresses struct { - height uint32 bi BlockInfo addresses map[string][]outpoint } @@ -154,10 +153,10 @@ func (b *BulkConnect) parallelStoreBalances(c chan error, all bool) { func (b *BulkConnect) storeBulkAddresses(wb *gorocksdb.WriteBatch) error { for _, ba := range b.bulkAddresses { - if err := b.d.storeAddresses(wb, ba.height, ba.addresses); err != nil { + if err := b.d.storeAddresses(wb, ba.bi.Height, ba.addresses); err != nil { return err } - if err := b.d.writeHeight(wb, ba.height, &ba.bi, opInsert); err != nil { + if err := b.d.writeHeight(wb, ba.bi.Height, &ba.bi, opInsert); err != nil { return err } } @@ -190,12 +189,12 @@ func (b *BulkConnect) ConnectBlock(block *bchain.Block, storeBlockTxs bool) erro } } b.bulkAddresses = append(b.bulkAddresses, bulkAddresses{ - height: block.Height, bi: BlockInfo{ - Hash: block.Hash, - Time: block.Time, - Txs: uint32(len(block.Txs)), - Size: uint32(block.Size), + Hash: block.Hash, + Time: block.Time, + Txs: uint32(len(block.Txs)), + Size: uint32(block.Size), + Height: block.Height, }, addresses: addresses, }) diff --git a/db/rocksdb.go b/db/rocksdb.go index b16647cd..8b715830 100644 --- a/db/rocksdb.go +++ b/db/rocksdb.go @@ -876,10 +876,11 @@ func (d *RocksDB) writeAddressesNonUTXO(wb *gorocksdb.WriteBatch, block *bchain. // BlockInfo holds information about blocks kept in column height type BlockInfo struct { - Hash string - Time int64 - Txs uint32 - Size uint32 + Hash string + Time int64 + Txs uint32 + Size uint32 + Height uint32 // Height is not packed! } func (d *RocksDB) packBlockInfo(block *BlockInfo) ([]byte, error) { @@ -959,15 +960,21 @@ func (d *RocksDB) GetBlockInfo(height uint32) (*BlockInfo, error) { return nil, err } defer val.Free() - return d.unpackBlockInfo(val.Data()) + bi, err := d.unpackBlockInfo(val.Data()) + if err != nil { + return nil, err + } + bi.Height = height + return bi, err } func (d *RocksDB) writeHeightFromBlock(wb *gorocksdb.WriteBatch, block *bchain.Block, op int) error { return d.writeHeight(wb, block.Height, &BlockInfo{ - Hash: block.Hash, - Time: block.Time, - Txs: uint32(len(block.Txs)), - Size: uint32(block.Size), + Hash: block.Hash, + Time: block.Time, + Txs: uint32(len(block.Txs)), + Size: uint32(block.Size), + Height: block.Height, }, op) } diff --git a/db/rocksdb_test.go b/db/rocksdb_test.go index 5b04fa3f..34ede351 100644 --- a/db/rocksdb_test.go +++ b/db/rocksdb_test.go @@ -730,10 +730,11 @@ func TestRocksDB_Index_UTXO(t *testing.T) { t.Fatal(err) } iw := &BlockInfo{ - Hash: "00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6", - Txs: 4, - Size: 2345678, - Time: 1534859123, + Hash: "00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6", + Txs: 4, + Size: 2345678, + Time: 1534859123, + Height: 225494, } if !reflect.DeepEqual(info, iw) { t.Errorf("GetAddressBalance() = %+v, want %+v", info, iw) diff --git a/server/public.go b/server/public.go index f0561715..3c67e9c2 100644 --- a/server/public.go +++ b/server/public.go @@ -21,6 +21,7 @@ import ( const blockbookAbout = "Blockbook - blockchain indexer for TREZOR wallet https://trezor.io/. Do not use for any other purpose." const txsOnPage = 25 +const blocksOnPage = 50 const txsInAPI = 1000 // PublicServer is a handle to public http server @@ -88,6 +89,7 @@ func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bch 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.Handle(path+"static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/")))) // API calls serveMux.HandleFunc(path+"api/block-index/", s.jsonHandler(s.apiBlockIndex)) @@ -277,6 +279,7 @@ const ( errorTpl txTpl addressTpl + blocksTpl tplCount ) @@ -288,6 +291,7 @@ type TemplateData struct { AddrStr string Tx *api.Tx Error *api.ApiError + Blocks *api.Blocks Page int PrevPage int NextPage int @@ -305,6 +309,7 @@ func parseTemplates() []*template.Template { t[errorTpl] = template.Must(template.New("error").Funcs(templateFuncMap).ParseFiles("./static/templates/error.html", "./static/templates/base.html")) t[txTpl] = template.Must(template.New("tx").Funcs(templateFuncMap).ParseFiles("./static/templates/tx.html", "./static/templates/txdetail.html", "./static/templates/base.html")) t[addressTpl] = template.Must(template.New("address").Funcs(templateFuncMap).ParseFiles("./static/templates/address.html", "./static/templates/txdetail.html", "./static/templates/paging.html", "./static/templates/base.html")) + t[blocksTpl] = template.Must(template.New("blocks").Funcs(templateFuncMap).ParseFiles("./static/templates/blocks.html", "./static/templates/paging.html", "./static/templates/base.html")) return t } @@ -364,6 +369,25 @@ func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) ( return addressTpl, data, nil } +func (s *PublicServer) explorerBlocks(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) { + var blocks *api.Blocks + var err error + s.metrics.ExplorerViews.With(common.Labels{"action": "blocks"}).Inc() + page, ec := strconv.Atoi(r.URL.Query().Get("page")) + if ec != nil { + page = 0 + } + blocks, err = s.api.GetBlocks(page, blocksOnPage) + if err != nil { + return errorTpl, nil, err + } + data := s.newTemplateData() + data.Blocks = blocks + data.Page = blocks.Page + data.PagingRange, data.PrevPage, data.NextPage = getPagingRange(blocks.Page, blocks.TotalPages) + return blocksTpl, data, nil +} + func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) { q := strings.TrimSpace(r.URL.Query().Get("q")) var tx *api.Tx diff --git a/static/templates/base.html b/static/templates/base.html index 3a1d3283..9d0b049d 100644 --- a/static/templates/base.html +++ b/static/templates/base.html @@ -61,7 +61,7 @@
- {{template "specific" .}} + {{- template "specific" . -}}