Show mempool content in explorer

This commit is contained in:
Martin Boehm 2019-04-02 11:39:38 +02:00
parent f2dc4a56d8
commit 987aec47f9
10 changed files with 149 additions and 0 deletions

View File

@ -337,3 +337,16 @@ type SystemInfo struct {
Blockbook *BlockbookInfo `json:"blockbook"`
Backend *bchain.ChainInfo `json:"backend"`
}
// MempoolTxid contains information about a transaction in mempool
type MempoolTxid struct {
Time int64 `json:"time"`
Txid string `json:"txid"`
}
// MempoolTxids contains a list of mempool txids with paging information
type MempoolTxids struct {
Paging
Mempool []MempoolTxid `json:"mempool"`
MempoolSize int `json:"mempoolSize"`
}

View File

@ -1070,3 +1070,26 @@ func (w *Worker) GetSystemInfo(internal bool) (*SystemInfo, error) {
glog.Info("GetSystemInfo finished in ", time.Since(start))
return &SystemInfo{bi, ci}, nil
}
// GetMempool returns a page of mempool txids
func (w *Worker) GetMempool(page int, itemsOnPage int) (*MempoolTxids, error) {
page--
if page < 0 {
page = 0
}
entries := w.mempool.GetAllEntries()
pg, from, to, page := computePaging(len(entries), page, itemsOnPage)
r := &MempoolTxids{
Paging: pg,
MempoolSize: len(entries),
}
r.Mempool = make([]MempoolTxid, to-from)
for i := from; i < to; i++ {
entry := &entries[i]
r.Mempool[i-from] = MempoolTxid{
Txid: entry.Txid,
Time: int64(entry.Time),
}
}
return r, nil
}

View File

@ -311,3 +311,8 @@ func (c *mempoolWithMetrics) GetAddrDescTransactions(addrDesc bchain.AddressDesc
defer func(s time.Time) { c.observeRPCLatency("GetMempoolTransactionsForAddrDesc", s, err) }(time.Now())
return c.mempool.GetAddrDescTransactions(addrDesc)
}
func (c *mempoolWithMetrics) GetAllEntries() (v bchain.MempoolTxidEntries) {
defer func(s time.Time) { c.observeRPCLatency("GetAllEntries", s, nil) }(time.Now())
return c.mempool.GetAllEntries()
}

View File

@ -1,6 +1,7 @@
package bchain
import (
"sort"
"sync"
"time"
@ -221,3 +222,35 @@ func (m *MempoolBitcoinType) Resync() (int, error) {
glog.Info("mempool: resync finished in ", time.Since(start), ", ", len(m.txEntries), " transactions in mempool")
return len(m.txEntries), nil
}
func (a MempoolTxidEntries) Len() int { return len(a) }
func (a MempoolTxidEntries) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a MempoolTxidEntries) Less(i, j int) bool {
// if the Time is equal, sort by txid to make the order defined
hi := a[i].Time
hj := a[j].Time
if hi == hj {
return a[i].Txid > a[j].Txid
}
// order in reverse
return hi > hj
}
func getAllEntries(txEntries map[string]txEntry) MempoolTxidEntries {
a := make(MempoolTxidEntries, len(txEntries))
i := 0
for txid, entry := range txEntries {
a[i] = MempoolTxidEntry{
Txid: txid,
Time: entry.time,
}
i++
}
sort.Sort(a)
return a
}
// GetAllEntries returns all mempool entries sorted by fist seen time in descending order
func (m *MempoolBitcoinType) GetAllEntries() MempoolTxidEntries {
return getAllEntries(m.txEntries)
}

View File

@ -137,3 +137,8 @@ func (m *MempoolEthereumType) Resync() (int, error) {
glog.Info("Mempool: resync finished in ", time.Since(start), ", ", len(m.txEntries), " transactions in mempool")
return len(m.txEntries), nil
}
// GetAllEntries returns all mempool entries sorted by fist seen time in descending order
func (m *MempoolEthereumType) GetAllEntries() MempoolTxidEntries {
return getAllEntries(m.txEntries)
}

View File

@ -185,6 +185,15 @@ type Erc20Transfer struct {
Tokens big.Int
}
// MempoolTxidEntry contains mempool txid with first seen time
type MempoolTxidEntry struct {
Txid string
Time uint32
}
// MempoolTxidEntries is array of MempoolTxidEntry
type MempoolTxidEntries []MempoolTxidEntry
// OnNewBlockFunc is used to send notification about a new block
type OnNewBlockFunc func(hash string, height uint32)
@ -281,4 +290,5 @@ type Mempool interface {
Resync() (int, error)
GetTransactions(address string) ([]Outpoint, error)
GetAddrDescTransactions(addrDesc AddressDescriptor) ([]Outpoint, error)
GetAllEntries() MempoolTxidEntries
}

View File

@ -26,6 +26,7 @@ import (
const txsOnPage = 25
const blocksOnPage = 50
const mempoolTxsOnPage = 50
const txsInAPI = 1000
const (
@ -139,6 +140,7 @@ func (s *PublicServer) ConnectFullPublicInterface() {
serveMux.HandleFunc(path+"block/", s.htmlTemplateHandler(s.explorerBlock))
serveMux.HandleFunc(path+"spending/", s.htmlTemplateHandler(s.explorerSpendingTx))
serveMux.HandleFunc(path+"sendtx", s.htmlTemplateHandler(s.explorerSendTx))
serveMux.HandleFunc(path+"mempool", s.htmlTemplateHandler(s.explorerMempool))
} else {
// redirect to wallet requests for tx and address, possibly to external site
serveMux.HandleFunc(path+"tx/", s.txRedirect)
@ -384,6 +386,7 @@ const (
blocksTpl
blockTpl
sendTransactionTpl
mempoolTpl
tplCount
)
@ -402,6 +405,7 @@ type TemplateData struct {
Blocks *api.Blocks
Block *api.Block
Info *api.SystemInfo
MempoolTxids *api.MempoolTxids
Page int
PrevPage int
NextPage int
@ -477,6 +481,7 @@ func (s *PublicServer) parseTemplates() []*template.Template {
t[blockTpl] = createTemplate("./static/templates/block.html", "./static/templates/txdetail.html", "./static/templates/paging.html", "./static/templates/base.html")
}
t[xpubTpl] = createTemplate("./static/templates/xpub.html", "./static/templates/txdetail.html", "./static/templates/paging.html", "./static/templates/base.html")
t[mempoolTpl] = createTemplate("./static/templates/mempool.html", "./static/templates/paging.html", "./static/templates/base.html")
return t
}
@ -798,6 +803,25 @@ func (s *PublicServer) explorerSendTx(w http.ResponseWriter, r *http.Request) (t
return sendTransactionTpl, data, nil
}
func (s *PublicServer) explorerMempool(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
var mempoolTxids *api.MempoolTxids
var err error
s.metrics.ExplorerViews.With(common.Labels{"action": "mempool"}).Inc()
page, ec := strconv.Atoi(r.URL.Query().Get("page"))
if ec != nil {
page = 0
}
mempoolTxids, err = s.api.GetMempool(page, mempoolTxsOnPage)
if err != nil {
return errorTpl, nil, err
}
data := s.newTemplateData()
data.MempoolTxids = mempoolTxids
data.Page = mempoolTxids.Page
data.PagingRange, data.PrevPage, data.NextPage = getPagingRange(mempoolTxids.Page, mempoolTxids.TotalPages)
return mempoolTpl, data, nil
}
func getPagingRange(page int, total int) ([]int, int, int) {
// total==-1 means total is unknown, show only prev/next buttons
if total >= 0 && total < 2 {

View File

@ -268,6 +268,11 @@ table.data-table table.data-table th {
margin: 0;
}
.h-container h5 {
margin-top: 6px;
margin-bottom: 0;
}
.page-link {
color: #428bca;
}

View File

@ -43,6 +43,10 @@
<td>Last Mempool Update</td>
<td class="data">{{formatTime $bb.LastMempoolTime}}</td>
</tr>
<tr>
<td>Transactions in Mempool</td>
<td class="data">{{if .InternalExplorer}}<a href="/mempool">{{$bb.MempoolSize}}</a>{{else}}{{$bb.MempoolSize}}{{end}}</td>
</tr>
<tr>
<td>Size On Disk</td>
<td class="data">{{$bb.DbSize}}</td>

View File

@ -0,0 +1,27 @@
{{define "specific"}}{{$txs := .MempoolTxids.Mempool}}{{$data := .}}
<h1>Mempool Transactions <small class="text-muted">by first seen time</small>
</h1>
<div class="row h-container">
<h5 class="col-md-6 col-sm-12">{{$.MempoolTxids.MempoolSize}} Transactions in mempool</h5>
<nav class="col-md-6 col-sm-12">{{template "paging" $data }}</nav>
</div>
<div class="data-div">
<table class="table table-striped data-table table-hover">
<thead>
<tr>
<th style="width: 70%;">Transaction</th>
<th style="width: 30%;">First Seen Time</span></th>
</tr>
</thead>
<tbody>
{{- range $tx := $txs -}}
<tr>
<td class="ellipsis"><a href="/tx/{{$tx.Txid}}">{{$tx.Txid}}</a></td>
<td>{{formatUnixTime $tx.Time}}</td>
</tr>
{{- end -}}
</tbody>
</table>
</div>
<nav>{{template "paging" $data }}</nav>
{{end}}