Implement list of blocks
This commit is contained in:
parent
e07b020c16
commit
bebddbcd11
@ -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"`
|
||||||
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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">
|
||||||
|
|||||||
30
static/templates/blocks.html
Normal file
30
static/templates/blocks.html
Normal 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}}
|
||||||
Loading…
Reference in New Issue
Block a user