Implement list of blocks

pull/56/head
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 (
"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"`
}

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))
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
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,
})

View File

@ -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)
}

View File

@ -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)

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 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

View File

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