Implement list of blocks

This commit is contained in:
Martin Boehm 2018-09-14 12:10:03 +02:00
parent e07b020c16
commit bebddbcd11
8 changed files with 137 additions and 22 deletions

View File

@ -2,6 +2,7 @@ package api
import ( import (
"blockbook/bchain" "blockbook/bchain"
"blockbook/db"
"math/big" "math/big"
) )
@ -90,3 +91,10 @@ type Address struct {
TotalPages int `json:"totalPages"` TotalPages int `json:"totalPages"`
TxsOnPage int `json:"txsOnPage"` TxsOnPage int `json:"txsOnPage"`
} }
type Blocks struct {
Blocks []db.BlockInfo `json:"blocks"`
Page int `json:"page"`
TotalPages int `json:"totalPages"`
BlocksOnPage int `json:"blocksOnPage"`
}

View File

@ -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)) glog.Info("GetAddress ", address, " finished in ", time.Since(start))
return r, nil 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
}

View File

@ -15,7 +15,6 @@ import (
// 2) rocksdb seems to handle better fewer larger batches than continuous stream of smaller batches // 2) rocksdb seems to handle better fewer larger batches than continuous stream of smaller batches
type bulkAddresses struct { type bulkAddresses struct {
height uint32
bi BlockInfo bi BlockInfo
addresses map[string][]outpoint 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 { func (b *BulkConnect) storeBulkAddresses(wb *gorocksdb.WriteBatch) error {
for _, ba := range b.bulkAddresses { 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 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 return err
} }
} }
@ -190,12 +189,12 @@ func (b *BulkConnect) ConnectBlock(block *bchain.Block, storeBlockTxs bool) erro
} }
} }
b.bulkAddresses = append(b.bulkAddresses, bulkAddresses{ b.bulkAddresses = append(b.bulkAddresses, bulkAddresses{
height: block.Height,
bi: BlockInfo{ bi: BlockInfo{
Hash: block.Hash, Hash: block.Hash,
Time: block.Time, Time: block.Time,
Txs: uint32(len(block.Txs)), Txs: uint32(len(block.Txs)),
Size: uint32(block.Size), Size: uint32(block.Size),
Height: block.Height,
}, },
addresses: addresses, addresses: addresses,
}) })

View File

@ -876,10 +876,11 @@ func (d *RocksDB) writeAddressesNonUTXO(wb *gorocksdb.WriteBatch, block *bchain.
// BlockInfo holds information about blocks kept in column height // BlockInfo holds information about blocks kept in column height
type BlockInfo struct { type BlockInfo struct {
Hash string Hash string
Time int64 Time int64
Txs uint32 Txs uint32
Size uint32 Size uint32
Height uint32 // Height is not packed!
} }
func (d *RocksDB) packBlockInfo(block *BlockInfo) ([]byte, error) { func (d *RocksDB) packBlockInfo(block *BlockInfo) ([]byte, error) {
@ -959,15 +960,21 @@ func (d *RocksDB) GetBlockInfo(height uint32) (*BlockInfo, error) {
return nil, err return nil, err
} }
defer val.Free() 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 { func (d *RocksDB) writeHeightFromBlock(wb *gorocksdb.WriteBatch, block *bchain.Block, op int) error {
return d.writeHeight(wb, block.Height, &BlockInfo{ return d.writeHeight(wb, block.Height, &BlockInfo{
Hash: block.Hash, Hash: block.Hash,
Time: block.Time, Time: block.Time,
Txs: uint32(len(block.Txs)), Txs: uint32(len(block.Txs)),
Size: uint32(block.Size), Size: uint32(block.Size),
Height: block.Height,
}, op) }, op)
} }

View File

@ -730,10 +730,11 @@ func TestRocksDB_Index_UTXO(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
iw := &BlockInfo{ iw := &BlockInfo{
Hash: "00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6", Hash: "00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6",
Txs: 4, Txs: 4,
Size: 2345678, Size: 2345678,
Time: 1534859123, Time: 1534859123,
Height: 225494,
} }
if !reflect.DeepEqual(info, iw) { if !reflect.DeepEqual(info, iw) {
t.Errorf("GetAddressBalance() = %+v, want %+v", info, iw) t.Errorf("GetAddressBalance() = %+v, want %+v", info, iw)

View File

@ -21,6 +21,7 @@ import (
const blockbookAbout = "Blockbook - blockchain indexer for TREZOR wallet https://trezor.io/. Do not use for any other purpose." const blockbookAbout = "Blockbook - blockchain indexer for TREZOR wallet https://trezor.io/. Do not use for any other purpose."
const txsOnPage = 25 const txsOnPage = 25
const blocksOnPage = 50
const txsInAPI = 1000 const txsInAPI = 1000
// PublicServer is a handle to public http server // 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/tx/", s.htmlTemplateHandler(s.explorerTx))
serveMux.HandleFunc(path+"explorer/address/", s.htmlTemplateHandler(s.explorerAddress)) serveMux.HandleFunc(path+"explorer/address/", s.htmlTemplateHandler(s.explorerAddress))
serveMux.HandleFunc(path+"explorer/search/", s.htmlTemplateHandler(s.explorerSearch)) 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/")))) serveMux.Handle(path+"static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
// API calls // API calls
serveMux.HandleFunc(path+"api/block-index/", s.jsonHandler(s.apiBlockIndex)) serveMux.HandleFunc(path+"api/block-index/", s.jsonHandler(s.apiBlockIndex))
@ -277,6 +279,7 @@ const (
errorTpl errorTpl
txTpl txTpl
addressTpl addressTpl
blocksTpl
tplCount tplCount
) )
@ -288,6 +291,7 @@ type TemplateData struct {
AddrStr string AddrStr string
Tx *api.Tx Tx *api.Tx
Error *api.ApiError Error *api.ApiError
Blocks *api.Blocks
Page int Page int
PrevPage int PrevPage int
NextPage 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[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[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[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 return t
} }
@ -364,6 +369,25 @@ func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) (
return addressTpl, data, nil 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) { func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
q := strings.TrimSpace(r.URL.Query().Get("q")) q := strings.TrimSpace(r.URL.Query().Get("q"))
var tx *api.Tx var tx *api.Tx

View File

@ -61,7 +61,7 @@
</header> </header>
<main id="wrap"> <main id="wrap">
<div class="container"> <div class="container">
{{template "specific" .}} {{- template "specific" . -}}
</div> </div>
</main> </main>
<footer id="footer" class="footer"> <footer id="footer" class="footer">

View File

@ -0,0 +1,30 @@
{{define "specific"}}{{$blocks := .Blocks}}{{$data := .}}
<h1>Blocks
<small class="text-muted">by date</small>
</h1>
{{if $blocks.Blocks -}}
<nav>{{template "paging" $data }}</nav>
<div class="data-div">
<table class="table table-striped data-table table-hover">
<thead>
<tr>
<th>Height</th>
<th>Timestamp</span></th>
<th class="text-right">Transactions</th>
<th class="text-right">Size</th>
</tr>
</thead>
<tbody>
{{- range $b := $blocks.Blocks -}}
<tr>
<td><a href="/explorer/block/{{$b.Height}}">{{$b.Height}}</a></td>
<td>{{formatUnixTime $b.Time}}</td>
<td class="text-right">{{$b.Txs}}</td>
<td class="text-right">{{$b.Size}}</td>
</tr>
{{- end -}}
</tbody>
</table>
</div>
<nav>{{template "paging" $data }}</nav>
{{end}}{{end}}