Implement list of blocks
parent
e07b020c16
commit
bebddbcd11
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
</header>
|
||||
<main id="wrap">
|
||||
<div class="container">
|
||||
{{template "specific" .}}
|
||||
{{- template "specific" . -}}
|
||||
</div>
|
||||
</main>
|
||||
<footer id="footer" class="footer">
|
||||
|
|
|
@ -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}}
|
Loading…
Reference in New Issue