Merge branch 'integration-tests-upgrade' into tests
commit
c5cb1e2e54
|
@ -69,9 +69,9 @@
|
|||
|
||||
[[projects]]
|
||||
name = "github.com/ethereum/go-ethereum"
|
||||
packages = [".","common","common/hexutil","common/math","core/types","crypto","crypto/secp256k1","crypto/sha3","ethclient","ethdb","log","metrics","params","rlp","rpc","trie"]
|
||||
revision = "329ac18ef617d0238f71637bffe78f028b0f13f7"
|
||||
version = "v1.8.3"
|
||||
packages = [".","common","common/hexutil","common/math","core/types","crypto","crypto/secp256k1","crypto/sha3","ethclient","ethdb","log","metrics","p2p/netutil","params","rlp","rpc","trie"]
|
||||
revision = "89451f7c382ad2185987ee369f16416f89c28a7d"
|
||||
version = "v1.8.15"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/go-stack/stack"
|
||||
|
|
54
api/types.go
54
api/types.go
|
@ -2,9 +2,14 @@ package api
|
|||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/common"
|
||||
"blockbook/db"
|
||||
"math/big"
|
||||
"time"
|
||||
)
|
||||
|
||||
const BlockbookAbout = "Blockbook - blockchain indexer for TREZOR wallet https://trezor.io/. Do not use for any other purpose."
|
||||
|
||||
type ApiError struct {
|
||||
Text string
|
||||
Public bool
|
||||
|
@ -73,10 +78,17 @@ type Tx struct {
|
|||
Size int `json:"size,omitempty"`
|
||||
ValueIn string `json:"valueIn"`
|
||||
Fees string `json:"fees"`
|
||||
WithSpends bool `json:"withSpends,omitempty"`
|
||||
Hex string `json:"hex"`
|
||||
}
|
||||
|
||||
type Paging struct {
|
||||
Page int `json:"page"`
|
||||
TotalPages int `json:"totalPages"`
|
||||
ItemsOnPage int `json:"itemsOnPage"`
|
||||
}
|
||||
|
||||
type Address struct {
|
||||
Paging
|
||||
AddrStr string `json:"addrStr"`
|
||||
Balance string `json:"balance"`
|
||||
TotalReceived string `json:"totalReceived"`
|
||||
|
@ -86,7 +98,41 @@ type Address struct {
|
|||
TxApperances int `json:"txApperances"`
|
||||
Transactions []*Tx `json:"txs,omitempty"`
|
||||
Txids []string `json:"transactions,omitempty"`
|
||||
Page int `json:"page"`
|
||||
TotalPages int `json:"totalPages"`
|
||||
TxsOnPage int `json:"txsOnPage"`
|
||||
}
|
||||
|
||||
type Blocks struct {
|
||||
Paging
|
||||
Blocks []db.BlockInfo `json:"blocks"`
|
||||
}
|
||||
|
||||
type Block struct {
|
||||
Paging
|
||||
bchain.BlockInfo
|
||||
TxCount int `json:"TxCount"`
|
||||
Transactions []*Tx `json:"txs,omitempty"`
|
||||
}
|
||||
|
||||
type BlockbookInfo struct {
|
||||
Coin string `json:"coin"`
|
||||
Host string `json:"host"`
|
||||
Version string `json:"version"`
|
||||
GitCommit string `json:"gitcommit"`
|
||||
BuildTime string `json:"buildtime"`
|
||||
SyncMode bool `json:"syncMode"`
|
||||
InitialSync bool `json:"initialsync"`
|
||||
InSync bool `json:"inSync"`
|
||||
BestHeight uint32 `json:"bestHeight"`
|
||||
LastBlockTime time.Time `json:"lastBlockTime"`
|
||||
InSyncMempool bool `json:"inSyncMempool"`
|
||||
LastMempoolTime time.Time `json:"lastMempoolTime"`
|
||||
MempoolSize int `json:"mempoolSize"`
|
||||
DbSize int64 `json:"dbSize"`
|
||||
DbSizeFromColumns int64 `json:"dbSizeFromColumns,omitempty"`
|
||||
DbColumns []common.InternalStateColumn `json:"dbColumns,omitempty"`
|
||||
About string `json:"about"`
|
||||
}
|
||||
|
||||
type SystemInfo struct {
|
||||
Blockbook *BlockbookInfo `json:"blockbook"`
|
||||
Backend *bchain.ChainInfo `json:"backend"`
|
||||
}
|
||||
|
|
256
api/worker.go
256
api/worker.go
|
@ -7,6 +7,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
@ -43,6 +44,61 @@ func (w *Worker) getAddressesFromVout(vout *bchain.Vout) (bchain.AddressDescript
|
|||
return addrDesc, a, s, err
|
||||
}
|
||||
|
||||
// setSpendingTxToVout is helper function, that finds transaction that spent given output and sets it to the output
|
||||
// there is not an index, it must be found using addresses -> txaddresses -> tx
|
||||
func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height, bestheight uint32) error {
|
||||
err := w.db.GetAddrDescTransactions(vout.ScriptPubKey.AddrDesc, height, ^uint32(0), func(t string, index uint32, isOutput bool) error {
|
||||
if isOutput == false {
|
||||
tsp, err := w.db.GetTxAddresses(t)
|
||||
if err != nil {
|
||||
glog.Warning("DB inconsistency: tx ", t, ": not found in txAddresses")
|
||||
} else {
|
||||
if len(tsp.Inputs) > int(index) {
|
||||
if tsp.Inputs[index].ValueSat.Cmp(&vout.ValueSat) == 0 {
|
||||
spentTx, spentHeight, err := w.txCache.GetTransaction(t, bestheight)
|
||||
if err != nil {
|
||||
glog.Warning("Tx ", t, ": not found")
|
||||
} else {
|
||||
if len(spentTx.Vin) > int(index) {
|
||||
if spentTx.Vin[index].Txid == txid {
|
||||
vout.SpentTxID = t
|
||||
vout.SpentHeight = int(spentHeight)
|
||||
vout.SpentIndex = int(index)
|
||||
return &db.StopIteration{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// GetSpendingTxid returns transaction id of transaction that spent given output
|
||||
func (w *Worker) GetSpendingTxid(txid string, n int) (string, error) {
|
||||
start := time.Now()
|
||||
bestheight, _, err := w.db.GetBestBlock()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tx, err := w.GetTransaction(txid, bestheight, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if n >= len(tx.Vout) || n < 0 {
|
||||
return "", NewApiError(fmt.Sprintf("Passed incorrect vout index %v for tx %v, len vout %v", n, tx.Txid, len(tx.Vout)), false)
|
||||
}
|
||||
err = w.setSpendingTxToVout(&tx.Vout[n], tx.Txid, uint32(tx.Blockheight), bestheight)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
glog.Info("GetSpendingTxid ", txid, " ", n, " finished in ", time.Since(start))
|
||||
return tx.Vout[n].SpentTxID, nil
|
||||
}
|
||||
|
||||
// GetTransaction reads transaction data from txid
|
||||
func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTxs bool) (*Tx, error) {
|
||||
start := time.Now()
|
||||
|
@ -124,37 +180,9 @@ func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTxs bool
|
|||
if ta != nil {
|
||||
vout.Spent = ta.Outputs[i].Spent
|
||||
if spendingTxs && vout.Spent {
|
||||
// find transaction that spent this output
|
||||
// there is not an index, it must be found in addresses -> txaddresses -> tx
|
||||
err = w.db.GetAddrDescTransactions(vout.ScriptPubKey.AddrDesc, height, ^uint32(0), func(t string, index uint32, isOutput bool) error {
|
||||
if isOutput == false {
|
||||
tsp, err := w.db.GetTxAddresses(t)
|
||||
if err != nil {
|
||||
glog.Warning("DB inconsistency: tx ", t, ": not found in txAddresses")
|
||||
} else {
|
||||
if len(tsp.Inputs) > int(index) {
|
||||
if tsp.Inputs[index].ValueSat.Cmp(&vout.ValueSat) == 0 {
|
||||
spentTx, spentHeight, err := w.txCache.GetTransaction(t, bestheight)
|
||||
if err != nil {
|
||||
glog.Warning("Tx ", t, ": not found")
|
||||
} else {
|
||||
if len(spentTx.Vin) > int(index) {
|
||||
if spentTx.Vin[index].Txid == bchainTx.Txid {
|
||||
vout.SpentTxID = t
|
||||
vout.SpentHeight = int(spentHeight)
|
||||
vout.SpentIndex = int(index)
|
||||
return &db.StopIteration{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
err = w.setSpendingTxToVout(vout, bchainTx.Txid, height, bestheight)
|
||||
if err != nil {
|
||||
glog.Errorf("GetAddrDescTransactions error %v, %v, output %v", err, vout.ScriptPubKey.AddrDesc)
|
||||
glog.Errorf("setSpendingTxToVout error %v, %v, output %v", err, vout.ScriptPubKey.AddrDesc, vout.N)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -173,12 +201,12 @@ func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTxs bool
|
|||
Confirmations: bchainTx.Confirmations,
|
||||
Fees: w.chainParser.AmountToDecimalString(&feesSat),
|
||||
Locktime: bchainTx.LockTime,
|
||||
WithSpends: spendingTxs,
|
||||
Time: bchainTx.Time,
|
||||
Txid: bchainTx.Txid,
|
||||
ValueIn: w.chainParser.AmountToDecimalString(&valInSat),
|
||||
ValueOut: w.chainParser.AmountToDecimalString(&valOutSat),
|
||||
Version: bchainTx.Version,
|
||||
Hex: bchainTx.Hex,
|
||||
Vin: vins,
|
||||
Vout: vouts,
|
||||
}
|
||||
|
@ -296,6 +324,27 @@ func (w *Worker) txFromTxAddress(txid string, ta *db.TxAddresses, bi *db.BlockIn
|
|||
return r
|
||||
}
|
||||
|
||||
func computePaging(count, page, itemsOnPage int) (Paging, int, int, int) {
|
||||
from := page * itemsOnPage
|
||||
totalPages := (count - 1) / itemsOnPage
|
||||
if totalPages < 0 {
|
||||
totalPages = 0
|
||||
}
|
||||
if from >= count {
|
||||
page = totalPages
|
||||
}
|
||||
from = page * itemsOnPage
|
||||
to := (page + 1) * itemsOnPage
|
||||
if to > count {
|
||||
to = count
|
||||
}
|
||||
return Paging{
|
||||
ItemsOnPage: itemsOnPage,
|
||||
Page: page + 1,
|
||||
TotalPages: totalPages + 1,
|
||||
}, from, to, page
|
||||
}
|
||||
|
||||
// GetAddress computes address value and gets transactions for given address
|
||||
func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids bool) (*Address, error) {
|
||||
start := time.Now()
|
||||
|
@ -341,23 +390,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids b
|
|||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "GetBestBlock")
|
||||
}
|
||||
// paging
|
||||
from := page * txsOnPage
|
||||
totalPages := (len(txc) - 1) / txsOnPage
|
||||
if totalPages < 0 {
|
||||
totalPages = 0
|
||||
}
|
||||
if from >= len(txc) {
|
||||
page = totalPages - 1
|
||||
if page < 0 {
|
||||
page = 0
|
||||
}
|
||||
}
|
||||
from = page * txsOnPage
|
||||
to := (page + 1) * txsOnPage
|
||||
if to > len(txc) {
|
||||
to = len(txc)
|
||||
}
|
||||
pg, from, to, page := computePaging(len(txc), page, txsOnPage)
|
||||
var txs []*Tx
|
||||
var txids []string
|
||||
if onlyTxids {
|
||||
|
@ -420,6 +453,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids b
|
|||
txs = txs[:txi]
|
||||
}
|
||||
r := &Address{
|
||||
Paging: pg,
|
||||
AddrStr: address,
|
||||
Balance: w.chainParser.AmountToDecimalString(&ba.BalanceSat),
|
||||
TotalReceived: w.chainParser.AmountToDecimalString(ba.ReceivedSat()),
|
||||
|
@ -429,10 +463,130 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids b
|
|||
UnconfirmedTxApperances: len(txm),
|
||||
Transactions: txs,
|
||||
Txids: txids,
|
||||
Page: page + 1,
|
||||
TotalPages: totalPages + 1,
|
||||
TxsOnPage: txsOnPage,
|
||||
}
|
||||
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")
|
||||
}
|
||||
pg, from, to, page := computePaging(bestheight+1, page, blocksOnPage)
|
||||
r := &Blocks{Paging: pg}
|
||||
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
|
||||
}
|
||||
|
||||
// GetBlock returns paged data about block
|
||||
func (w *Worker) GetBlock(bid string, page int, txsOnPage int) (*Block, error) {
|
||||
start := time.Now()
|
||||
page--
|
||||
if page < 0 {
|
||||
page = 0
|
||||
}
|
||||
var hash string
|
||||
height, err := strconv.Atoi(bid)
|
||||
if err == nil && height < int(^uint32(0)) {
|
||||
hash, err = w.db.GetBlockHash(uint32(height))
|
||||
} else {
|
||||
hash = bid
|
||||
}
|
||||
bi, err := w.chain.GetBlockInfo(hash)
|
||||
if err != nil {
|
||||
if err == bchain.ErrBlockNotFound {
|
||||
return nil, NewApiError("Block not found", true)
|
||||
}
|
||||
return nil, NewApiError(fmt.Sprintf("Block not found, %v", err), true)
|
||||
}
|
||||
dbi := &db.BlockInfo{
|
||||
Hash: bi.Hash,
|
||||
Height: bi.Height,
|
||||
Time: bi.Time,
|
||||
}
|
||||
txCount := len(bi.Txids)
|
||||
bestheight, _, err := w.db.GetBestBlock()
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "GetBestBlock")
|
||||
}
|
||||
pg, from, to, page := computePaging(txCount, page, txsOnPage)
|
||||
glog.Info("GetBlock ", bid, ", page ", page, " finished in ", time.Since(start))
|
||||
txs := make([]*Tx, to-from)
|
||||
txi := 0
|
||||
for i := from; i < to; i++ {
|
||||
txid := bi.Txids[i]
|
||||
ta, err := w.db.GetTxAddresses(txid)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "GetTxAddresses %v", txid)
|
||||
}
|
||||
if ta == nil {
|
||||
glog.Warning("DB inconsistency: tx ", txid, ": not found in txAddresses")
|
||||
continue
|
||||
}
|
||||
txs[txi] = w.txFromTxAddress(txid, ta, dbi, bestheight)
|
||||
txi++
|
||||
}
|
||||
txs = txs[:txi]
|
||||
bi.Txids = nil
|
||||
return &Block{
|
||||
Paging: pg,
|
||||
BlockInfo: *bi,
|
||||
TxCount: txCount,
|
||||
Transactions: txs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetSystemInfo returns information about system
|
||||
func (w *Worker) GetSystemInfo(internal bool) (*SystemInfo, error) {
|
||||
start := time.Now()
|
||||
ci, err := w.chain.GetChainInfo()
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "GetChainInfo")
|
||||
}
|
||||
vi := common.GetVersionInfo()
|
||||
ss, bh, st := w.is.GetSyncState()
|
||||
ms, mt, msz := w.is.GetMempoolSyncState()
|
||||
var dbc []common.InternalStateColumn
|
||||
var dbs int64
|
||||
if internal {
|
||||
dbc = w.is.GetAllDBColumnStats()
|
||||
dbs = w.is.DBSizeTotal()
|
||||
}
|
||||
bi := &BlockbookInfo{
|
||||
Coin: w.is.Coin,
|
||||
Host: w.is.Host,
|
||||
Version: vi.Version,
|
||||
GitCommit: vi.GitCommit,
|
||||
BuildTime: vi.BuildTime,
|
||||
SyncMode: w.is.SyncMode,
|
||||
InitialSync: w.is.InitialSync,
|
||||
InSync: ss,
|
||||
BestHeight: bh,
|
||||
LastBlockTime: st,
|
||||
InSyncMempool: ms,
|
||||
LastMempoolTime: mt,
|
||||
MempoolSize: msz,
|
||||
DbSize: w.db.DatabaseSizeOnDisk(),
|
||||
DbSizeFromColumns: dbs,
|
||||
DbColumns: dbc,
|
||||
About: BlockbookAbout,
|
||||
}
|
||||
glog.Info("GetSystemInfo finished in ", time.Since(start))
|
||||
return &SystemInfo{bi, ci}, nil
|
||||
}
|
||||
|
|
|
@ -157,7 +157,7 @@ func Test_GetAddressesFromAddrDesc(t *testing.T) {
|
|||
{
|
||||
name: "OP_RETURN hex",
|
||||
parser: mainParserCashAddr,
|
||||
addresses: []string{"OP_RETURN 07 2020f1686f6a20"},
|
||||
addresses: []string{"OP_RETURN 2020f1686f6a20"},
|
||||
searchable: false,
|
||||
hex: "6a072020f1686f6a20",
|
||||
wantErr: false,
|
||||
|
|
|
@ -101,7 +101,10 @@ func (b *BCashRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
|||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
// size is not returned by GetBlockHeader and would be overwritten
|
||||
size := block.Size
|
||||
block.BlockHeader = *header
|
||||
block.Size = size
|
||||
return block, nil
|
||||
}
|
||||
|
||||
|
@ -127,6 +130,28 @@ func (b *BCashRPC) GetBlockRaw(hash string) ([]byte, error) {
|
|||
return hex.DecodeString(res.Result)
|
||||
}
|
||||
|
||||
// GetBlockInfo returns extended header (more info than in bchain.BlockHeader) with a list of txids
|
||||
func (b *BCashRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
|
||||
glog.V(1).Info("rpc: getblock (verbosity=1) ", hash)
|
||||
|
||||
res := btc.ResGetBlockInfo{}
|
||||
req := cmdGetBlock{Method: "getblock"}
|
||||
req.Params.BlockHash = hash
|
||||
req.Params.Verbose = true
|
||||
err := b.Call(&req, &res)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
if res.Error != nil {
|
||||
if isErrBlockNotFound(res.Error) {
|
||||
return nil, bchain.ErrBlockNotFound
|
||||
}
|
||||
return nil, errors.Annotatef(res.Error, "hash %v", hash)
|
||||
}
|
||||
return &res.Result, nil
|
||||
}
|
||||
|
||||
// GetBlockFull returns block with given hash.
|
||||
func (b *BCashRPC) GetBlockFull(hash string) (*bchain.Block, error) {
|
||||
return nil, errors.New("Not implemented")
|
||||
|
|
|
@ -35,6 +35,7 @@ func init() {
|
|||
BlockChainFactories["Zcash"] = zec.NewZCashRPC
|
||||
BlockChainFactories["Zcash Testnet"] = zec.NewZCashRPC
|
||||
BlockChainFactories["Ethereum"] = eth.NewEthereumRPC
|
||||
BlockChainFactories["Ethereum Classic"] = eth.NewEthereumRPC
|
||||
BlockChainFactories["Ethereum Testnet Ropsten"] = eth.NewEthereumRPC
|
||||
BlockChainFactories["Bcash"] = bch.NewBCashRPC
|
||||
BlockChainFactories["Bcash Testnet"] = bch.NewBCashRPC
|
||||
|
@ -131,9 +132,9 @@ func (c *blockChainWithMetrics) GetSubversion() string {
|
|||
return c.b.GetSubversion()
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) GetBlockChainInfo() (v string, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("GetBlockChainInfo", s, err) }(time.Now())
|
||||
return c.b.GetBlockChainInfo()
|
||||
func (c *blockChainWithMetrics) GetChainInfo() (v *bchain.ChainInfo, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("GetChainInfo", s, err) }(time.Now())
|
||||
return c.b.GetChainInfo()
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) GetBestBlockHash() (v string, err error) {
|
||||
|
@ -161,6 +162,11 @@ func (c *blockChainWithMetrics) GetBlock(hash string, height uint32) (v *bchain.
|
|||
return c.b.GetBlock(hash, height)
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) GetBlockInfo(hash string) (v *bchain.BlockInfo, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("GetBlockInfo", s, err) }(time.Now())
|
||||
return c.b.GetBlockInfo(hash)
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) GetMempool() (v []string, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("GetMempool", s, err) }(time.Now())
|
||||
return c.b.GetMempool()
|
||||
|
@ -191,7 +197,7 @@ func (c *blockChainWithMetrics) SendRawTransaction(tx string) (v string, err err
|
|||
return c.b.SendRawTransaction(tx)
|
||||
}
|
||||
|
||||
func (c *blockChainWithMetrics) ResyncMempool(onNewTxAddr func(txid string, addr string)) (count int, err error) {
|
||||
func (c *blockChainWithMetrics) ResyncMempool(onNewTxAddr bchain.OnNewTxAddrFunc) (count int, err error) {
|
||||
defer func(s time.Time) { c.observeRPCLatency("ResyncMempool", s, err) }(time.Now())
|
||||
count, err = c.b.ResyncMempool(onNewTxAddr)
|
||||
if err == nil {
|
||||
|
|
|
@ -87,8 +87,22 @@ func (p *BitcoinParser) addressToOutputScript(address string) ([]byte, error) {
|
|||
// TryParseOPReturn tries to process OP_RETURN script and return its string representation
|
||||
func TryParseOPReturn(script []byte) string {
|
||||
if len(script) > 1 && script[0] == txscript.OP_RETURN {
|
||||
l := int(script[1])
|
||||
data := script[2:]
|
||||
// trying 2 variants of OP_RETURN data
|
||||
// 1) OP_RETURN OP_PUSHDATA1 <datalen> <data>
|
||||
// 2) OP_RETURN <datalen> <data>
|
||||
var data []byte
|
||||
var l int
|
||||
if script[1] == txscript.OP_PUSHDATA1 && len(script) > 2 {
|
||||
l = int(script[2])
|
||||
data = script[3:]
|
||||
if l != len(data) {
|
||||
l = int(script[1])
|
||||
data = script[2:]
|
||||
}
|
||||
} else {
|
||||
l = int(script[1])
|
||||
data = script[2:]
|
||||
}
|
||||
if l == len(data) {
|
||||
isASCII := true
|
||||
for _, c := range data {
|
||||
|
@ -101,7 +115,7 @@ func TryParseOPReturn(script []byte) string {
|
|||
if isASCII {
|
||||
ed = "(" + string(data) + ")"
|
||||
} else {
|
||||
ed = hex.EncodeToString([]byte{byte(l)}) + " " + hex.EncodeToString(data)
|
||||
ed = hex.EncodeToString(data)
|
||||
}
|
||||
return "OP_RETURN " + ed
|
||||
}
|
||||
|
|
|
@ -108,10 +108,17 @@ func Test_GetAddressesFromAddrDesc(t *testing.T) {
|
|||
want2: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "OP_RETURN OP_PUSHDATA1 ascii",
|
||||
args: args{script: "6a4c0b446c6f7568792074657874"},
|
||||
want: []string{"OP_RETURN (Dlouhy text)"},
|
||||
want2: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "OP_RETURN hex",
|
||||
args: args{script: "6a072020f1686f6a20"},
|
||||
want: []string{"OP_RETURN 07 2020f1686f6a20"},
|
||||
want: []string{"OP_RETURN 2020f1686f6a20"},
|
||||
want2: false,
|
||||
wantErr: false,
|
||||
},
|
||||
|
|
|
@ -102,10 +102,11 @@ func NewBitcoinRPC(config json.RawMessage, pushHandler func(bchain.NotificationT
|
|||
// and if successful it connects to ZeroMQ and creates mempool handler
|
||||
func (b *BitcoinRPC) GetChainInfoAndInitializeMempool(bc bchain.BlockChain) (string, error) {
|
||||
// try to connect to block chain and get some info
|
||||
chainName, err := bc.GetBlockChainInfo()
|
||||
ci, err := bc.GetChainInfo()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
chainName := ci.Chain
|
||||
|
||||
mq, err := bchain.NewMQ(b.ChainConfig.MessageQueueBinding, b.pushHandler)
|
||||
if err != nil {
|
||||
|
@ -217,10 +218,30 @@ type CmdGetBlockChainInfo struct {
|
|||
type ResGetBlockChainInfo struct {
|
||||
Error *bchain.RPCError `json:"error"`
|
||||
Result struct {
|
||||
Chain string `json:"chain"`
|
||||
Blocks int `json:"blocks"`
|
||||
Headers int `json:"headers"`
|
||||
Bestblockhash string `json:"bestblockhash"`
|
||||
Chain string `json:"chain"`
|
||||
Blocks int `json:"blocks"`
|
||||
Headers int `json:"headers"`
|
||||
Bestblockhash string `json:"bestblockhash"`
|
||||
Difficulty json.Number `json:"difficulty"`
|
||||
SizeOnDisk int64 `json:"size_on_disk"`
|
||||
Warnings string `json:"warnings"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
// getnetworkinfo
|
||||
|
||||
type CmdGetNetworkInfo struct {
|
||||
Method string `json:"method"`
|
||||
}
|
||||
|
||||
type ResGetNetworkInfo struct {
|
||||
Error *bchain.RPCError `json:"error"`
|
||||
Result struct {
|
||||
Version json.Number `json:"version"`
|
||||
Subversion json.Number `json:"subversion"`
|
||||
ProtocolVersion json.Number `json:"protocolversion"`
|
||||
Timeoffset float64 `json:"timeoffset"`
|
||||
Warnings string `json:"warnings"`
|
||||
} `json:"result"`
|
||||
}
|
||||
|
||||
|
@ -265,9 +286,14 @@ type ResGetBlockRaw struct {
|
|||
Result string `json:"result"`
|
||||
}
|
||||
|
||||
type BlockThin struct {
|
||||
bchain.BlockHeader
|
||||
Txids []string `json:"tx"`
|
||||
}
|
||||
|
||||
type ResGetBlockThin struct {
|
||||
Error *bchain.RPCError `json:"error"`
|
||||
Result bchain.ThinBlock `json:"result"`
|
||||
Result BlockThin `json:"result"`
|
||||
}
|
||||
|
||||
type ResGetBlockFull struct {
|
||||
|
@ -275,6 +301,11 @@ type ResGetBlockFull struct {
|
|||
Result bchain.Block `json:"result"`
|
||||
}
|
||||
|
||||
type ResGetBlockInfo struct {
|
||||
Error *bchain.RPCError `json:"error"`
|
||||
Result bchain.BlockInfo `json:"result"`
|
||||
}
|
||||
|
||||
// getrawtransaction
|
||||
|
||||
type CmdGetRawTransaction struct {
|
||||
|
@ -386,21 +417,48 @@ func (b *BitcoinRPC) GetBestBlockHeight() (uint32, error) {
|
|||
return res.Result, nil
|
||||
}
|
||||
|
||||
// GetBlockChainInfo returns the name of the block chain: main/test/regtest.
|
||||
func (b *BitcoinRPC) GetBlockChainInfo() (string, error) {
|
||||
// GetChainInfo returns information about the connected backend
|
||||
func (b *BitcoinRPC) GetChainInfo() (*bchain.ChainInfo, error) {
|
||||
glog.V(1).Info("rpc: getblockchaininfo")
|
||||
|
||||
res := ResGetBlockChainInfo{}
|
||||
req := CmdGetBlockChainInfo{Method: "getblockchaininfo"}
|
||||
err := b.Call(&req, &res)
|
||||
|
||||
resCi := ResGetBlockChainInfo{}
|
||||
err := b.Call(&CmdGetBlockChainInfo{Method: "getblockchaininfo"}, &resCi)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
if res.Error != nil {
|
||||
return "", res.Error
|
||||
if resCi.Error != nil {
|
||||
return nil, resCi.Error
|
||||
}
|
||||
return res.Result.Chain, nil
|
||||
|
||||
glog.V(1).Info("rpc: getnetworkinfo")
|
||||
resNi := ResGetNetworkInfo{}
|
||||
err = b.Call(&CmdGetNetworkInfo{Method: "getnetworkinfo"}, &resNi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resNi.Error != nil {
|
||||
return nil, resNi.Error
|
||||
}
|
||||
|
||||
rv := &bchain.ChainInfo{
|
||||
Bestblockhash: resCi.Result.Bestblockhash,
|
||||
Blocks: resCi.Result.Blocks,
|
||||
Chain: resCi.Result.Chain,
|
||||
Difficulty: string(resCi.Result.Difficulty),
|
||||
Headers: resCi.Result.Headers,
|
||||
SizeOnDisk: resCi.Result.SizeOnDisk,
|
||||
Subversion: string(resNi.Result.Subversion),
|
||||
Timeoffset: resNi.Result.Timeoffset,
|
||||
}
|
||||
rv.Version = string(resNi.Result.Version)
|
||||
rv.ProtocolVersion = string(resNi.Result.ProtocolVersion)
|
||||
if len(resCi.Result.Warnings) > 0 {
|
||||
rv.Warnings = resCi.Result.Warnings + " "
|
||||
}
|
||||
if resCi.Result.Warnings != resNi.Result.Warnings {
|
||||
rv.Warnings += resNi.Result.Warnings
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func isErrBlockNotFound(err *bchain.RPCError) bool {
|
||||
|
@ -454,7 +512,7 @@ func (b *BitcoinRPC) GetBlockHeader(hash string) (*bchain.BlockHeader, error) {
|
|||
// GetBlock returns block with given hash.
|
||||
func (b *BitcoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
||||
var err error
|
||||
if hash == "" && height > 0 {
|
||||
if hash == "" {
|
||||
hash, err = b.GetBlockHash(height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -483,7 +541,29 @@ func (b *BitcoinRPC) GetBlock(hash string, height uint32) (*bchain.Block, error)
|
|||
return block, nil
|
||||
}
|
||||
|
||||
// getBlockWithoutHeader is an optimization - it does not call GetBlockHeader to get prev, next hashes
|
||||
// GetBlockInfo returns extended header (more info than in bchain.BlockHeader) with a list of txids
|
||||
func (b *BitcoinRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
|
||||
glog.V(1).Info("rpc: getblock (verbosity=1) ", hash)
|
||||
|
||||
res := ResGetBlockInfo{}
|
||||
req := CmdGetBlock{Method: "getblock"}
|
||||
req.Params.BlockHash = hash
|
||||
req.Params.Verbosity = 1
|
||||
err := b.Call(&req, &res)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "hash %v", hash)
|
||||
}
|
||||
if res.Error != nil {
|
||||
if isErrBlockNotFound(res.Error) {
|
||||
return nil, bchain.ErrBlockNotFound
|
||||
}
|
||||
return nil, errors.Annotatef(res.Error, "hash %v", hash)
|
||||
}
|
||||
return &res.Result, nil
|
||||
}
|
||||
|
||||
// GetBlockWithoutHeader is an optimization - it does not call GetBlockHeader to get prev, next hashes
|
||||
// instead it sets to header only block hash and height passed in parameters
|
||||
func (b *BitcoinRPC) GetBlockWithoutHeader(hash string, height uint32) (*bchain.Block, error) {
|
||||
data, err := b.GetBlockRaw(hash)
|
||||
|
@ -613,7 +693,7 @@ func (b *BitcoinRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
|||
// ResyncMempool gets mempool transactions and maps output scripts to transactions.
|
||||
// ResyncMempool is not reentrant, it should be called from a single thread.
|
||||
// It returns number of transactions in mempool
|
||||
func (b *BitcoinRPC) ResyncMempool(onNewTxAddr func(txid string, addr string)) (int, error) {
|
||||
func (b *BitcoinRPC) ResyncMempool(onNewTxAddr bchain.OnNewTxAddrFunc) (int, error) {
|
||||
return b.Mempool.Resync(onNewTxAddr)
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"blockbook/bchain/coins/btc"
|
||||
"blockbook/bchain/coins/utils"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
|
@ -22,7 +23,7 @@ var (
|
|||
TestNetParams chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
func initParams() {
|
||||
MainNetParams = chaincfg.MainNetParams
|
||||
MainNetParams.Net = MainnetMagic
|
||||
|
||||
|
@ -62,6 +63,9 @@ func NewBGoldParser(params *chaincfg.Params, c *btc.Configuration) *BGoldParser
|
|||
// the regression test Bitcoin Cash network, the test Bitcoin Cash network and
|
||||
// the simulation test Bitcoin Cash network, in this order
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
if MainNetParams.Name == "" {
|
||||
initParams()
|
||||
}
|
||||
switch chain {
|
||||
case "test":
|
||||
return &TestNetParams
|
||||
|
@ -75,11 +79,13 @@ func GetChainParams(chain string) *chaincfg.Params {
|
|||
// headerFixedLength is the length of fixed fields of a block (i.e. without solution)
|
||||
// see https://github.com/BTCGPU/BTCGPU/wiki/Technical-Spec#block-header
|
||||
const headerFixedLength = 44 + (chainhash.HashSize * 3)
|
||||
const timestampOffset = 100
|
||||
const timestampLength = 4
|
||||
|
||||
// ParseBlock parses raw block to our Block struct
|
||||
func (p *BGoldParser) ParseBlock(b []byte) (*bchain.Block, error) {
|
||||
r := bytes.NewReader(b)
|
||||
err := skipHeader(r, 0)
|
||||
time, err := getTimestampAndSkipHeader(r, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -95,24 +101,41 @@ func (p *BGoldParser) ParseBlock(b []byte) (*bchain.Block, error) {
|
|||
txs[ti] = p.TxFromMsgTx(t, false)
|
||||
}
|
||||
|
||||
return &bchain.Block{Txs: txs}, nil
|
||||
return &bchain.Block{
|
||||
BlockHeader: bchain.BlockHeader{
|
||||
Size: len(b),
|
||||
Time: time,
|
||||
},
|
||||
Txs: txs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func skipHeader(r io.ReadSeeker, pver uint32) error {
|
||||
_, err := r.Seek(headerFixedLength, io.SeekStart)
|
||||
func getTimestampAndSkipHeader(r io.ReadSeeker, pver uint32) (int64, error) {
|
||||
_, err := r.Seek(timestampOffset, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
buf := make([]byte, timestampLength)
|
||||
if _, err = io.ReadFull(r, buf); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
time := binary.LittleEndian.Uint32(buf)
|
||||
|
||||
_, err = r.Seek(headerFixedLength-timestampOffset-timestampLength, io.SeekCurrent)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
size, err := wire.ReadVarInt(r, pver)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
_, err = r.Seek(int64(size), io.SeekCurrent)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return nil
|
||||
return int64(time), nil
|
||||
}
|
||||
|
|
|
@ -12,72 +12,86 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
var testParseBlockTxs = map[int][]string{
|
||||
104000: []string{
|
||||
"331d4ef64118e9e5be75f0f51f1a4c5057550c3320e22ff7206f3e1101f113d0",
|
||||
"1f4817d8e91c21d8c8d163dabccdd1875f760fd2dc34a1c2b7b8fa204e103597",
|
||||
"268163b1a1092aa0996d118a6027b0b6f1076627e02addc4e66ae30239936818",
|
||||
"27277a1049fafa2a46368ad02961d37da633416b014bcd42a1f1391753cbf559",
|
||||
"276d2d331585d0968762d9788f71ae71524ccba3494f638b2328ac51e52edd3d",
|
||||
"28d9f85089834c20507cc5e4ec54aaaf5b79feab80cad24a48b8296b6d327a43",
|
||||
"2a2d66d3d9e8b6f154958a388377f681abd82ce98785a5bbf2e27d0ca454da3f",
|
||||
"39c9e995a12b638b541d21ed9f9e555716709add6a97c8ba63fe26e4d26bdc3f",
|
||||
"4a768efc6cc0716932800115989376c2ce3e5e17668b08bd2f43031105a6ac6e",
|
||||
"4bc41fa0188d988d853a176eb847a541c5adf35348eab21d128904aab36e8574",
|
||||
"4cad1bd38cc7be880f3a968af6f13a3aeb5dbdb51a774b7d1ae3d6d6bfd114e4",
|
||||
"6bc558801583bfdb656106889c4b8bd783168133784666338c57e5b2a675a922",
|
||||
"75eb5c1aa89b18ce71f363f95147942b46645ca9b1e472784fcb8c9a096f4f5c",
|
||||
"91755365cff22c4eed09a57a8fb7b2faa5c1caa5fa750c89f830a2bb56fa4c68",
|
||||
"9417d34969891f2a0b9aa3a1226505edf3b429fa1acd21a140d358a217d11d55",
|
||||
"950fbb5f413af9f7c6e5dabfacf68b715c9851b5cf6ab6806960f7cc7cad2f9d",
|
||||
"99b134dae55ddfab1f5d5243c2e60030a9ed969ba5915f98840b877f8af28ce0",
|
||||
"9d7b15eaaccce66e25efe7e2979454ae4968b61281d50f32be9872d2d256c244",
|
||||
"a54df5296f1d1a6101cee0869ffda03502e417fc72475b7902a6dfd5b9329399",
|
||||
"adba400f14b476f0c2b11340ee1fa440208b49fd99c1072136198b5c43664826",
|
||||
"bd7d8fee068bd45b06b4c17ccdf577b4bc2b21c9c4e0cee8453409b0e63b4f5d",
|
||||
"beabd2d68b66a9b47b6aff23b569c1b59e429074f06bdd4993e9d3c2cb69c992",
|
||||
"bfa81174d549eb7ed15be3f965686aff3084f22523c52fbed03c3fc3e18b7cda",
|
||||
"e42472099cb039b1c2248b611ce212580338550977e02bd77accdf29bfd86e96",
|
||||
"f056e02b12d99377f724a4987cde68ecf6f234fd7e2bdf4324172c03d015ba25",
|
||||
"f1815cfb1ef4cfe13ff5ec2c15b5bc55fde043db85daca1bb34cc1b491193308",
|
||||
"f225abce64f75383686fa08abe47242e59e97809a31c8fd7a88acce1e6cbcd27",
|
||||
"f93f1b125bfa2da5ccaaf30ff96635b905b657d48a5962c24be93884a82ef354",
|
||||
"fef75d015f2e9926d1d4bf82e567b91e51af66a6e636d03a072f203dd3062ae5",
|
||||
"051b60a6accead85da54b8d18f4b2360ea946da948d3a27836306d2927fed13e",
|
||||
"28e47b050ec4064cdbd3364f3be9445d52635e9730691ef71ed1db0f0147afd6",
|
||||
"446ebde2457102bcbc2c86cac6ff582c595b00346fd0b27ea5a645240020504b",
|
||||
"46c8fafe2b7bb1646aeefa229b18fa7ffe20fee0a30c4a9ef3e63c36c808f6f7",
|
||||
"550d96cf82fbe91dcc9b96e7aa404f392ee47400c22a98a7631d29eee43fceaa",
|
||||
"59b6b78a72cc33cd29741894b3007b1330fc7f7945bdc0a7a4044ed1dd289c19",
|
||||
"5a3aa07474338cf193c1d7aacdc37f3311c971857ba8cfd308e8fabf5e473882",
|
||||
"82e014b1a9c6cb7729274653ce748c66953de6abb3d1411471515b41b727cf75",
|
||||
"8d70af4f135696da396c9aa9f936b54195bfbe6ff0e08b3b210ca0b52bc167d2",
|
||||
"9949c2f2f3b96a557ef6e14004cbd239a0744c056faca34bdff01e125b4380e8",
|
||||
"d09a8c83123ce1cb6ff837e7670aab5f50c5155d9706dd26f7e0761fd20c5536",
|
||||
"f601482efc5b2dd3d0031e318e840cd06f7cab0ffff8cc37a5bf471b515ddfb7",
|
||||
"f88d3c0ebe8b294f11e70d2ae6d2f0048437bfb20dae7e69d545a4c72d3432dd",
|
||||
"2b9e574b90556250b20a79d9c94ceaff3dfd062291c34c3fa79c7ca8d85a3500",
|
||||
"b9484ef8e38ceafe8d2b09ecf59562d262b15d185844b2d00db362718d52b2c2",
|
||||
"07a4af0a81b55313a1c16da7d808829d689436fd078fa9559b6d1603dd72474e",
|
||||
"3393bdcc3a7232b37d0fb6b56d603a2b9b0419e461bf989f1c375859a5d0156a",
|
||||
"33ad36d79d63b575c7532c516f16b19541f5c637caf7073beb7ddf604c3f39cc",
|
||||
type testBlock struct {
|
||||
size int
|
||||
time int64
|
||||
txs []string
|
||||
}
|
||||
|
||||
var testParseBlockTxs = map[int]testBlock{
|
||||
104000: testBlock{
|
||||
size: 15776,
|
||||
time: 1295705889,
|
||||
txs: []string{
|
||||
"331d4ef64118e9e5be75f0f51f1a4c5057550c3320e22ff7206f3e1101f113d0",
|
||||
"1f4817d8e91c21d8c8d163dabccdd1875f760fd2dc34a1c2b7b8fa204e103597",
|
||||
"268163b1a1092aa0996d118a6027b0b6f1076627e02addc4e66ae30239936818",
|
||||
"27277a1049fafa2a46368ad02961d37da633416b014bcd42a1f1391753cbf559",
|
||||
"276d2d331585d0968762d9788f71ae71524ccba3494f638b2328ac51e52edd3d",
|
||||
"28d9f85089834c20507cc5e4ec54aaaf5b79feab80cad24a48b8296b6d327a43",
|
||||
"2a2d66d3d9e8b6f154958a388377f681abd82ce98785a5bbf2e27d0ca454da3f",
|
||||
"39c9e995a12b638b541d21ed9f9e555716709add6a97c8ba63fe26e4d26bdc3f",
|
||||
"4a768efc6cc0716932800115989376c2ce3e5e17668b08bd2f43031105a6ac6e",
|
||||
"4bc41fa0188d988d853a176eb847a541c5adf35348eab21d128904aab36e8574",
|
||||
"4cad1bd38cc7be880f3a968af6f13a3aeb5dbdb51a774b7d1ae3d6d6bfd114e4",
|
||||
"6bc558801583bfdb656106889c4b8bd783168133784666338c57e5b2a675a922",
|
||||
"75eb5c1aa89b18ce71f363f95147942b46645ca9b1e472784fcb8c9a096f4f5c",
|
||||
"91755365cff22c4eed09a57a8fb7b2faa5c1caa5fa750c89f830a2bb56fa4c68",
|
||||
"9417d34969891f2a0b9aa3a1226505edf3b429fa1acd21a140d358a217d11d55",
|
||||
"950fbb5f413af9f7c6e5dabfacf68b715c9851b5cf6ab6806960f7cc7cad2f9d",
|
||||
"99b134dae55ddfab1f5d5243c2e60030a9ed969ba5915f98840b877f8af28ce0",
|
||||
"9d7b15eaaccce66e25efe7e2979454ae4968b61281d50f32be9872d2d256c244",
|
||||
"a54df5296f1d1a6101cee0869ffda03502e417fc72475b7902a6dfd5b9329399",
|
||||
"adba400f14b476f0c2b11340ee1fa440208b49fd99c1072136198b5c43664826",
|
||||
"bd7d8fee068bd45b06b4c17ccdf577b4bc2b21c9c4e0cee8453409b0e63b4f5d",
|
||||
"beabd2d68b66a9b47b6aff23b569c1b59e429074f06bdd4993e9d3c2cb69c992",
|
||||
"bfa81174d549eb7ed15be3f965686aff3084f22523c52fbed03c3fc3e18b7cda",
|
||||
"e42472099cb039b1c2248b611ce212580338550977e02bd77accdf29bfd86e96",
|
||||
"f056e02b12d99377f724a4987cde68ecf6f234fd7e2bdf4324172c03d015ba25",
|
||||
"f1815cfb1ef4cfe13ff5ec2c15b5bc55fde043db85daca1bb34cc1b491193308",
|
||||
"f225abce64f75383686fa08abe47242e59e97809a31c8fd7a88acce1e6cbcd27",
|
||||
"f93f1b125bfa2da5ccaaf30ff96635b905b657d48a5962c24be93884a82ef354",
|
||||
"fef75d015f2e9926d1d4bf82e567b91e51af66a6e636d03a072f203dd3062ae5",
|
||||
"051b60a6accead85da54b8d18f4b2360ea946da948d3a27836306d2927fed13e",
|
||||
"28e47b050ec4064cdbd3364f3be9445d52635e9730691ef71ed1db0f0147afd6",
|
||||
"446ebde2457102bcbc2c86cac6ff582c595b00346fd0b27ea5a645240020504b",
|
||||
"46c8fafe2b7bb1646aeefa229b18fa7ffe20fee0a30c4a9ef3e63c36c808f6f7",
|
||||
"550d96cf82fbe91dcc9b96e7aa404f392ee47400c22a98a7631d29eee43fceaa",
|
||||
"59b6b78a72cc33cd29741894b3007b1330fc7f7945bdc0a7a4044ed1dd289c19",
|
||||
"5a3aa07474338cf193c1d7aacdc37f3311c971857ba8cfd308e8fabf5e473882",
|
||||
"82e014b1a9c6cb7729274653ce748c66953de6abb3d1411471515b41b727cf75",
|
||||
"8d70af4f135696da396c9aa9f936b54195bfbe6ff0e08b3b210ca0b52bc167d2",
|
||||
"9949c2f2f3b96a557ef6e14004cbd239a0744c056faca34bdff01e125b4380e8",
|
||||
"d09a8c83123ce1cb6ff837e7670aab5f50c5155d9706dd26f7e0761fd20c5536",
|
||||
"f601482efc5b2dd3d0031e318e840cd06f7cab0ffff8cc37a5bf471b515ddfb7",
|
||||
"f88d3c0ebe8b294f11e70d2ae6d2f0048437bfb20dae7e69d545a4c72d3432dd",
|
||||
"2b9e574b90556250b20a79d9c94ceaff3dfd062291c34c3fa79c7ca8d85a3500",
|
||||
"b9484ef8e38ceafe8d2b09ecf59562d262b15d185844b2d00db362718d52b2c2",
|
||||
"07a4af0a81b55313a1c16da7d808829d689436fd078fa9559b6d1603dd72474e",
|
||||
"3393bdcc3a7232b37d0fb6b56d603a2b9b0419e461bf989f1c375859a5d0156a",
|
||||
"33ad36d79d63b575c7532c516f16b19541f5c637caf7073beb7ddf604c3f39cc",
|
||||
},
|
||||
},
|
||||
532144: []string{
|
||||
"574348e23301cc89535408b6927bf75f2ac88fadf8fdfb181c17941a5de02fe0",
|
||||
"9f048446401e7fac84963964df045b1f3992eda330a87b02871e422ff0a3fd28",
|
||||
"9516c320745a227edb07c98087b1febea01c3ba85122a34387896fc82ba965e4",
|
||||
"9d37e1ab5a28c49ce5e7ece4a2b9df740fb4c3a84bdec93b3023148cf20f0de7",
|
||||
"a3cd0481b983ba402fed8805ef9daf5063d6d4e5085b82eca5b4781c9e362d6a",
|
||||
"7f2c2567e8de0321744817cfeb751922d7e8d82ef71aa01164c84fb74a463a53",
|
||||
"cd064315e3f5d07920b3d159160c218d0bb5b7b4be606265767b208ae7e256eb",
|
||||
"a9523400f341aa425b0fcc00656ec1fa5421bf3545433bff98a8614fc9a99d1f",
|
||||
"ec766daacbb05a8f48a3205e5c6494a7c817bd35eefff9aaf55e0dd47fe6e8fc",
|
||||
"0837a4116872abf52caa52d1ff7608674ba5b09a239a1f43f3a25ba4052e4c77",
|
||||
"a3e23a0344fe6ba7083fc6afb940517cdb666dce00389cbd8598bd599199cdda",
|
||||
"048d951cef84d35d68f0bc3b575662caf23fee692e8035bd5efe38ab67e0d6c2",
|
||||
"11307491b24d42ddd7ea27fc795d444b65c3936be31b904a97da68fabc85e5b8",
|
||||
"84ad99dc0884e03fc71f163eebf515a1eb79d00b1aad7a1126b22747960a8275",
|
||||
"728c8d0858e506d4a1a9b506f7b974b335e6c54047af9f40d4cb1a0561f783e1",
|
||||
532144: testBlock{
|
||||
size: 12198,
|
||||
time: 1528372417,
|
||||
txs: []string{
|
||||
"574348e23301cc89535408b6927bf75f2ac88fadf8fdfb181c17941a5de02fe0",
|
||||
"9f048446401e7fac84963964df045b1f3992eda330a87b02871e422ff0a3fd28",
|
||||
"9516c320745a227edb07c98087b1febea01c3ba85122a34387896fc82ba965e4",
|
||||
"9d37e1ab5a28c49ce5e7ece4a2b9df740fb4c3a84bdec93b3023148cf20f0de7",
|
||||
"a3cd0481b983ba402fed8805ef9daf5063d6d4e5085b82eca5b4781c9e362d6a",
|
||||
"7f2c2567e8de0321744817cfeb751922d7e8d82ef71aa01164c84fb74a463a53",
|
||||
"cd064315e3f5d07920b3d159160c218d0bb5b7b4be606265767b208ae7e256eb",
|
||||
"a9523400f341aa425b0fcc00656ec1fa5421bf3545433bff98a8614fc9a99d1f",
|
||||
"ec766daacbb05a8f48a3205e5c6494a7c817bd35eefff9aaf55e0dd47fe6e8fc",
|
||||
"0837a4116872abf52caa52d1ff7608674ba5b09a239a1f43f3a25ba4052e4c77",
|
||||
"a3e23a0344fe6ba7083fc6afb940517cdb666dce00389cbd8598bd599199cdda",
|
||||
"048d951cef84d35d68f0bc3b575662caf23fee692e8035bd5efe38ab67e0d6c2",
|
||||
"11307491b24d42ddd7ea27fc795d444b65c3936be31b904a97da68fabc85e5b8",
|
||||
"84ad99dc0884e03fc71f163eebf515a1eb79d00b1aad7a1126b22747960a8275",
|
||||
"728c8d0858e506d4a1a9b506f7b974b335e6c54047af9f40d4cb1a0561f783e1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -104,7 +118,7 @@ func helperLoadBlock(t *testing.T, height int) []byte {
|
|||
func TestParseBlock(t *testing.T) {
|
||||
p := NewBGoldParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for height, txs := range testParseBlockTxs {
|
||||
for height, tb := range testParseBlockTxs {
|
||||
b := helperLoadBlock(t, height)
|
||||
|
||||
blk, err := p.ParseBlock(b)
|
||||
|
@ -112,11 +126,19 @@ func TestParseBlock(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(blk.Txs) != len(txs) {
|
||||
t.Errorf("ParseBlock() number of transactions: got %d, want %d", len(blk.Txs), len(txs))
|
||||
if blk.Size != tb.size {
|
||||
t.Errorf("ParseBlock() block size: got %d, want %d", blk.Size, tb.size)
|
||||
}
|
||||
|
||||
for ti, tx := range txs {
|
||||
if blk.Time != tb.time {
|
||||
t.Errorf("ParseBlock() block time: got %d, want %d", blk.Time, tb.time)
|
||||
}
|
||||
|
||||
if len(blk.Txs) != len(tb.txs) {
|
||||
t.Errorf("ParseBlock() number of transactions: got %d, want %d", len(blk.Txs), len(tb.txs))
|
||||
}
|
||||
|
||||
for ti, tx := range tb.txs {
|
||||
if blk.Txs[ti].Txid != tx {
|
||||
t.Errorf("ParseBlock() transaction %d: got %s, want %s", ti, blk.Txs[ti].Txid, tx)
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ var (
|
|||
RegtestParams chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
func initParams() {
|
||||
MainNetParams = chaincfg.MainNetParams
|
||||
MainNetParams.Net = MainnetMagic
|
||||
|
||||
|
@ -67,6 +67,9 @@ func NewDashParser(params *chaincfg.Params, c *btc.Configuration) *DashParser {
|
|||
// the regression test Dash network, the test Dash network and
|
||||
// the simulation test Dash network, in this order
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
if MainNetParams.Name == "" {
|
||||
initParams()
|
||||
}
|
||||
switch chain {
|
||||
case "test":
|
||||
return &TestNetParams
|
||||
|
|
|
@ -18,7 +18,7 @@ var (
|
|||
MainNetParams chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
func initParams() {
|
||||
MainNetParams = chaincfg.MainNetParams
|
||||
MainNetParams.Net = MainnetMagic
|
||||
MainNetParams.PubKeyHashAddrID = []byte{30}
|
||||
|
@ -43,6 +43,9 @@ func NewDogecoinParser(params *chaincfg.Params, c *btc.Configuration) *DogecoinP
|
|||
// GetChainParams contains network parameters for the main Dogecoin network,
|
||||
// and the test Dogecoin network
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
if MainNetParams.Name == "" {
|
||||
initParams()
|
||||
}
|
||||
switch chain {
|
||||
default:
|
||||
return &MainNetParams
|
||||
|
@ -75,5 +78,11 @@ func (p *DogecoinParser) ParseBlock(b []byte) (*bchain.Block, error) {
|
|||
txs[ti] = p.TxFromMsgTx(t, false)
|
||||
}
|
||||
|
||||
return &bchain.Block{Txs: txs}, nil
|
||||
return &bchain.Block{
|
||||
BlockHeader: bchain.BlockHeader{
|
||||
Size: len(b),
|
||||
Time: h.Timestamp.Unix(),
|
||||
},
|
||||
Txs: txs,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -259,89 +259,111 @@ func Test_UnpackTx(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
var testParseBlockTxs = map[int][]string{
|
||||
type testBlock struct {
|
||||
size int
|
||||
time int64
|
||||
txs []string
|
||||
}
|
||||
|
||||
var testParseBlockTxs = map[int]testBlock{
|
||||
// block without auxpow
|
||||
12345: []string{
|
||||
"9d1662dcc1443af9999c4fd1d6921b91027b5e2d0d3ebfaa41d84163cb99cad5",
|
||||
"8284292cedeb0c9c509f9baa235802d52a546e1e9990040d35d018b97ad11cfa",
|
||||
"3299d93aae5c3d37c795c07150ceaf008aefa5aad3205ea2519f94a35adbbe10",
|
||||
"3f03016f32b63db48fdc0b17443c2d917ba5e307dcc2fc803feeb21c7219ee1b",
|
||||
"a889449e9bc618c131c01f564cd309d2217ba1c5731480314795e44f1e02609b",
|
||||
"29f79d91c10bc311ff5b69fe7ba57101969f68b6391cf0ca67d5f37ca1f0601b",
|
||||
"b794ebc7c0176c35b125cd8b84a980257cf3dd9cefe2ed47da4ed1d73ee568f3",
|
||||
"0ec479ba3c954dd422d75c4c5488a6edc3c588deb10ebdbfa8bd8edb7afcfea0",
|
||||
"f357b6e667dfa456e7988bfa474377df25d0e0bfe07e5f97fc97ea3a0155f031",
|
||||
"4ff189766f0455721a93d6be27a91eafa750383c800cb053fad2f86c434122d2",
|
||||
"446d164e2ec4c9f2ac6c499c110735606d949a3625fb849274ac627c033eddbc",
|
||||
"c489edebd8a2e17fd08f2801f528b95663aaafe15c897d56686423dd430e2d1f",
|
||||
"3f42a7f1a356897da324d41eed94169c79438212bb9874eea58e9cbaf07481df",
|
||||
"62c88fdd0fb111676844fcbaebc9e2211a0c990aa7e7529539cb25947a307a1b",
|
||||
"522c47e315bc1949826339c535d419eb206aec4a332f91dfbd25c206f3c9527b",
|
||||
"18ea78346e7e34cbdf2d2b6ba1630f8b15f9ef9a940114a3e6ee92d26f96691e",
|
||||
"43dc0fbd1b9b87bcfc9a51c89457a7b3274855c01d429193aff1181791225f3c",
|
||||
"d78cdfaadbe5b6b591529cb5c6869866a4cabe46ef82aa835fd2432056b4a383",
|
||||
"d181759c7a3900ccaf4958f1f25a44949163ceefc306006502efc7a1de6f579e",
|
||||
"8610b9230188854c7871258163cd1c2db353443d631c5512bff17224a24e95bf",
|
||||
"e82f40a6bea32122f1d568d427c92708dcb684bdb3035ff3905617230e5ae5b8",
|
||||
"c50ae6c127f8c346c60e7438fbd10c44c3629f3fe426646db77a2250fb2939f9",
|
||||
"585202c03894ecaf25188ba4e5447dadd413f2010c2dc2a65c37598dbc6ad907",
|
||||
"8bd766fde8c65e2f724dad581944dde4e23e4dbb4f7f7faf55bc348923f4d5ee",
|
||||
"2d2fa25691088181569e508dd8f683b21f2b80ceefb5ccbd6714ebe2a697139f",
|
||||
"5954622ffc602bec177d61da6c26a68990c42c1886627b218c3ab0e9e3491f4a",
|
||||
"01b634bc53334df1cd9f04522729a34d811c418c2535144c3ed156cbc319e43e",
|
||||
"c429a6c8265482b2d824af03afe1c090b233a856f243791485cb4269f2729649",
|
||||
"dbe79231b916b6fb47a91ef874f35150270eb571af60c2d640ded92b41749940",
|
||||
"1c396493a8dfd59557052b6e8643123405894b64f48b2eb6eb7a003159034077",
|
||||
"2e2816ffb7bf1378f11acf5ba30d498efc8fd219d4b67a725e8254ce61b1b7ee",
|
||||
12345: testBlock{
|
||||
size: 8582,
|
||||
time: 1387104223,
|
||||
txs: []string{
|
||||
"9d1662dcc1443af9999c4fd1d6921b91027b5e2d0d3ebfaa41d84163cb99cad5",
|
||||
"8284292cedeb0c9c509f9baa235802d52a546e1e9990040d35d018b97ad11cfa",
|
||||
"3299d93aae5c3d37c795c07150ceaf008aefa5aad3205ea2519f94a35adbbe10",
|
||||
"3f03016f32b63db48fdc0b17443c2d917ba5e307dcc2fc803feeb21c7219ee1b",
|
||||
"a889449e9bc618c131c01f564cd309d2217ba1c5731480314795e44f1e02609b",
|
||||
"29f79d91c10bc311ff5b69fe7ba57101969f68b6391cf0ca67d5f37ca1f0601b",
|
||||
"b794ebc7c0176c35b125cd8b84a980257cf3dd9cefe2ed47da4ed1d73ee568f3",
|
||||
"0ec479ba3c954dd422d75c4c5488a6edc3c588deb10ebdbfa8bd8edb7afcfea0",
|
||||
"f357b6e667dfa456e7988bfa474377df25d0e0bfe07e5f97fc97ea3a0155f031",
|
||||
"4ff189766f0455721a93d6be27a91eafa750383c800cb053fad2f86c434122d2",
|
||||
"446d164e2ec4c9f2ac6c499c110735606d949a3625fb849274ac627c033eddbc",
|
||||
"c489edebd8a2e17fd08f2801f528b95663aaafe15c897d56686423dd430e2d1f",
|
||||
"3f42a7f1a356897da324d41eed94169c79438212bb9874eea58e9cbaf07481df",
|
||||
"62c88fdd0fb111676844fcbaebc9e2211a0c990aa7e7529539cb25947a307a1b",
|
||||
"522c47e315bc1949826339c535d419eb206aec4a332f91dfbd25c206f3c9527b",
|
||||
"18ea78346e7e34cbdf2d2b6ba1630f8b15f9ef9a940114a3e6ee92d26f96691e",
|
||||
"43dc0fbd1b9b87bcfc9a51c89457a7b3274855c01d429193aff1181791225f3c",
|
||||
"d78cdfaadbe5b6b591529cb5c6869866a4cabe46ef82aa835fd2432056b4a383",
|
||||
"d181759c7a3900ccaf4958f1f25a44949163ceefc306006502efc7a1de6f579e",
|
||||
"8610b9230188854c7871258163cd1c2db353443d631c5512bff17224a24e95bf",
|
||||
"e82f40a6bea32122f1d568d427c92708dcb684bdb3035ff3905617230e5ae5b8",
|
||||
"c50ae6c127f8c346c60e7438fbd10c44c3629f3fe426646db77a2250fb2939f9",
|
||||
"585202c03894ecaf25188ba4e5447dadd413f2010c2dc2a65c37598dbc6ad907",
|
||||
"8bd766fde8c65e2f724dad581944dde4e23e4dbb4f7f7faf55bc348923f4d5ee",
|
||||
"2d2fa25691088181569e508dd8f683b21f2b80ceefb5ccbd6714ebe2a697139f",
|
||||
"5954622ffc602bec177d61da6c26a68990c42c1886627b218c3ab0e9e3491f4a",
|
||||
"01b634bc53334df1cd9f04522729a34d811c418c2535144c3ed156cbc319e43e",
|
||||
"c429a6c8265482b2d824af03afe1c090b233a856f243791485cb4269f2729649",
|
||||
"dbe79231b916b6fb47a91ef874f35150270eb571af60c2d640ded92b41749940",
|
||||
"1c396493a8dfd59557052b6e8643123405894b64f48b2eb6eb7a003159034077",
|
||||
"2e2816ffb7bf1378f11acf5ba30d498efc8fd219d4b67a725e8254ce61b1b7ee",
|
||||
},
|
||||
},
|
||||
// 1st block with auxpow
|
||||
371337: []string{
|
||||
"4547b14bc16db4184fa9f141d645627430dd3dfa662d0e6f418fba497091da75",
|
||||
"a965dba2ed06827ed9a24f0568ec05b73c431bc7f0fb6913b144e62db7faa519",
|
||||
"5e3ab18cb7ba3abc44e62fb3a43d4c8168d00cf0a2e0f8dbeb2636bb9a212d12",
|
||||
"f022935ac7c4c734bd2c9c6a780f8e7280352de8bd358d760d0645b7fe734a93",
|
||||
"ec063cc8025f9f30a6ed40fc8b1fe63b0cbd2ea2c62664eb26b365e6243828ca",
|
||||
"02c16e3389320da3e77686d39773dda65a1ecdf98a2ef9cfb938c9f4b58f7a40",
|
||||
371337: testBlock{
|
||||
size: 1704,
|
||||
time: 1410464577,
|
||||
txs: []string{
|
||||
"4547b14bc16db4184fa9f141d645627430dd3dfa662d0e6f418fba497091da75",
|
||||
"a965dba2ed06827ed9a24f0568ec05b73c431bc7f0fb6913b144e62db7faa519",
|
||||
"5e3ab18cb7ba3abc44e62fb3a43d4c8168d00cf0a2e0f8dbeb2636bb9a212d12",
|
||||
"f022935ac7c4c734bd2c9c6a780f8e7280352de8bd358d760d0645b7fe734a93",
|
||||
"ec063cc8025f9f30a6ed40fc8b1fe63b0cbd2ea2c62664eb26b365e6243828ca",
|
||||
"02c16e3389320da3e77686d39773dda65a1ecdf98a2ef9cfb938c9f4b58f7a40",
|
||||
},
|
||||
},
|
||||
// block with auxpow
|
||||
567890: []string{
|
||||
"db20feea53be1f60848a66604d5bca63df62de4f6c66220f9c84436d788625a8",
|
||||
"cf7e9e27c0f56f0b100eaf5c776ce106025e3412bd5927c6e1ce575500e24eaa",
|
||||
"af84e010c1cf0bd927740d08e5e8163db45397b70f00df07aea5339c14d5f3aa",
|
||||
"7362e25e8131255d101e5d874e6b6bb2faa7a821356cb041f1843d0901dffdbd",
|
||||
"3b875344302e8893f6d5c9e7269d806ed27217ec67944940ae9048fc619bdae9",
|
||||
"e3b95e269b7c251d87e8e241ea2a08a66ec14d12a1012762be368b3db55471e3",
|
||||
"6ba3f95a37bcab5d0cb5b8bd2fe48040db0a6ae390f320d6dcc8162cc096ff8f",
|
||||
"3211ccc66d05b10959fa6e56d1955c12368ea52b40303558b254d7dc22570382",
|
||||
"54c1b279e78b924dfa15857c80131c3ddf835ab02f513dc03aa514f87b680493",
|
||||
567890: testBlock{
|
||||
size: 3833,
|
||||
time: 1422855443,
|
||||
txs: []string{
|
||||
"db20feea53be1f60848a66604d5bca63df62de4f6c66220f9c84436d788625a8",
|
||||
"cf7e9e27c0f56f0b100eaf5c776ce106025e3412bd5927c6e1ce575500e24eaa",
|
||||
"af84e010c1cf0bd927740d08e5e8163db45397b70f00df07aea5339c14d5f3aa",
|
||||
"7362e25e8131255d101e5d874e6b6bb2faa7a821356cb041f1843d0901dffdbd",
|
||||
"3b875344302e8893f6d5c9e7269d806ed27217ec67944940ae9048fc619bdae9",
|
||||
"e3b95e269b7c251d87e8e241ea2a08a66ec14d12a1012762be368b3db55471e3",
|
||||
"6ba3f95a37bcab5d0cb5b8bd2fe48040db0a6ae390f320d6dcc8162cc096ff8f",
|
||||
"3211ccc66d05b10959fa6e56d1955c12368ea52b40303558b254d7dc22570382",
|
||||
"54c1b279e78b924dfa15857c80131c3ddf835ab02f513dc03aa514f87b680493",
|
||||
},
|
||||
},
|
||||
// recent block
|
||||
2264125: []string{
|
||||
"76f0126562c99e020b5fba41b68dd8141a4f21eef62012b76a1e0635092045e9",
|
||||
"7bb6688bec16de94014574e3e1d3f6f5fb956530d6b179b28db367f1fd8ae099",
|
||||
"d7e2ee30c3d179ac896651fc09c1396333f41d952d008af8d5d6665cbea377bf",
|
||||
"8e4783878df782003c43d014fcbb9c57d2034dfd1d9fcd7319bb1a9f501dbbb7",
|
||||
"8d2a4ae226b6f23eea545957be5d71c68cd08674d96a3502d4ca21ffadacb5a9",
|
||||
"a0da2b49de881133655c54b1b5c23af443a71c2b937e2d9bbdf3f498247e6b7b",
|
||||
"c780a19b9cf46ed70b53c5d5722e8d33951211a4051cb165b25fb0c22a4ae1ff",
|
||||
"ce29c2644d642bb4fedd09d0840ed98c9945bf292967fede8fcc6b26054b4058",
|
||||
"a360b0566f68c329e2757918f67ee6421d3d76f70f1b452cdd32266805986119",
|
||||
"17e85bd33cc5fb5035e489c5188979f45e75e92d14221eca937e14f5f7d7b074",
|
||||
"3973eb930fd2d0726abbd81912eae645384268cd3500b9ec84d806fdd65a426a",
|
||||
"b91cc1c98e5c77e80eec9bf93e86af27f810b00dfbce3ee2646758797a28d5f2",
|
||||
"1a8c7bd3389dcbbc1133ee600898ed9e082f7a9c75f9eb52f33940ed7c2247ef",
|
||||
"9b1782449bbd3fc3014c363167777f7bdf41f5ef6db192fbda784b29603911b0",
|
||||
"afab4bcdc1a32891d638579c3029ae49ee72be3303425c6d62e1f8eaebe0ce18",
|
||||
"5f839f9cd5293c02ff4f7cf5589c53dec52adb42a077599dc7a2c5842a156ca9",
|
||||
"756d2dfd1d2872ba2531fae3b8984008506871bec41d19cb299f5e0f216cfb9b",
|
||||
"6aa82514ab7a9cc624fabf3d06ccbd46ecb4009b3c784768e6243d7840d4bf93",
|
||||
"d1430b3f7ecf147534796c39ba631ea22ac03530e25b9428367c0dc381b10863",
|
||||
"2aeb69b1eb9eef8039da6b97d7851e46f57325851e6998ef5a84fc9a826c2c74",
|
||||
"fc61d13eef806af8da693cfa621fe92110694f1514567b186a35c54e7ef4a188",
|
||||
"a02dd44e60ba62fa00c83a67116f8079bf71062939b207bee0808cb98b30cf22",
|
||||
"279f97cfc606fe62777b44614ff28675ce661687904e068e3ec79f619c4fdae7",
|
||||
"d515d271849717b091a9c46bf11c47efb9d975e72b668c137786a208cf0a9739",
|
||||
"a800da44e6eed944043561fe22ee0a6e11341e6bc1a8ec2789b83930cc9b170e",
|
||||
2264125: testBlock{
|
||||
size: 8531,
|
||||
time: 1529099968,
|
||||
txs: []string{
|
||||
"76f0126562c99e020b5fba41b68dd8141a4f21eef62012b76a1e0635092045e9",
|
||||
"7bb6688bec16de94014574e3e1d3f6f5fb956530d6b179b28db367f1fd8ae099",
|
||||
"d7e2ee30c3d179ac896651fc09c1396333f41d952d008af8d5d6665cbea377bf",
|
||||
"8e4783878df782003c43d014fcbb9c57d2034dfd1d9fcd7319bb1a9f501dbbb7",
|
||||
"8d2a4ae226b6f23eea545957be5d71c68cd08674d96a3502d4ca21ffadacb5a9",
|
||||
"a0da2b49de881133655c54b1b5c23af443a71c2b937e2d9bbdf3f498247e6b7b",
|
||||
"c780a19b9cf46ed70b53c5d5722e8d33951211a4051cb165b25fb0c22a4ae1ff",
|
||||
"ce29c2644d642bb4fedd09d0840ed98c9945bf292967fede8fcc6b26054b4058",
|
||||
"a360b0566f68c329e2757918f67ee6421d3d76f70f1b452cdd32266805986119",
|
||||
"17e85bd33cc5fb5035e489c5188979f45e75e92d14221eca937e14f5f7d7b074",
|
||||
"3973eb930fd2d0726abbd81912eae645384268cd3500b9ec84d806fdd65a426a",
|
||||
"b91cc1c98e5c77e80eec9bf93e86af27f810b00dfbce3ee2646758797a28d5f2",
|
||||
"1a8c7bd3389dcbbc1133ee600898ed9e082f7a9c75f9eb52f33940ed7c2247ef",
|
||||
"9b1782449bbd3fc3014c363167777f7bdf41f5ef6db192fbda784b29603911b0",
|
||||
"afab4bcdc1a32891d638579c3029ae49ee72be3303425c6d62e1f8eaebe0ce18",
|
||||
"5f839f9cd5293c02ff4f7cf5589c53dec52adb42a077599dc7a2c5842a156ca9",
|
||||
"756d2dfd1d2872ba2531fae3b8984008506871bec41d19cb299f5e0f216cfb9b",
|
||||
"6aa82514ab7a9cc624fabf3d06ccbd46ecb4009b3c784768e6243d7840d4bf93",
|
||||
"d1430b3f7ecf147534796c39ba631ea22ac03530e25b9428367c0dc381b10863",
|
||||
"2aeb69b1eb9eef8039da6b97d7851e46f57325851e6998ef5a84fc9a826c2c74",
|
||||
"fc61d13eef806af8da693cfa621fe92110694f1514567b186a35c54e7ef4a188",
|
||||
"a02dd44e60ba62fa00c83a67116f8079bf71062939b207bee0808cb98b30cf22",
|
||||
"279f97cfc606fe62777b44614ff28675ce661687904e068e3ec79f619c4fdae7",
|
||||
"d515d271849717b091a9c46bf11c47efb9d975e72b668c137786a208cf0a9739",
|
||||
"a800da44e6eed944043561fe22ee0a6e11341e6bc1a8ec2789b83930cc9b170e",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -368,7 +390,7 @@ func helperLoadBlock(t *testing.T, height int) []byte {
|
|||
func TestParseBlock(t *testing.T) {
|
||||
p := NewDogecoinParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for height, txs := range testParseBlockTxs {
|
||||
for height, tb := range testParseBlockTxs {
|
||||
b := helperLoadBlock(t, height)
|
||||
|
||||
blk, err := p.ParseBlock(b)
|
||||
|
@ -376,11 +398,19 @@ func TestParseBlock(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(blk.Txs) != len(txs) {
|
||||
t.Errorf("ParseBlock() number of transactions: got %d, want %d", len(blk.Txs), len(txs))
|
||||
if blk.Size != tb.size {
|
||||
t.Errorf("ParseBlock() block size: got %d, want %d", blk.Size, tb.size)
|
||||
}
|
||||
|
||||
for ti, tx := range txs {
|
||||
if blk.Time != tb.time {
|
||||
t.Errorf("ParseBlock() block time: got %d, want %d", blk.Time, tb.time)
|
||||
}
|
||||
|
||||
if len(blk.Txs) != len(tb.txs) {
|
||||
t.Errorf("ParseBlock() number of transactions: got %d, want %d", len(blk.Txs), len(tb.txs))
|
||||
}
|
||||
|
||||
for ti, tx := range tb.txs {
|
||||
if blk.Txs[ti].Txid != tx {
|
||||
t.Errorf("ParseBlock() transaction %d: got %s, want %s", ti, blk.Txs[ti].Txid, tx)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -41,9 +42,7 @@ type EthereumRPC struct {
|
|||
client *ethclient.Client
|
||||
rpc *rpc.Client
|
||||
timeout time.Duration
|
||||
rpcURL string
|
||||
Parser *EthereumParser
|
||||
CoinName string
|
||||
Testnet bool
|
||||
Network string
|
||||
Mempool *bchain.NonUTXOMempool
|
||||
|
@ -54,6 +53,7 @@ type EthereumRPC struct {
|
|||
chanNewTx chan ethcommon.Hash
|
||||
newTxSubscription *rpc.ClientSubscription
|
||||
ChainConfig *Configuration
|
||||
isETC bool
|
||||
}
|
||||
|
||||
// NewEthereumRPC returns new EthRPC instance.
|
||||
|
@ -80,6 +80,9 @@ func NewEthereumRPC(config json.RawMessage, pushHandler func(bchain.Notification
|
|||
s.Parser = NewEthereumParser()
|
||||
s.timeout = time.Duration(c.RPCTimeout) * time.Second
|
||||
|
||||
// detect ethereum classic
|
||||
s.isETC = s.ChainConfig.CoinName == "Ethereum Classic"
|
||||
|
||||
// new blocks notifications handling
|
||||
// the subscription is done in Initialize
|
||||
s.chanNewBlock = make(chan *ethtypes.Header)
|
||||
|
@ -143,21 +146,25 @@ func (b *EthereumRPC) Initialize() error {
|
|||
}
|
||||
glog.Info("rpc: block chain ", b.Network)
|
||||
|
||||
// subscriptions
|
||||
if err = b.subscribe(func() (*rpc.ClientSubscription, error) {
|
||||
// invalidate the previous subscription - it is either the first one or there was an error
|
||||
b.newBlockSubscription = nil
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
sub, err := b.rpc.EthSubscribe(ctx, b.chanNewBlock, "newHeads")
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "EthSubscribe newHeads")
|
||||
if b.isETC {
|
||||
glog.Info(b.ChainConfig.CoinName, " does not support subscription to newHeads")
|
||||
} else {
|
||||
// subscriptions
|
||||
if err = b.subscribe(func() (*rpc.ClientSubscription, error) {
|
||||
// invalidate the previous subscription - it is either the first one or there was an error
|
||||
b.newBlockSubscription = nil
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
sub, err := b.rpc.EthSubscribe(ctx, b.chanNewBlock, "newHeads")
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "EthSubscribe newHeads")
|
||||
}
|
||||
b.newBlockSubscription = sub
|
||||
glog.Info("Subscribed to newHeads")
|
||||
return sub, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
b.newBlockSubscription = sub
|
||||
glog.Info("Subscribed to newHeads")
|
||||
return sub, nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = b.subscribe(func() (*rpc.ClientSubscription, error) {
|
||||
// invalidate the previous subscription - it is either the first one or there was an error
|
||||
|
@ -252,16 +259,23 @@ func (b *EthereumRPC) GetSubversion() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
// GetBlockChainInfo returns the NetworkID of the ethereum network
|
||||
func (b *EthereumRPC) GetBlockChainInfo() (string, error) {
|
||||
// GetChainInfo returns information about the connected backend
|
||||
func (b *EthereumRPC) GetChainInfo() (*bchain.ChainInfo, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
|
||||
id, err := b.client.NetworkID(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
return id.String(), nil
|
||||
rv := &bchain.ChainInfo{}
|
||||
idi := int(id.Uint64())
|
||||
if idi == 1 {
|
||||
rv.Chain = "mainnet"
|
||||
} else {
|
||||
rv.Chain = "testnet " + strconv.Itoa(idi)
|
||||
}
|
||||
// TODO - return more information about the chain
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
func (b *EthereumRPC) getBestHeader() (*ethtypes.Header, error) {
|
||||
|
@ -410,6 +424,12 @@ func (b *EthereumRPC) GetBlock(hash string, height uint32) (*bchain.Block, error
|
|||
return &bbk, nil
|
||||
}
|
||||
|
||||
// GetBlockInfo returns extended header (more info than in bchain.BlockHeader) with a list of txids
|
||||
func (b *EthereumRPC) GetBlockInfo(hash string) (*bchain.BlockInfo, error) {
|
||||
// TODO - implement
|
||||
return nil, errors.New("Not implemented yet")
|
||||
}
|
||||
|
||||
// GetTransactionForMempool returns a transaction by the transaction ID.
|
||||
// It could be optimized for mempool, i.e. without block time and confirmations
|
||||
func (b *EthereumRPC) GetTransactionForMempool(txid string) (*bchain.Tx, error) {
|
||||
|
@ -427,7 +447,11 @@ func (b *EthereumRPC) GetTransaction(txid string) (*bchain.Tx, error) {
|
|||
} else if tx == nil {
|
||||
return nil, ethereum.NotFound
|
||||
} else if tx.R == "" {
|
||||
return nil, errors.Annotatef(fmt.Errorf("server returned transaction without signature"), "txid %v", txid)
|
||||
if !b.isETC {
|
||||
return nil, errors.Annotatef(fmt.Errorf("server returned transaction without signature"), "txid %v", txid)
|
||||
} else {
|
||||
glog.Warning("server returned transaction without signature, txid ", txid)
|
||||
}
|
||||
}
|
||||
var btx *bchain.Tx
|
||||
if tx.BlockNumber == "" {
|
||||
|
@ -503,12 +527,28 @@ func (b *EthereumRPC) EstimateSmartFee(blocks int, conservative bool) (big.Int,
|
|||
return r, nil
|
||||
}
|
||||
|
||||
// SendRawTransaction sends raw transaction
|
||||
func (b *EthereumRPC) SendRawTransaction(tx string) (string, error) {
|
||||
return "", errors.New("SendRawTransaction: not implemented")
|
||||
// SendRawTransaction sends raw transaction.
|
||||
func (b *EthereumRPC) SendRawTransaction(hex string) (string, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), b.timeout)
|
||||
defer cancel()
|
||||
var raw json.RawMessage
|
||||
err := b.rpc.CallContext(ctx, &raw, "eth_sendRawTransaction", hex)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if len(raw) == 0 {
|
||||
return "", errors.New("SendRawTransaction: failed")
|
||||
}
|
||||
var result string
|
||||
if err := json.Unmarshal(raw, &result); err != nil {
|
||||
return "", errors.Annotatef(err, "raw result %v", raw)
|
||||
}
|
||||
if result == "" {
|
||||
return "", errors.New("SendRawTransaction: failed, empty result")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (b *EthereumRPC) ResyncMempool(onNewTxAddr func(txid string, addr string)) (int, error) {
|
||||
func (b *EthereumRPC) ResyncMempool(onNewTxAddr bchain.OnNewTxAddrFunc) (int, error) {
|
||||
return b.Mempool.Resync(onNewTxAddr)
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ var (
|
|||
TestNetParams chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
func initParams() {
|
||||
MainNetParams = chaincfg.MainNetParams
|
||||
MainNetParams.Net = MainnetMagic
|
||||
MainNetParams.PubKeyHashAddrID = []byte{48}
|
||||
|
@ -53,6 +53,9 @@ func NewLitecoinParser(params *chaincfg.Params, c *btc.Configuration) *LitecoinP
|
|||
// GetChainParams contains network parameters for the main Litecoin network,
|
||||
// and the test Litecoin network
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
if MainNetParams.Name == "" {
|
||||
initParams()
|
||||
}
|
||||
switch chain {
|
||||
case "test":
|
||||
return &TestNetParams
|
||||
|
|
|
@ -26,7 +26,7 @@ var (
|
|||
MonaTestParams monacoinCfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
func initParams() {
|
||||
MainNetParams = chaincfg.MainNetParams
|
||||
MainNetParams.Net = MainnetMagic
|
||||
MainNetParams.PubKeyHashAddrID = []byte{50}
|
||||
|
@ -71,6 +71,9 @@ func NewMonacoinParser(params *chaincfg.Params, c *btc.Configuration) *MonacoinP
|
|||
// GetChainParams contains network parameters for the main Monacoin network,
|
||||
// and the test Monacoin network
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
if MainNetParams.Name == "" {
|
||||
initParams()
|
||||
}
|
||||
switch chain {
|
||||
case "test":
|
||||
return &TestNetParams
|
||||
|
|
|
@ -170,7 +170,7 @@ func Test_GetAddressesFromAddrDesc(t *testing.T) {
|
|||
{
|
||||
name: "OP_RETURN hex",
|
||||
args: args{script: "6a072020f1686f6a20"},
|
||||
want: []string{"OP_RETURN 07 2020f1686f6a20"},
|
||||
want: []string{"OP_RETURN 2020f1686f6a20"},
|
||||
want2: false,
|
||||
wantErr: false,
|
||||
},
|
||||
|
|
|
@ -18,7 +18,7 @@ var (
|
|||
MainNetParams chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
func initParams() {
|
||||
MainNetParams = chaincfg.MainNetParams
|
||||
MainNetParams.Net = MainnetMagic
|
||||
MainNetParams.PubKeyHashAddrID = []byte{52}
|
||||
|
@ -43,6 +43,9 @@ func NewNamecoinParser(params *chaincfg.Params, c *btc.Configuration) *NamecoinP
|
|||
// GetChainParams contains network parameters for the main Namecoin network,
|
||||
// and the test Namecoin network
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
if MainNetParams.Name == "" {
|
||||
initParams()
|
||||
}
|
||||
switch chain {
|
||||
default:
|
||||
return &MainNetParams
|
||||
|
@ -75,5 +78,11 @@ func (p *NamecoinParser) ParseBlock(b []byte) (*bchain.Block, error) {
|
|||
txs[ti] = p.TxFromMsgTx(t, false)
|
||||
}
|
||||
|
||||
return &bchain.Block{Txs: txs}, nil
|
||||
return &bchain.Block{
|
||||
BlockHeader: bchain.BlockHeader{
|
||||
Size: len(b),
|
||||
Time: h.Timestamp.Unix(),
|
||||
},
|
||||
Txs: txs,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -53,11 +53,21 @@ func Test_GetAddrDescFromAddress_Mainnet(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
var testParseBlockTxs = map[int][]string{
|
||||
40000: []string{
|
||||
"e193a821393b20b99f4a4e05a481368ef8a8cfd43d0c45bdad7f53bc9535e844",
|
||||
"ddcbf95797e81dd04127885bd001e96695b717e11c52721f6e8ee53f6dea8a6f",
|
||||
"31ff728f24200f59fa4958e6c26de03d172b320e6eef2b8abecf6f94d01dd4ae",
|
||||
type testBlock struct {
|
||||
size int
|
||||
time int64
|
||||
txs []string
|
||||
}
|
||||
|
||||
var testParseBlockTxs = map[int]testBlock{
|
||||
40000: testBlock{
|
||||
size: 1385,
|
||||
time: 1327728573,
|
||||
txs: []string{
|
||||
"e193a821393b20b99f4a4e05a481368ef8a8cfd43d0c45bdad7f53bc9535e844",
|
||||
"ddcbf95797e81dd04127885bd001e96695b717e11c52721f6e8ee53f6dea8a6f",
|
||||
"31ff728f24200f59fa4958e6c26de03d172b320e6eef2b8abecf6f94d01dd4ae",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -84,7 +94,7 @@ func helperLoadBlock(t *testing.T, height int) []byte {
|
|||
func TestParseBlock(t *testing.T) {
|
||||
p := NewNamecoinParser(GetChainParams("main"), &btc.Configuration{})
|
||||
|
||||
for height, txs := range testParseBlockTxs {
|
||||
for height, tb := range testParseBlockTxs {
|
||||
b := helperLoadBlock(t, height)
|
||||
|
||||
blk, err := p.ParseBlock(b)
|
||||
|
@ -92,11 +102,19 @@ func TestParseBlock(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(blk.Txs) != len(txs) {
|
||||
t.Errorf("ParseBlock() number of transactions: got %d, want %d", len(blk.Txs), len(txs))
|
||||
if blk.Size != tb.size {
|
||||
t.Errorf("ParseBlock() block size: got %d, want %d", blk.Size, tb.size)
|
||||
}
|
||||
|
||||
for ti, tx := range txs {
|
||||
if blk.Time != tb.time {
|
||||
t.Errorf("ParseBlock() block time: got %d, want %d", blk.Time, tb.time)
|
||||
}
|
||||
|
||||
if len(blk.Txs) != len(tb.txs) {
|
||||
t.Errorf("ParseBlock() number of transactions: got %d, want %d", len(blk.Txs), len(tb.txs))
|
||||
}
|
||||
|
||||
for ti, tx := range tb.txs {
|
||||
if blk.Txs[ti].Txid != tx {
|
||||
t.Errorf("ParseBlock() transaction %d: got %s, want %s", ti, blk.Txs[ti].Txid, tx)
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ func DecodeTransactions(r io.Reader, pver uint32, enc wire.MessageEncoding, blk
|
|||
if txCount > maxTxPerBlock {
|
||||
str := fmt.Sprintf("too many transactions to fit into a block "+
|
||||
"[count %d, max %d]", txCount, maxTxPerBlock)
|
||||
return &wire.MessageError{Func: "btg.decodeTransactions", Description: str}
|
||||
return &wire.MessageError{Func: "utils.decodeTransactions", Description: str}
|
||||
}
|
||||
|
||||
blk.Transactions = make([]*wire.MsgTx, 0, txCount)
|
||||
|
|
|
@ -18,7 +18,7 @@ var (
|
|||
TestNetParams chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
func initParams() {
|
||||
MainNetParams = chaincfg.MainNetParams
|
||||
MainNetParams.Net = MainnetMagic
|
||||
MainNetParams.PubKeyHashAddrID = []byte{71}
|
||||
|
@ -53,6 +53,9 @@ func NewVertcoinParser(params *chaincfg.Params, c *btc.Configuration) *VertcoinP
|
|||
// GetChainParams contains network parameters for the main Vertcoin network,
|
||||
// and the test Vertcoin network
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
if MainNetParams.Name == "" {
|
||||
initParams()
|
||||
}
|
||||
switch chain {
|
||||
case "test":
|
||||
return &TestNetParams
|
||||
|
|
|
@ -19,7 +19,7 @@ var (
|
|||
TestNetParams chaincfg.Params
|
||||
)
|
||||
|
||||
func init() {
|
||||
func initParams() {
|
||||
MainNetParams = chaincfg.MainNetParams
|
||||
MainNetParams.Net = MainnetMagic
|
||||
|
||||
|
@ -63,6 +63,9 @@ func NewZCashParser(params *chaincfg.Params, c *btc.Configuration) *ZCashParser
|
|||
// the regression test ZCash network, the test ZCash network and
|
||||
// the simulation test ZCash network, in this order
|
||||
func GetChainParams(chain string) *chaincfg.Params {
|
||||
if MainNetParams.Name == "" {
|
||||
initParams()
|
||||
}
|
||||
var params *chaincfg.Params
|
||||
switch chain {
|
||||
case "test":
|
||||
|
|
|
@ -52,7 +52,7 @@ func (m *NonUTXOMempool) updateMappings(newTxToInputOutput map[string][]addrInde
|
|||
// Resync gets mempool transactions and maps outputs to transactions.
|
||||
// Resync is not reentrant, it should be called from a single thread.
|
||||
// Read operations (GetTransactions) are safe.
|
||||
func (m *NonUTXOMempool) Resync(onNewTxAddr func(txid string, addr string)) (int, error) {
|
||||
func (m *NonUTXOMempool) Resync(onNewTxAddr OnNewTxAddrFunc) (int, error) {
|
||||
start := time.Now()
|
||||
glog.V(1).Info("Mempool: resync")
|
||||
txs, err := m.chain.GetMempool()
|
||||
|
@ -84,7 +84,7 @@ func (m *NonUTXOMempool) Resync(onNewTxAddr func(txid string, addr string)) (int
|
|||
io = append(io, addrIndex{string(addrDesc), int32(output.N)})
|
||||
}
|
||||
if onNewTxAddr != nil && len(output.ScriptPubKey.Addresses) == 1 {
|
||||
onNewTxAddr(tx.Txid, output.ScriptPubKey.Addresses[0])
|
||||
onNewTxAddr(tx.Txid, output.ScriptPubKey.Addresses[0], true)
|
||||
}
|
||||
}
|
||||
for _, input := range tx.Vin {
|
||||
|
@ -96,6 +96,9 @@ func (m *NonUTXOMempool) Resync(onNewTxAddr func(txid string, addr string)) (int
|
|||
continue
|
||||
}
|
||||
io = append(io, addrIndex{string(addrDesc), int32(^i)})
|
||||
if onNewTxAddr != nil {
|
||||
onNewTxAddr(tx.Txid, a, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ type UTXOMempool struct {
|
|||
addrDescToTx map[string][]outpoint
|
||||
chanTxid chan string
|
||||
chanAddrIndex chan txidio
|
||||
onNewTxAddr func(txid string, addr string)
|
||||
onNewTxAddr OnNewTxAddrFunc
|
||||
}
|
||||
|
||||
// NewUTXOMempool creates new mempool handler.
|
||||
|
@ -133,7 +133,7 @@ func (m *UTXOMempool) getTxAddrs(txid string, chanInput chan outpoint, chanResul
|
|||
io = append(io, addrIndex{string(addrDesc), int32(output.N)})
|
||||
}
|
||||
if m.onNewTxAddr != nil && len(output.ScriptPubKey.Addresses) == 1 {
|
||||
m.onNewTxAddr(tx.Txid, output.ScriptPubKey.Addresses[0])
|
||||
m.onNewTxAddr(tx.Txid, output.ScriptPubKey.Addresses[0], true)
|
||||
}
|
||||
}
|
||||
dispatched := 0
|
||||
|
@ -170,7 +170,7 @@ func (m *UTXOMempool) getTxAddrs(txid string, chanInput chan outpoint, chanResul
|
|||
// Resync gets mempool transactions and maps outputs to transactions.
|
||||
// Resync is not reentrant, it should be called from a single thread.
|
||||
// Read operations (GetTransactions) are safe.
|
||||
func (m *UTXOMempool) Resync(onNewTxAddr func(txid string, addr string)) (int, error) {
|
||||
func (m *UTXOMempool) Resync(onNewTxAddr OnNewTxAddrFunc) (int, error) {
|
||||
start := time.Now()
|
||||
glog.V(1).Info("mempool: resync")
|
||||
m.onNewTxAddr = onNewTxAddr
|
||||
|
|
|
@ -71,11 +71,7 @@ type Block struct {
|
|||
Txs []Tx `json:"tx"`
|
||||
}
|
||||
|
||||
type ThinBlock struct {
|
||||
BlockHeader
|
||||
Txids []string `json:"tx"`
|
||||
}
|
||||
|
||||
// BlockHeader contains limited data (as needed for indexing) from backend block header
|
||||
type BlockHeader struct {
|
||||
Hash string `json:"hash"`
|
||||
Prev string `json:"previousblockhash"`
|
||||
|
@ -86,6 +82,17 @@ type BlockHeader struct {
|
|||
Time int64 `json:"time,omitempty"`
|
||||
}
|
||||
|
||||
// BlockInfo contains extended block header data and a list of block txids
|
||||
type BlockInfo struct {
|
||||
BlockHeader
|
||||
Version json.Number `json:"version"`
|
||||
MerkleRoot string `json:"merkleroot"`
|
||||
Nonce json.Number `json:"nonce"`
|
||||
Bits string `json:"bits"`
|
||||
Difficulty json.Number `json:"difficulty"`
|
||||
Txids []string `json:"tx,omitempty"`
|
||||
}
|
||||
|
||||
type MempoolEntry struct {
|
||||
Size uint32 `json:"size"`
|
||||
FeeSat big.Int
|
||||
|
@ -103,6 +110,20 @@ type MempoolEntry struct {
|
|||
Depends []string `json:"depends"`
|
||||
}
|
||||
|
||||
type ChainInfo struct {
|
||||
Chain string `json:"chain"`
|
||||
Blocks int `json:"blocks"`
|
||||
Headers int `json:"headers"`
|
||||
Bestblockhash string `json:"bestblockhash"`
|
||||
Difficulty string `json:"difficulty"`
|
||||
SizeOnDisk int64 `json:"size_on_disk"`
|
||||
Version string `json:"version"`
|
||||
Subversion string `json:"subversion"`
|
||||
ProtocolVersion string `json:"protocolversion"`
|
||||
Timeoffset float64 `json:"timeoffset"`
|
||||
Warnings string `json:"warnings"`
|
||||
}
|
||||
|
||||
type RPCError struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
|
@ -119,6 +140,12 @@ func (ad AddressDescriptor) String() string {
|
|||
return "ad:" + hex.EncodeToString(ad)
|
||||
}
|
||||
|
||||
// OnNewBlockFunc is used to send notification about a new block
|
||||
type OnNewBlockFunc func(hash string, height uint32)
|
||||
|
||||
// OnNewTxAddrFunc is used to send notification about a new transaction/address
|
||||
type OnNewTxAddrFunc func(txid string, addr string, isOutput bool)
|
||||
|
||||
// BlockChain defines common interface to block chain daemon
|
||||
type BlockChain interface {
|
||||
// life-cycle methods
|
||||
|
@ -129,13 +156,14 @@ type BlockChain interface {
|
|||
GetNetworkName() string
|
||||
GetSubversion() string
|
||||
GetCoinName() string
|
||||
GetChainInfo() (*ChainInfo, error)
|
||||
// requests
|
||||
GetBlockChainInfo() (string, error)
|
||||
GetBestBlockHash() (string, error)
|
||||
GetBestBlockHeight() (uint32, error)
|
||||
GetBlockHash(height uint32) (string, error)
|
||||
GetBlockHeader(hash string) (*BlockHeader, error)
|
||||
GetBlock(hash string, height uint32) (*Block, error)
|
||||
GetBlockInfo(hash string) (*BlockInfo, error)
|
||||
GetMempool() ([]string, error)
|
||||
GetTransaction(txid string) (*Tx, error)
|
||||
GetTransactionForMempool(txid string) (*Tx, error)
|
||||
|
@ -143,7 +171,7 @@ type BlockChain interface {
|
|||
EstimateFee(blocks int) (big.Int, error)
|
||||
SendRawTransaction(tx string) (string, error)
|
||||
// mempool
|
||||
ResyncMempool(onNewTxAddr func(txid string, addr string)) (int, error)
|
||||
ResyncMempool(onNewTxAddr OnNewTxAddrFunc) (int, error)
|
||||
GetMempoolTransactions(address string) ([]string, error)
|
||||
GetMempoolTransactionsForAddrDesc(addrDesc AddressDescriptor) ([]string, error)
|
||||
GetMempoolEntry(txid string) (*MempoolEntry, error)
|
||||
|
|
107
blockbook.go
107
blockbook.go
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"blockbook/api"
|
||||
"blockbook/bchain"
|
||||
"blockbook/bchain/coins"
|
||||
"blockbook/common"
|
||||
|
@ -19,7 +20,7 @@ import (
|
|||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/erikdubbelboer/gspt"
|
||||
// "github.com/erikdubbelboer/gspt"
|
||||
"github.com/golang/glog"
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
|
@ -36,8 +37,9 @@ const storeInternalStatePeriodMs = 59699
|
|||
var (
|
||||
blockchain = flag.String("blockchaincfg", "", "path to blockchain RPC service configuration json file")
|
||||
|
||||
dbPath = flag.String("datadir", "./data", "path to database directory")
|
||||
dbCache = flag.Int("dbcache", 1<<29, "size of the rocksdb cache")
|
||||
dbPath = flag.String("datadir", "./data", "path to database directory")
|
||||
dbCache = flag.Int("dbcache", 1<<29, "size of the rocksdb cache")
|
||||
dbMaxOpenFiles = flag.Int("dbmaxopenfiles", 1<<14, "max open files by rocksdb")
|
||||
|
||||
blockFrom = flag.Int("blockheight", -1, "height of the starting block")
|
||||
blockUntil = flag.Int("blockuntil", -1, "height of the final block")
|
||||
|
@ -49,17 +51,17 @@ var (
|
|||
repair = flag.Bool("repair", false, "repair the database")
|
||||
prof = flag.String("prof", "", "http server binding [address]:port of the interface to profiling data /debug/pprof/ (default no profiling)")
|
||||
|
||||
syncChunk = flag.Int("chunk", 100, "block chunk size for processing")
|
||||
syncWorkers = flag.Int("workers", 8, "number of workers to process blocks")
|
||||
syncChunk = flag.Int("chunk", 100, "block chunk size for processing in bulk mode")
|
||||
syncWorkers = flag.Int("workers", 8, "number of workers to process blocks in bulk mode")
|
||||
dryRun = flag.Bool("dryrun", false, "do not index blocks, only download")
|
||||
|
||||
debugMode = flag.Bool("debug", false, "debug mode, return more verbose errors, reload templates on each request")
|
||||
|
||||
internalBinding = flag.String("internal", "", "internal http server binding [address]:port, (default no internal server)")
|
||||
|
||||
publicBinding = flag.String("public", "", "public http server binding [address]:port[/path], (default no public server)")
|
||||
publicBinding = flag.String("public", "", "public http server binding [address]:port[/path] (default no public server)")
|
||||
|
||||
certFiles = flag.String("certfile", "", "to enable SSL specify path to certificate files without extension, expecting <certfile>.crt and <certfile>.key, (default no SSL)")
|
||||
certFiles = flag.String("certfile", "", "to enable SSL specify path to certificate files without extension, expecting <certfile>.crt and <certfile>.key (default no SSL)")
|
||||
|
||||
explorerURL = flag.String("explorer", "", "address of blockchain explorer")
|
||||
|
||||
|
@ -84,10 +86,11 @@ var (
|
|||
chain bchain.BlockChain
|
||||
index *db.RocksDB
|
||||
txCache *db.TxCache
|
||||
metrics *common.Metrics
|
||||
syncWorker *db.SyncWorker
|
||||
internalState *common.InternalState
|
||||
callbacksOnNewBlockHash []func(hash string)
|
||||
callbacksOnNewTxAddr []func(txid string, addr string)
|
||||
callbacksOnNewBlock []bchain.OnNewBlockFunc
|
||||
callbacksOnNewTxAddr []bchain.OnNewTxAddrFunc
|
||||
chanOsSignal chan os.Signal
|
||||
inShutdown int32
|
||||
)
|
||||
|
@ -154,9 +157,9 @@ func main() {
|
|||
glog.Fatal("config: ", err)
|
||||
}
|
||||
|
||||
gspt.SetProcTitle("blockbook-" + normalizeName(coin))
|
||||
// gspt.SetProcTitle("blockbook-" + normalizeName(coin))
|
||||
|
||||
metrics, err := common.GetMetrics(coin)
|
||||
metrics, err = common.GetMetrics(coin)
|
||||
if err != nil {
|
||||
glog.Fatal("metrics: ", err)
|
||||
}
|
||||
|
@ -165,7 +168,7 @@ func main() {
|
|||
glog.Fatal("rpc: ", err)
|
||||
}
|
||||
|
||||
index, err = db.NewRocksDB(*dbPath, *dbCache, chain.GetChainParser(), metrics)
|
||||
index, err = db.NewRocksDB(*dbPath, *dbCache, *dbMaxOpenFiles, chain.GetChainParser(), metrics)
|
||||
if err != nil {
|
||||
glog.Fatal("rocksDB: ", err)
|
||||
}
|
||||
|
@ -239,6 +242,11 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
// report BlockbookAppInfo metric, only log possible error
|
||||
if err = blockbookAppInfoMetric(index, chain, txCache, internalState, metrics); err != nil {
|
||||
glog.Error("blockbookAppInfoMetric ", err)
|
||||
}
|
||||
|
||||
var internalServer *server.InternalServer
|
||||
if *internalBinding != "" {
|
||||
internalServer, err = server.NewInternalServer(*internalBinding, *certFiles, index, chain, txCache, internalState)
|
||||
|
@ -259,19 +267,9 @@ func main() {
|
|||
}()
|
||||
}
|
||||
|
||||
if *synchronize {
|
||||
if err := syncWorker.ResyncIndex(nil, true); err != nil {
|
||||
glog.Error("resyncIndex ", err)
|
||||
return
|
||||
}
|
||||
if _, err = chain.ResyncMempool(nil); err != nil {
|
||||
glog.Error("resyncMempool ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var publicServer *server.PublicServer
|
||||
if *publicBinding != "" {
|
||||
// start public server in limited functionality, extend it after sync is finished by calling ConnectFullPublicInterface
|
||||
publicServer, err = server.NewPublicServer(*publicBinding, *certFiles, index, chain, txCache, *explorerURL, metrics, internalState, *debugMode)
|
||||
if err != nil {
|
||||
glog.Error("socketio: ", err)
|
||||
|
@ -288,15 +286,32 @@ func main() {
|
|||
}
|
||||
}
|
||||
}()
|
||||
callbacksOnNewBlockHash = append(callbacksOnNewBlockHash, publicServer.OnNewBlockHash)
|
||||
callbacksOnNewBlock = append(callbacksOnNewBlock, publicServer.OnNewBlock)
|
||||
callbacksOnNewTxAddr = append(callbacksOnNewTxAddr, publicServer.OnNewTxAddr)
|
||||
}
|
||||
|
||||
if *synchronize {
|
||||
// start the synchronization loops after the server interfaces are started
|
||||
internalState.SyncMode = true
|
||||
internalState.InitialSync = true
|
||||
if err := syncWorker.ResyncIndex(nil, true); err != nil {
|
||||
glog.Error("resyncIndex ", err)
|
||||
return
|
||||
}
|
||||
var mempoolCount int
|
||||
if mempoolCount, err = chain.ResyncMempool(nil); err != nil {
|
||||
glog.Error("resyncMempool ", err)
|
||||
return
|
||||
}
|
||||
internalState.FinishedMempoolSync(mempoolCount)
|
||||
go syncIndexLoop()
|
||||
go syncMempoolLoop()
|
||||
go storeInternalStateLoop()
|
||||
internalState.InitialSync = false
|
||||
}
|
||||
go storeInternalStateLoop()
|
||||
|
||||
if *publicBinding != "" {
|
||||
// start full public interface
|
||||
publicServer.ConnectFullPublicInterface()
|
||||
}
|
||||
|
||||
if *blockFrom >= 0 {
|
||||
|
@ -334,6 +349,25 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
func blockbookAppInfoMetric(db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is *common.InternalState, metrics *common.Metrics) error {
|
||||
api, err := api.NewWorker(db, chain, txCache, is)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
si, err := api.GetSystemInfo(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
metrics.BlockbookAppInfo.Reset()
|
||||
metrics.BlockbookAppInfo.With(common.Labels{
|
||||
"blockbook_version": si.Blockbook.Version,
|
||||
"blockbook_commit": si.Blockbook.GitCommit,
|
||||
"backend_version": si.Backend.Version,
|
||||
"backend_subversion": si.Backend.Subversion,
|
||||
"backend_protocol_version": si.Backend.ProtocolVersion}).Set(float64(0))
|
||||
return nil
|
||||
}
|
||||
|
||||
func newInternalState(coin string, coinShortcut string, d *db.RocksDB) (*common.InternalState, error) {
|
||||
is, err := d.LoadInternalState(coin)
|
||||
if err != nil {
|
||||
|
@ -399,9 +433,9 @@ func syncIndexLoop() {
|
|||
glog.Info("syncIndexLoop stopped")
|
||||
}
|
||||
|
||||
func onNewBlockHash(hash string) {
|
||||
for _, c := range callbacksOnNewBlockHash {
|
||||
c(hash)
|
||||
func onNewBlockHash(hash string, height uint32) {
|
||||
for _, c := range callbacksOnNewBlock {
|
||||
c(hash, height)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -431,8 +465,8 @@ func storeInternalStateLoop() {
|
|||
lastCompute := time.Now()
|
||||
// randomize the duration between ComputeInternalStateColumnStats to avoid peaks after reboot of machine with multiple blockbooks
|
||||
computePeriod := 9*time.Hour + time.Duration(rand.Float64()*float64((2*time.Hour).Nanoseconds()))
|
||||
lastLogMemory := time.Now()
|
||||
logMemoryPeriod := 15 * time.Minute
|
||||
lastAppInfo := time.Now()
|
||||
logAppInfoPeriod := 15 * time.Minute
|
||||
glog.Info("storeInternalStateLoop starting with db stats recompute period ", computePeriod)
|
||||
tickAndDebounce(storeInternalStatePeriodMs*time.Millisecond, (storeInternalStatePeriodMs-1)*time.Millisecond, chanStoreInternalState, func() {
|
||||
if !computeRunning && lastCompute.Add(computePeriod).Before(time.Now()) {
|
||||
|
@ -449,17 +483,20 @@ func storeInternalStateLoop() {
|
|||
if err := index.StoreInternalState(internalState); err != nil {
|
||||
glog.Error("storeInternalStateLoop ", errors.ErrorStack(err))
|
||||
}
|
||||
if lastLogMemory.Add(logMemoryPeriod).Before(time.Now()) {
|
||||
if lastAppInfo.Add(logAppInfoPeriod).Before(time.Now()) {
|
||||
glog.Info(index.GetMemoryStats())
|
||||
lastLogMemory = time.Now()
|
||||
if err := blockbookAppInfoMetric(index, chain, txCache, internalState, metrics); err != nil {
|
||||
glog.Error("blockbookAppInfoMetric ", err)
|
||||
}
|
||||
lastAppInfo = time.Now()
|
||||
}
|
||||
})
|
||||
glog.Info("storeInternalStateLoop stopped")
|
||||
}
|
||||
|
||||
func onNewTxAddr(txid string, addr string) {
|
||||
func onNewTxAddr(txid string, addr string, isOutput bool) {
|
||||
for _, c := range callbacksOnNewTxAddr {
|
||||
c(txid, addr)
|
||||
c(txid, addr, isOutput)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBFpZENYBEACgAfjxYOe3IG0/IUmrpTExiWFvBsVAI2qDn5F11vuimbr1YMGX
|
||||
PqcmFGWrYZM/zEQCO6xNZEIQ9rh79N15hXfLc9dlLx8PjuxPHNKnPDprpdsYpa37
|
||||
Dw0ufHpv+av2qqwG7o9zUan2a+QqsWXjCWhdJVNwYOFY9uRBKqkYMf+r0VOFGPIX
|
||||
Pjb0n5TXdpI0FFvydeFb+HABnYqbrPcEAQQETpVukQZQtmE8UBDueO+Hz0RvJAw+
|
||||
xz79ks4muyPf4MGoKOu6xWM2mViXeP1AN+Mi3nV3EUVPKLxS2EyByrgWEh8iIdgj
|
||||
vu2fRSaHSuhJWbVSkMnAPJsC3bQE/aZZ8PtaLvgVUH/0euVoKHmxIg8bj1RzNvYb
|
||||
VZ2VL8Sp8xHIptkTwhgj+spi88BDI1xkBXaHt0ZlddyhgMtK7dl75mn6d+UsGQsT
|
||||
4dovbtRWRFU1hww7utgtgJniwOYMzhLdY6+OZ8CSA/uB1bTGRigbSd3UtTEwHnQi
|
||||
xEkHzuF4IoIbaIRLluCASs331vwk+cCZoXD8hdNQdVOwI/4Ss16RAdMFa+DGbBKb
|
||||
OUpnHBx9wmGH/nq1rfBZD1r/sE7X3LhDeQfqHrRGZOFTAF3CBsiZr7SDAJMCPys6
|
||||
7y4KQtC+Kfyku+iDNsnObbCXc86qIrYEOspwMqrZgT3FmA0H+cX9EtoE8QARAQAB
|
||||
tEhTaGFtbWFoIENoYW5jZWxsb3IgKEJpdGNvaW4gQUJDIGtleSkgPHNoYW1tYWgu
|
||||
Y2hhbmNlbGxvckBiaXRjb2luYWJjLm9yZz6JAhwEEAEIAAYFAlsWzFAACgkQjVUt
|
||||
ITLQxfZMCBAAmWotQ7ow3WIEdnBYUxZMMdGxU0WRz13lN+4YnwxAeKoBcpNwFhSz
|
||||
0wGZAAVzm/hH2aLNMY/Xqt4vq2vuHC1ovOrRIL7Wk0msT/enlyPmK1j+r2zNz1Qk
|
||||
92ybAfuZ2r/8zO6M1NXOrhFEOmBn6lPLQu79P6T0wGfQue3O0XPx53+WYKmNoyq7
|
||||
DQRphmBbRJikdz5TP1ffEUZ+AL901Q/b3gJ4fLpowaJ44C4J+cH++OoLY2lQFPVR
|
||||
v2JyVa8SeUFfB19ki0m0r9gYEm7RDHlwcLPKwuM45armV6JTE+PW31XZqsE3Xw5h
|
||||
vGGOT9bY+KoMfgplYJYr9XH1qNgUBYJZ91BAs0vuqieIoMsMHl5h+ii/MDz6eJ9y
|
||||
5mwR1BKuhS0oIgvoviVU/GhEpo0GQqhsaOejnNl1/kYFpHzosFSf+9EmLWHgicKO
|
||||
zwumIwsi+AjSI97pg8K8YaxkaA/uagHkutQ1oFgt/tAd6eOGnyNDCBBRSbYVuFJV
|
||||
YoiRG8IaIHZkUGmrHqDeZd2Qk5YwIG7tLAX9v0cgMlDLFLx1B8LQSY9wH06zjV1a
|
||||
mY69q3RzQ3mvKCf2QidB5o1FA/CV9Sn0nTvN0UKyYIXNr/KeoyCZkBuge+hD5au/
|
||||
OBrtCjGTzq6496hJbwQR83O6S3r4t4KBz2isQuoksrdxwR0cHg65hBqJAhwEEAEI
|
||||
AAYFAls6O2kACgkQ5EhjVueoHSwGlA/6ApGOSYwQlppEgmwrN2BZLEmfN8pfn4ES
|
||||
ymYoFKn5/4R2A11Xp5w2z9EE9JW7/yLnoar0jR/Hw16TPceiVoyRD1r5MCEeMDmj
|
||||
TymDJF4p5ZeiWqkFq1CAvEdCsNB952O2F5PmqGnIm0SW7H8h49Hy1c1Cpa+d0xgg
|
||||
eQkoEqtihxWabMJ7UW1r6T77dEDoPyAcP2Yi8Yv/e0Eaayv58gxB7ieGi+NsAXLL
|
||||
LiGkRXhP+FHpBS9yLnxqLEPcDfivMaL3CGocJziA9GlZgSQGUjBkaVoI+Ynd9JTw
|
||||
C30XususiGthXrqF5e56PIVubOhrxqAlKoxahx/kedRfWPlD/MApNxaokgmDqBpi
|
||||
E2YLPzaY/RKek/AC5UAgw97yiDactt+UICcHbtG/tznQ6ECO8llI7+DSAWAKCNi1
|
||||
iCPZmdjGqv+j/S5xR3X3EdB0LoXpqE6HJE2E1+mUN/QCsz/6u/zmw1Zj5x9niPOp
|
||||
g5PiIPDTLrhropMKMmvxdfKfKWOXD+I9b/tKldWi7Dz8MPlvXpkEqeA8HKRK8Ek7
|
||||
bq9iLdqGFTP/0T+nU77baOmMMSHsoimIIZRysg04n8WR6A4AAOACX/BoB6agxLn8
|
||||
1N//xMTqUA0Aror9dLNXl0iCbUshnFkx1wiq93rdZAn6nCI0wlFtg2yhh/dWo1sZ
|
||||
caGNIgGdxn2JAhwEEAEIAAYFAls7ja0ACgkQXXkiu9ZJxKdf1RAArXapCE/zxWpd
|
||||
f9cNOClRBLXZlj7FiW7Mf63DgdO0ae4gK4sL812bSwEH4KeuQxkugYsOcH6oezWh
|
||||
mXxJ8lHOeCFbW+a6VBx7kclUweEOpFtcfrJU1bgLywGM6FtWNn1m1mAMKCrqDgPQ
|
||||
U2L6UnhY+H3QgRqqYo1nzRsfpqnAxw+7nsquD4ziQS/Mumbj2C0Gjg5ScjkfAidk
|
||||
okp5jdoIEbanjKQ8zuSRWvomGeT6UF1AmPA3bNtN3ROD9co4H8fKeFKamrCzVZul
|
||||
+eu5/Y+cCvQ5PEHZoflZYwa3qdTgQJhIlzBmGIipYstHVPfyeFKN9JYXv01FNvDT
|
||||
h1ebvDzgvaBB/lln3CxWHSEkv7jTu902ljUO9iuPdd0tX89FN1FlLCqeXXoT9Qfi
|
||||
NOaRJzNDkqdHl53VrhuNH5mZWXXAuD5hkZo1I5A90YuOLOQ5wnl/8MbDS4VW8tpK
|
||||
HWqn5b0GFacB2xsSphYpletkQnwQWFy352NBveDf68E9rGA9WS5B7cVvnSiNZWNT
|
||||
5hzE5krS6VZjSIo062PVySTWAqgCy1OWdEPZXo71BpKHV3AgBTAaoPj6EWDFaqS3
|
||||
4/5s4odJBUgtceEg4naXFp5GdrrixzInLwjNu1FdpU3NM1thelWzErYi8k8OMVKy
|
||||
QroQJ+ObZ3PIKBaLgAyOTJq6TZqxa/2JAjcEEwEIACsFAlpZENYJEH05WMREJ2dK
|
||||
AhsDBQkHhM4ABQsJCAcCBhUKCQgLAgQWAgMBAABVdw/+Jc4K7/8rcWawTG7u2jQG
|
||||
C3un44dHrVwFxft1IQWbc1ZYx7xvTFtAyOwatwL+PnHjJlMK6ODztJnNHS95CqPS
|
||||
9dkUztrlQF+j4gmc6/7h0Ew3M56N9tVOXP/3Un+kPgGLdWvVYf0YZsy0r8BO7vQb
|
||||
PXk2coKiWwfony55DDt9RaxLNlWukKq3dg+vejZzzucfKuZIlUcQDRJO4rCXxMBv
|
||||
Oo/BtgfOXACjzi4O8aF2TNzEKyXLp8KqcB8j2knu2PIJidr8fwzyBwe3yF8tox3x
|
||||
4nvSn5IYvvvP4EG8fhsGVBEc+EwZJnbrk5afRBnM/uYW+e+/7vBQwlZyvFOr/zMJ
|
||||
rhbtcWwW8VkFhvbwc40K7qsiJOxfzXMfGVPtutbzhz8sRivija03LrE0ysgesoM4
|
||||
LpxsHDhwScwTTupZ8qsvoNDqgyuJvEUeNXKv1fdY9IVtKgJuU5JHcLOyhUB8/xkd
|
||||
2qle6fKQK1k776HhdmseU6Ba3cbe3zZYJfYGnoHXttfOZ5QhKNlFhyCdmv7CImP3
|
||||
KFYKfYcbx+CVhGJpm5YEYk3WW2FyuBx80WrEbztGcvqZzPQmNPAhvRkVErNoxA/A
|
||||
KCuLTNrz0jivgBny6JKGdY8drXRafovBroJcLGGFfTVjgZouUis1VlZX4Bo4Twcq
|
||||
Pc5AK4OXCa/WK1IjXTSblDWJAlQEEwEIAD4WIQR6VaRPOjI5gnyKWU59OVjERCdn
|
||||
SgUCWlkQ1gIbAwUJB4TOAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRB9OVjE
|
||||
RCdnSmNWD/47fhdIy2gdHKl+BqjUezcmBs5eR5V8/pz6kbT1+cDFHZ+ImcK+jf8I
|
||||
8oQPeFWecBF1ZERoLaIRD6UwIVs6z7yAGYlCeweitWf7cfgvIvzP9b/sLweAfwD8
|
||||
fNfKTQuw3wx6Yr66+y42lM5dasu+WyzBYLQhQI6q+WgsRL5j7IuNFTdtAc/gNY5c
|
||||
GlEPXMl9RCjJlCeyo9UNvMeL61UrkPUMVOOqH4Bl8yatPilu0vyGtYogp+XTbmEk
|
||||
ZJ1BWhL4Y8VkVdBhX9LhYZ+klI6UjNxnZEnVwTwVhZCMXBgi6t4X8EJqeVa5/gb5
|
||||
LQ7L261z3y3wzyKZ9+wTjjihXaHX+5n7b1oTZF08+1cY9qe1tDIWfsDY5HjoJb15
|
||||
MSqy5EdTJsH+h2JlzMD3t57pTCmGXPmA8mdec78q5DA2PlCdc0qewOTDJ8d56J/x
|
||||
4PrSZ8TZDRIKA3fflRfWwKrAmB+xwR4cvAXbNC1PxC4BvlVOFKBseDHv7VwWJvnH
|
||||
0Gnc6hdHdzJJYtTQBglOH6CifHBnksc6/pnFedQ8yWRKXFY3rsaqpzkiiqCPl9jG
|
||||
QCPWAEvehT6v4iFFiQkZ9OTjBVmT9Ea6BvhpCplpgPo+AFKS3L1mMZYf8RIHazdo
|
||||
fQof6w/oMxmwQWT/k3O0Z4JRNW0p2eyB8RS3tK3YT2zY3y4TSyYqY7kCDQRaWRDW
|
||||
ARAA0zg0VamqvOmzSg+viNRFwPmetdPNmiKq7feDail0r8vtfAIKuXcjR6EF0FEB
|
||||
w9eWlzbX0Eu6c/MVPRycj9Ka+7KqZbEAlOOC97gzA2tcRar8aEM1ZXDBzyr3ZbbK
|
||||
aH6/XPYm+oxnYxwlMY99/LpiTcOkQyc4yJCXSC8GV9frQhVOGPI7gPca7DjroGdb
|
||||
as/uTiyPNp4xq0JTI/xWYLRtH+dTEfH2/vTpoXku0itdfR4uoCPFzOYCegfObIOX
|
||||
3UsUvkoNKWBoQGc14YarTMAjagDDUBhnhJ84b/ftOWTvyYyq/QR4oJdGUkTp854Z
|
||||
4r2J8cWzW2tpuALxUrHs4AcTmW3cD37B7j3lhzpfA3NGLYwDBQfQkFMwtRIxskf3
|
||||
EG7Ednq5fXJWCekIYSji8bDaGu/A5exuj5H0Mg3DKUcVQ+p2bRh6fjKZwG6gEQWU
|
||||
rxu6RsjFYg6KuLUb0q0Y0Vt/vvLASkY4FoIxjj0zGR+KBlg8yJsU44HdiI40LRPy
|
||||
2juYgGAhL2j53Yrr+ID0ZFbe9TpQePcBfD5lEWxVH9aBjjfEz1TUYguNQXo3mVrO
|
||||
OWZek6f9Uh5Yt+WSPSrZfzbRsROcv3OOC83lSYcL2ugiFjt9oGXZBYhfYNLCKDpW
|
||||
10nGVe0oVSjIA53QIdhQ233WPvwi6ur5Fa8j5OvaIWnwpecAEQEAAYkCPAQYAQgA
|
||||
JhYhBHpVpE86MjmCfIpZTn05WMREJ2dKBQJaWRDWAhsMBQkHhM4AAAoJEH05WMRE
|
||||
J2dK5VMP/2IIPi707oF3/iflM3JvQukjmGNLpm3sVrx0baSODW2IfSNQeMWYXvoa
|
||||
xgaKrMG+WGzr1P/D1jv+HD8gxjakTvZYooJLHF/9nmZE1bD/p0BGFpoRuvX9L/J9
|
||||
Yqu32S89lYqnvyXY3c6Aqt/sJj1SHCPdFWMT/kcYjGHeBaX2Ub5+zVm4THESwssv
|
||||
GsT37N6Wzz0P2wI1rSRu29evKbRTebRIZXaSWiVlx4P7Y6CNFNptzkH7oqf6JmkB
|
||||
qaQZuOZ3dhe6Z9HetOFN9D5X0ju3DgUITEmF6BYekGBEv/HVJs6rbTb9us8sV4Vu
|
||||
WvmTYCCIn/fOnmFD6WomjVeX1E6DJ9YvRtCPtau0JcxxBy1L3/3IS2fEf3zpTK6O
|
||||
aX2GnpijmLFweDGD7CocaEmfw3xUvwgDEpIIvaMNnlciujZFd+8MMJ/JeQ+l5xuw
|
||||
yWcUciwnWIoCQBOTl3u0u/mWeQsHQr81pirknugjJ8OCPyMUbtsNm9aAySFj6lra
|
||||
O18EF0uVo+oYDulM+2InSjEJQLYj392XOG4sWkO0bPWJ6FG8VFC2MH09bnsU7dyH
|
||||
mN6ZWn6JZI3uPY3UbRsXTnVws4OG4bBCpuCe0mJ35gWGAqj0ZDm8SA5Utx4j2oEl
|
||||
h0HChP/lLDxJ5t5PfBVyudK+xewFFx/cX+4f/n7GZp2+wvduWrs7
|
||||
=Grfn
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
|
@ -37,6 +37,10 @@ type InternalState struct {
|
|||
|
||||
LastStore time.Time `json:"lastStore"`
|
||||
|
||||
// true if application is with flag --sync
|
||||
SyncMode bool `json:"syncMode"`
|
||||
|
||||
InitialSync bool `json:"initialSync"`
|
||||
IsSynchronized bool `json:"isSynchronized"`
|
||||
BestHeight uint32 `json:"bestHeight"`
|
||||
LastSync time.Time `json:"lastSync"`
|
||||
|
@ -64,6 +68,14 @@ func (is *InternalState) FinishedSync(bestHeight uint32) {
|
|||
is.LastSync = time.Now()
|
||||
}
|
||||
|
||||
// UpdateBestHeight sets new best height, without changing IsSynchronized flag
|
||||
func (is *InternalState) UpdateBestHeight(bestHeight uint32) {
|
||||
is.mux.Lock()
|
||||
defer is.mux.Unlock()
|
||||
is.BestHeight = bestHeight
|
||||
is.LastSync = time.Now()
|
||||
}
|
||||
|
||||
// FinishedSyncNoChange marks end of synchronization in case no index update was necessary, it does not update lastSync time
|
||||
func (is *InternalState) FinishedSyncNoChange() {
|
||||
is.mux.Lock()
|
||||
|
|
|
@ -21,6 +21,7 @@ type Metrics struct {
|
|||
MempoolSize prometheus.Gauge
|
||||
DbColumnRows *prometheus.GaugeVec
|
||||
DbColumnSize *prometheus.GaugeVec
|
||||
BlockbookAppInfo *prometheus.GaugeVec
|
||||
}
|
||||
|
||||
type Labels = prometheus.Labels
|
||||
|
@ -139,6 +140,14 @@ func GetMetrics(coin string) (*Metrics, error) {
|
|||
},
|
||||
[]string{"column"},
|
||||
)
|
||||
metrics.BlockbookAppInfo = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "blockbook_app_info",
|
||||
Help: "Information about blockbook and backend application versions",
|
||||
ConstLabels: Labels{"coin": coin},
|
||||
},
|
||||
[]string{"blockbook_version", "blockbook_commit", "backend_version", "backend_subversion", "backend_protocol_version"},
|
||||
)
|
||||
|
||||
v := reflect.ValueOf(metrics)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
|
|
|
@ -22,10 +22,10 @@
|
|||
"package_name": "backend-bcash",
|
||||
"package_revision": "satoshilabs-1",
|
||||
"system_user": "bcash",
|
||||
"version": "0.17.1",
|
||||
"binary_url": "https://download.bitcoinabc.org/0.17.1/linux/bitcoin-abc-0.17.1-x86_64-linux-gnu.tar.gz",
|
||||
"version": "0.18.2",
|
||||
"binary_url": "https://download.bitcoinabc.org/0.18.2/linux/bitcoin-abc-0.18.2-x86_64-linux-gnu.tar.gz",
|
||||
"verification_type": "sha256",
|
||||
"verification_source": "eccf8b61ba0549f6839e586c7dc6fc4bf6d7591ac432aaea8a7df0266b113d27",
|
||||
"verification_source": "28d8511789a126aff16e256a03288948f2660c3c8cb0a4c809c5a8618a519a16",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"exclude_files": [
|
||||
"bin/bitcoin-qt"
|
||||
|
@ -44,7 +44,7 @@
|
|||
"system_user": "blockbook-bcash",
|
||||
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
|
||||
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
|
||||
"explorer_url": "https://bitcoincash.blockexplorer.com",
|
||||
"explorer_url": "",
|
||||
"additional_params": "",
|
||||
"block_chain": {
|
||||
"parse": true,
|
||||
|
|
|
@ -22,10 +22,10 @@
|
|||
"package_name": "backend-bcash-testnet",
|
||||
"package_revision": "satoshilabs-1",
|
||||
"system_user": "bcash",
|
||||
"version": "0.17.1",
|
||||
"binary_url": "https://download.bitcoinabc.org/0.17.1/linux/bitcoin-abc-0.17.1-x86_64-linux-gnu.tar.gz",
|
||||
"version": "0.18.2",
|
||||
"binary_url": "https://download.bitcoinabc.org/0.18.2/linux/bitcoin-abc-0.18.2-x86_64-linux-gnu.tar.gz",
|
||||
"verification_type": "sha256",
|
||||
"verification_source": "eccf8b61ba0549f6839e586c7dc6fc4bf6d7591ac432aaea8a7df0266b113d27",
|
||||
"verification_source": "28d8511789a126aff16e256a03288948f2660c3c8cb0a4c809c5a8618a519a16",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"exclude_files": [
|
||||
"bin/bitcoin-qt"
|
||||
|
@ -44,7 +44,7 @@
|
|||
"system_user": "blockbook-bcash",
|
||||
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
|
||||
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
|
||||
"explorer_url": "https://bitcoincash.blockexplorer.com",
|
||||
"explorer_url": "",
|
||||
"additional_params": "",
|
||||
"block_chain": {
|
||||
"parse": true,
|
||||
|
|
|
@ -22,10 +22,10 @@
|
|||
"package_name": "backend-bgold",
|
||||
"package_revision": "satoshilabs-1",
|
||||
"system_user": "bgold",
|
||||
"version": "0.15.1",
|
||||
"binary_url": "https://github.com/BTCGPU/BTCGPU/releases/download/v0.15.1/bitcoin-gold-0.15.1-x86_64-linux-gnu.tar.gz",
|
||||
"version": "0.15.2",
|
||||
"binary_url": "https://github.com/BTCGPU/BTCGPU/releases/download/v0.15.2/bitcoin-gold-0.15.2-x86_64-linux-gnu.tar.gz",
|
||||
"verification_type": "gpg-sha256",
|
||||
"verification_source": "https://github.com/BTCGPU/BTCGPU/releases/download/v0.15.1/SHA256SUMS.asc",
|
||||
"verification_source": "https://github.com/BTCGPU/BTCGPU/releases/download/v0.15.2/SHA256SUMS.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"exclude_files": [
|
||||
"bin/bitcoin-qt"
|
||||
|
@ -239,11 +239,11 @@
|
|||
"system_user": "blockbook-bgold",
|
||||
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
|
||||
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
|
||||
"explorer_url": "https://btg-explorer.trezor.io/",
|
||||
"explorer_url": "",
|
||||
"additional_params": "",
|
||||
"block_chain": {
|
||||
"parse": true,
|
||||
"subversion": "/Bitcoin Gold:0.15.1/",
|
||||
"subversion": "/Bitcoin Gold:0.15.2/",
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
|
|
|
@ -22,10 +22,10 @@
|
|||
"package_name": "backend-bitcoin",
|
||||
"package_revision": "satoshilabs-1",
|
||||
"system_user": "bitcoin",
|
||||
"version": "0.16.1",
|
||||
"binary_url": "https://bitcoin.org/bin/bitcoin-core-0.16.1/bitcoin-0.16.1-x86_64-linux-gnu.tar.gz",
|
||||
"version": "0.16.3",
|
||||
"binary_url": "https://bitcoin.org/bin/bitcoin-core-0.16.3/bitcoin-0.16.3-x86_64-linux-gnu.tar.gz",
|
||||
"verification_type": "gpg-sha256",
|
||||
"verification_source": "https://bitcoin.org/bin/bitcoin-core-0.16.1/SHA256SUMS.asc",
|
||||
"verification_source": "https://bitcoin.org/bin/bitcoin-core-0.16.3/SHA256SUMS.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"exclude_files": [
|
||||
"bin/bitcoin-qt"
|
||||
|
@ -47,7 +47,7 @@
|
|||
"system_user": "blockbook-bitcoin",
|
||||
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
|
||||
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
|
||||
"explorer_url": "/explorer",
|
||||
"explorer_url": "",
|
||||
"additional_params": "-dbcache=1073741824",
|
||||
"block_chain": {
|
||||
"parse": true,
|
||||
|
|
|
@ -22,10 +22,10 @@
|
|||
"package_name": "backend-bitcoin-testnet",
|
||||
"package_revision": "satoshilabs-1",
|
||||
"system_user": "bitcoin",
|
||||
"version": "0.16.1",
|
||||
"binary_url": "https://bitcoin.org/bin/bitcoin-core-0.16.1/bitcoin-0.16.1-x86_64-linux-gnu.tar.gz",
|
||||
"version": "0.16.3",
|
||||
"binary_url": "https://bitcoin.org/bin/bitcoin-core-0.16.3/bitcoin-0.16.3-x86_64-linux-gnu.tar.gz",
|
||||
"verification_type": "gpg-sha256",
|
||||
"verification_source": "https://bitcoin.org/bin/bitcoin-core-0.16.1/SHA256SUMS.asc",
|
||||
"verification_source": "https://bitcoin.org/bin/bitcoin-core-0.16.3/SHA256SUMS.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"exclude_files": [
|
||||
"bin/bitcoin-qt"
|
||||
|
@ -47,7 +47,7 @@
|
|||
"system_user": "blockbook-bitcoin",
|
||||
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
|
||||
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
|
||||
"explorer_url": "/explorer",
|
||||
"explorer_url": "",
|
||||
"additional_params": "",
|
||||
"block_chain": {
|
||||
"parse": true,
|
||||
|
|
|
@ -22,10 +22,10 @@
|
|||
"package_name": "backend-dash",
|
||||
"package_revision": "satoshilabs-1",
|
||||
"system_user": "dash",
|
||||
"version": "0.12.3",
|
||||
"binary_url": "https://github.com/dashpay/dash/releases/download/v0.12.3.2/dashcore-0.12.3.2-x86_64-linux-gnu.tar.gz",
|
||||
"version": "0.12.3.3",
|
||||
"binary_url": "https://github.com/dashpay/dash/releases/download/v0.12.3.3/dashcore-0.12.3.3-x86_64-linux-gnu.tar.gz",
|
||||
"verification_type": "gpg-sha256",
|
||||
"verification_source": "https://github.com/dashpay/dash/releases/download/v0.12.3.2/SHA256SUMS.asc",
|
||||
"verification_source": "https://github.com/dashpay/dash/releases/download/v0.12.3.3/SHA256SUMS.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"exclude_files": [
|
||||
"bin/dash-qt"
|
||||
|
@ -47,11 +47,11 @@
|
|||
"system_user": "blockbook-dash",
|
||||
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
|
||||
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
|
||||
"explorer_url": "https://dash-explorer.trezor.io",
|
||||
"explorer_url": "",
|
||||
"additional_params": "",
|
||||
"block_chain": {
|
||||
"parse": true,
|
||||
"subversion": "/Dash Core:0.12.3.2/",
|
||||
"subversion": "/Dash Core:0.12.3.3/",
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
|
|
|
@ -22,10 +22,10 @@
|
|||
"package_name": "backend-dash-testnet",
|
||||
"package_revision": "satoshilabs-1",
|
||||
"system_user": "dash",
|
||||
"version": "0.12.3",
|
||||
"binary_url": "https://github.com/dashpay/dash/releases/download/v0.12.3.2/dashcore-0.12.3.2-x86_64-linux-gnu.tar.gz",
|
||||
"version": "0.12.3.3",
|
||||
"binary_url": "https://github.com/dashpay/dash/releases/download/v0.12.3.3/dashcore-0.12.3.3-x86_64-linux-gnu.tar.gz",
|
||||
"verification_type": "gpg-sha256",
|
||||
"verification_source": "https://github.com/dashpay/dash/releases/download/v0.12.3.2/SHA256SUMS.asc",
|
||||
"verification_source": "https://github.com/dashpay/dash/releases/download/v0.12.3.3/SHA256SUMS.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"exclude_files": [
|
||||
"bin/dash-qt"
|
||||
|
@ -47,11 +47,11 @@
|
|||
"system_user": "blockbook-dash",
|
||||
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
|
||||
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
|
||||
"explorer_url": "https://dash-explorer.trezor.io",
|
||||
"explorer_url": "",
|
||||
"additional_params": "",
|
||||
"block_chain": {
|
||||
"parse": true,
|
||||
"subversion": "/Dash Core:0.12.3.2/",
|
||||
"subversion": "/Dash Core:0.12.3.3/",
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
|
||||
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
|
||||
"explorer_url": "https://gastracker.io/",
|
||||
"additional_params": "",
|
||||
"additional_params": "-resyncindexperiod=4441",
|
||||
"block_chain": {
|
||||
"parse": true,
|
||||
"mempool_workers": 8,
|
||||
|
|
|
@ -21,10 +21,10 @@
|
|||
"package_name": "backend-ethereum",
|
||||
"package_revision": "satoshilabs-1",
|
||||
"system_user": "ethereum",
|
||||
"version": "1.8.10-eae63c51",
|
||||
"binary_url": "https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.8.10-eae63c51.tar.gz",
|
||||
"version": "1.8.15-89451f7c",
|
||||
"binary_url": "https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.8.15-89451f7c.tar.gz",
|
||||
"verification_type": "gpg",
|
||||
"verification_source": "https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.8.10-eae63c51.tar.gz.asc",
|
||||
"verification_source": "https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.8.15-89451f7c.tar.gz.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"exclude_files": [],
|
||||
"exec_command_template": "/bin/sh -c '{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/geth --ipcdisable --syncmode full --cache 1024 --nat none --datadir {{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend --port 38336 --ws --wsaddr 0.0.0.0 --wsport {{.Ports.BackendRPC}} --wsorigins \"*\" --rpc --rpcport 8136 -rpcaddr 0.0.0.0 --rpccorsdomain \"*\" --rpcvhosts \"*\" 2>>{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/{{.Coin.Alias}}.log'",
|
||||
|
|
|
@ -20,13 +20,13 @@
|
|||
"package_name": "backend-ethereum-testnet-ropsten",
|
||||
"package_revision": "satoshilabs-1",
|
||||
"system_user": "ethereum",
|
||||
"version": "1.8.10-eae63c51",
|
||||
"binary_url": "https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.8.10-eae63c51.tar.gz",
|
||||
"version": "1.8.15-89451f7c",
|
||||
"binary_url": "https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.8.15-89451f7c.tar.gz",
|
||||
"verification_type": "gpg",
|
||||
"verification_source": "https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.8.10-eae63c51.tar.gz.asc",
|
||||
"verification_source": "https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.8.15-89451f7c.tar.gz.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"exclude_files": [],
|
||||
"exec_command_template": "/bin/sh -c '{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/geth --testnet --ipcdisable --cache 1024 --nat none --datadir {{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend --port 48336 --ws --wsaddr 0.0.0.0 --wsport {{.Ports.BackendRPC}} --wsorigins \"*\" 2>>{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/{{.Coin.Alias}}.log'",
|
||||
"exec_command_template": "/bin/sh -c '{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/geth --testnet --syncmode full --ipcdisable --cache 1024 --nat none --datadir {{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend --port 48336 --ws --wsaddr 0.0.0.0 --wsport {{.Ports.BackendRPC}} --wsorigins \"*\" 2>>{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/{{.Coin.Alias}}.log'",
|
||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/{{.Coin.Alias}}.log",
|
||||
"postinst_script_template": "",
|
||||
"service_type": "simple",
|
||||
|
|
|
@ -22,10 +22,10 @@
|
|||
"package_name": "backend-litecoin",
|
||||
"package_revision": "satoshilabs-1",
|
||||
"system_user": "litecoin",
|
||||
"version": "0.16.0",
|
||||
"binary_url": "https://download.litecoin.org/litecoin-0.16.0/linux/litecoin-0.16.0-x86_64-linux-gnu.tar.gz",
|
||||
"version": "0.16.3",
|
||||
"binary_url": "https://download.litecoin.org/litecoin-0.16.3/linux/litecoin-0.16.3-x86_64-linux-gnu.tar.gz",
|
||||
"verification_type": "gpg-sha256",
|
||||
"verification_source": "https://download.litecoin.org/litecoin-0.16.0/linux/litecoin-0.16.0-linux-signatures.asc",
|
||||
"verification_source": "https://download.litecoin.org/litecoin-0.16.3/linux/litecoin-0.16.3-linux-signatures.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"exclude_files": [
|
||||
"bin/litecoin-qt"
|
||||
|
@ -47,7 +47,7 @@
|
|||
"system_user": "blockbook-litecoin",
|
||||
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
|
||||
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
|
||||
"explorer_url": "https://ltc-explorer.trezor.io/",
|
||||
"explorer_url": "",
|
||||
"additional_params": "",
|
||||
"block_chain": {
|
||||
"parse": true,
|
||||
|
|
|
@ -22,10 +22,10 @@
|
|||
"package_name": "backend-litecoin-testnet",
|
||||
"package_revision": "satoshilabs-1",
|
||||
"system_user": "litecoin",
|
||||
"version": "0.16.0",
|
||||
"binary_url": "https://download.litecoin.org/litecoin-0.16.0/linux/litecoin-0.16.0-x86_64-linux-gnu.tar.gz",
|
||||
"version": "0.16.3",
|
||||
"binary_url": "https://download.litecoin.org/litecoin-0.16.3/linux/litecoin-0.16.3-x86_64-linux-gnu.tar.gz",
|
||||
"verification_type": "gpg-sha256",
|
||||
"verification_source": "https://download.litecoin.org/litecoin-0.16.0/linux/litecoin-0.16.0-linux-signatures.asc",
|
||||
"verification_source": "https://download.litecoin.org/litecoin-0.16.3/linux/litecoin-0.16.3-linux-signatures.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"exclude_files": [
|
||||
"bin/litecoin-qt"
|
||||
|
@ -47,7 +47,7 @@
|
|||
"system_user": "blockbook-litecoin",
|
||||
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
|
||||
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
|
||||
"explorer_url": "https://ltc-explorer.trezor.io",
|
||||
"explorer_url": "",
|
||||
"additional_params": "",
|
||||
"block_chain": {
|
||||
"parse": true,
|
||||
|
|
|
@ -22,10 +22,10 @@
|
|||
"package_name": "backend-monacoin",
|
||||
"package_revision": "satoshilabs-1",
|
||||
"system_user": "monacoin",
|
||||
"version": "0.15.1",
|
||||
"binary_url": "https://github.com/monacoinproject/monacoin/releases/download/monacoin-0.15.1/monacoin-0.15.1-x86_64-linux-gnu.tar.gz",
|
||||
"version": "0.16.3",
|
||||
"binary_url": "https://github.com/monacoinproject/monacoin/releases/download/monacoin-0.16.3/monacoin-0.16.3-x86_64-linux-gnu.tar.gz",
|
||||
"verification_type": "gpg-sha256",
|
||||
"verification_source": "https://github.com/monacoinproject/monacoin/releases/download/monacoin-0.15.1/monacoin-0.15.1-signatures.asc",
|
||||
"verification_source": "https://github.com/monacoinproject/monacoin/releases/download/monacoin-0.16.3/monacoin-0.16.3-signatures.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"exclude_files": [
|
||||
"bin/monacoin-qt"
|
||||
|
|
|
@ -22,10 +22,10 @@
|
|||
"package_name": "backend-monacoin-testnet",
|
||||
"package_revision": "satoshilabs-1",
|
||||
"system_user": "monacoin",
|
||||
"version": "0.15.1",
|
||||
"binary_url": "https://github.com/monacoinproject/monacoin/releases/download/monacoin-0.15.1/monacoin-0.15.1-x86_64-linux-gnu.tar.gz",
|
||||
"version": "0.16.3",
|
||||
"binary_url": "https://github.com/monacoinproject/monacoin/releases/download/monacoin-0.16.3/monacoin-0.16.3-x86_64-linux-gnu.tar.gz",
|
||||
"verification_type": "gpg-sha256",
|
||||
"verification_source": "https://github.com/monacoinproject/monacoin/releases/download/monacoin-0.15.1/monacoin-0.15.1-signatures.asc",
|
||||
"verification_source": "https://github.com/monacoinproject/monacoin/releases/download/monacoin-0.16.3/monacoin-0.16.3-signatures.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"exclude_files": [
|
||||
"bin/monacoin-qt"
|
||||
|
|
|
@ -22,10 +22,10 @@
|
|||
"package_name": "backend-namecoin",
|
||||
"package_revision": "satoshilabs-1",
|
||||
"system_user": "namecoin",
|
||||
"version": "0.13.99",
|
||||
"binary_url": "https://namecoin.org/files/namecoin-core-0.13.99-name-tab-beta1-notreproduced/namecoin-0.13.99-x86_64-linux-gnu.tar.gz",
|
||||
"version": "0.16.3",
|
||||
"binary_url": "https://www.namecoin.org/files/namecoin-core-0.16.3/namecoin-0.16.3-x86_64-linux-gnu.tar.gz",
|
||||
"verification_type": "sha256",
|
||||
"verification_source": "294b1106001d6ea2b9d9ee6a655021ef207a24e8f1dec8efd5899728b3849129",
|
||||
"verification_source": "14ebaaf6f22f69b057a5bcb9b6959548f0a3f1b62cc113f19581d2297044827e",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"exclude_files": [
|
||||
"bin/namecoin-qt"
|
||||
|
@ -54,7 +54,7 @@
|
|||
"system_user": "blockbook-namecoin",
|
||||
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
|
||||
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
|
||||
"explorer_url": "https://namecha.in/",
|
||||
"explorer_url": "",
|
||||
"additional_params": "",
|
||||
"block_chain": {
|
||||
"parse": true,
|
||||
|
|
|
@ -22,10 +22,10 @@
|
|||
"package_name": "backend-vertcoin",
|
||||
"package_revision": "satoshilabs-1",
|
||||
"system_user": "vertcoin",
|
||||
"version": "0.13.2",
|
||||
"binary_url": "https://github.com/vertcoin-project/vertcoin-core/releases/download/0.13.2/vertcoind-v0.13.2-linux-amd64.zip",
|
||||
"version": "0.13.3",
|
||||
"binary_url": "https://github.com/vertcoin-project/vertcoin-core/releases/download/0.13.3/vertcoind-v0.13.3-linux-amd64.zip",
|
||||
"verification_type": "gpg",
|
||||
"verification_source": "https://github.com/vertcoin-project/vertcoin-core/releases/download/0.13.2/vertcoind-v0.13.2-linux-amd64.zip.sig",
|
||||
"verification_source": "https://github.com/vertcoin-project/vertcoin-core/releases/download/0.13.3/vertcoind-v0.13.3-linux-amd64.zip.sig",
|
||||
"extract_command": "unzip -d backend",
|
||||
"exclude_files": [],
|
||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/vertcoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||
|
@ -45,7 +45,7 @@
|
|||
"system_user": "blockbook-vertcoin",
|
||||
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
|
||||
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
|
||||
"explorer_url": "https://insight.vertcoin.org",
|
||||
"explorer_url": "",
|
||||
"additional_params": "",
|
||||
"block_chain": {
|
||||
"parse": true,
|
||||
|
|
|
@ -22,10 +22,10 @@
|
|||
"package_name": "backend-vertcoin-testnet",
|
||||
"package_revision": "satoshilabs-1",
|
||||
"system_user": "vertcoin",
|
||||
"version": "0.13.2",
|
||||
"binary_url": "https://github.com/vertcoin-project/vertcoin-core/releases/download/0.13.2/vertcoind-v0.13.2-linux-amd64.zip",
|
||||
"version": "0.13.3",
|
||||
"binary_url": "https://github.com/vertcoin-project/vertcoin-core/releases/download/0.13.3/vertcoind-v0.13.3-linux-amd64.zip",
|
||||
"verification_type": "gpg",
|
||||
"verification_source": "https://github.com/vertcoin-project/vertcoin-core/releases/download/0.13.2/vertcoind-v0.13.2-linux-amd64.zip.sig",
|
||||
"verification_source": "https://github.com/vertcoin-project/vertcoin-core/releases/download/0.13.3/vertcoind-v0.13.3-linux-amd64.zip.sig",
|
||||
"extract_command": "unzip -d backend",
|
||||
"exclude_files": [],
|
||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/vertcoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||
|
@ -45,7 +45,7 @@
|
|||
"system_user": "blockbook-vertcoin",
|
||||
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
|
||||
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
|
||||
"explorer_url": "https://insight.vertcoin.org/",
|
||||
"explorer_url": "",
|
||||
"additional_params": "",
|
||||
"block_chain": {
|
||||
"parse": true,
|
||||
|
|
|
@ -22,10 +22,10 @@
|
|||
"package_name": "backend-zcash",
|
||||
"package_revision": "satoshilabs-1",
|
||||
"system_user": "zcash",
|
||||
"version": "1.1.1",
|
||||
"binary_url": "https://z.cash/downloads/zcash-1.1.1-linux64.tar.gz",
|
||||
"version": "2.0.0",
|
||||
"binary_url": "https://z.cash/downloads/zcash-2.0.0-linux64.tar.gz",
|
||||
"verification_type": "gpg",
|
||||
"verification_source": "https://z.cash/downloads/zcash-1.1.1-linux64.tar.gz.asc",
|
||||
"verification_source": "https://z.cash/downloads/zcash-2.0.0-linux64.tar.gz.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"exclude_files": [],
|
||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/zcashd -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||
|
@ -47,7 +47,7 @@
|
|||
"system_user": "blockbook-zcash",
|
||||
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
|
||||
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
|
||||
"explorer_url": "https://zcash.blockexplorer.com/",
|
||||
"explorer_url": "",
|
||||
"additional_params": "",
|
||||
"block_chain": {
|
||||
"parse": true,
|
||||
|
|
|
@ -22,10 +22,10 @@
|
|||
"package_name": "backend-zcash-testnet",
|
||||
"package_revision": "satoshilabs-1",
|
||||
"system_user": "zcash",
|
||||
"version": "1.1.1",
|
||||
"binary_url": "https://z.cash/downloads/zcash-1.1.1-linux64.tar.gz",
|
||||
"version": "2.0.0",
|
||||
"binary_url": "https://z.cash/downloads/zcash-2.0.0-linux64.tar.gz",
|
||||
"verification_type": "gpg",
|
||||
"verification_source": "https://z.cash/downloads/zcash-1.1.1-linux64.tar.gz.asc",
|
||||
"verification_source": "https://z.cash/downloads/zcash-2.0.0-linux64.tar.gz.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"exclude_files": [],
|
||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/zcashd -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||
|
@ -47,7 +47,7 @@
|
|||
"system_user": "blockbook-zcash",
|
||||
"internal_binding_template": ":{{.Ports.BlockbookInternal}}",
|
||||
"public_binding_template": ":{{.Ports.BlockbookPublic}}",
|
||||
"explorer_url": "https://explorer.testnet.z.cash/",
|
||||
"explorer_url": "",
|
||||
"additional_params": "",
|
||||
"block_chain": {
|
||||
"parse": true,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "0.0.6",
|
||||
"version": "0.1.0",
|
||||
"backend_install_path": "/opt/coins/nodes",
|
||||
"backend_data_path": "/opt/coins/data",
|
||||
"blockbook_install_path": "/opt/coins/blockbook",
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -38,7 +38,7 @@ func boolToChar(b bool) C.uchar {
|
|||
}
|
||||
*/
|
||||
|
||||
func createAndSetDBOptions(bloomBits int, c *gorocksdb.Cache) *gorocksdb.Options {
|
||||
func createAndSetDBOptions(bloomBits int, c *gorocksdb.Cache, maxOpenFiles int) *gorocksdb.Options {
|
||||
blockOpts := gorocksdb.NewDefaultBlockBasedTableOptions()
|
||||
blockOpts.SetBlockSize(32 << 10) // 32kB
|
||||
blockOpts.SetBlockCache(c)
|
||||
|
@ -54,7 +54,7 @@ func createAndSetDBOptions(bloomBits int, c *gorocksdb.Cache) *gorocksdb.Options
|
|||
opts.SetBytesPerSync(8 << 20) // 8MB
|
||||
opts.SetWriteBufferSize(1 << 27) // 128MB
|
||||
opts.SetMaxBytesForLevelBase(1 << 27) // 128MB
|
||||
opts.SetMaxOpenFiles(25000)
|
||||
opts.SetMaxOpenFiles(maxOpenFiles)
|
||||
opts.SetCompression(gorocksdb.LZ4HCCompression)
|
||||
return opts
|
||||
}
|
||||
|
|
228
db/rocksdb.go
228
db/rocksdb.go
|
@ -33,17 +33,29 @@ func RepairRocksDB(name string) error {
|
|||
return gorocksdb.RepairDb(name, opts)
|
||||
}
|
||||
|
||||
type connectBlockStats struct {
|
||||
txAddressesHit int
|
||||
txAddressesMiss int
|
||||
balancesHit int
|
||||
balancesMiss int
|
||||
}
|
||||
|
||||
// RocksDB handle
|
||||
type RocksDB struct {
|
||||
path string
|
||||
db *gorocksdb.DB
|
||||
wo *gorocksdb.WriteOptions
|
||||
ro *gorocksdb.ReadOptions
|
||||
cfh []*gorocksdb.ColumnFamilyHandle
|
||||
chainParser bchain.BlockChainParser
|
||||
is *common.InternalState
|
||||
metrics *common.Metrics
|
||||
cache *gorocksdb.Cache
|
||||
path string
|
||||
db *gorocksdb.DB
|
||||
wo *gorocksdb.WriteOptions
|
||||
ro *gorocksdb.ReadOptions
|
||||
cfh []*gorocksdb.ColumnFamilyHandle
|
||||
chainParser bchain.BlockChainParser
|
||||
is *common.InternalState
|
||||
metrics *common.Metrics
|
||||
cache *gorocksdb.Cache
|
||||
maxOpenFiles int
|
||||
cbs connectBlockStats
|
||||
chanUpdateBalance chan updateBalanceData
|
||||
chanUpdateBalanceResult chan error
|
||||
updateBalancesMap map[string]*AddrBalance
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -58,12 +70,12 @@ const (
|
|||
|
||||
var cfNames = []string{"default", "height", "addresses", "txAddresses", "addressBalance", "blockTxs", "transactions"}
|
||||
|
||||
func openDB(path string, c *gorocksdb.Cache) (*gorocksdb.DB, []*gorocksdb.ColumnFamilyHandle, error) {
|
||||
func openDB(path string, c *gorocksdb.Cache, openFiles int) (*gorocksdb.DB, []*gorocksdb.ColumnFamilyHandle, error) {
|
||||
// opts with bloom filter
|
||||
opts := createAndSetDBOptions(10, c)
|
||||
opts := createAndSetDBOptions(10, c, openFiles)
|
||||
// opts for addresses without bloom filter
|
||||
// from documentation: if most of your queries are executed using iterators, you shouldn't set bloom filter
|
||||
optsAddresses := createAndSetDBOptions(0, c)
|
||||
optsAddresses := createAndSetDBOptions(0, c, openFiles)
|
||||
// default, height, addresses, txAddresses, addressBalance, blockTxids, transactions
|
||||
fcOptions := []*gorocksdb.Options{opts, opts, optsAddresses, opts, opts, opts, opts}
|
||||
db, cfh, err := gorocksdb.OpenDbColumnFamilies(opts, path, cfNames, fcOptions)
|
||||
|
@ -75,13 +87,26 @@ func openDB(path string, c *gorocksdb.Cache) (*gorocksdb.DB, []*gorocksdb.Column
|
|||
|
||||
// NewRocksDB opens an internal handle to RocksDB environment. Close
|
||||
// needs to be called to release it.
|
||||
func NewRocksDB(path string, cacheSize int, parser bchain.BlockChainParser, metrics *common.Metrics) (d *RocksDB, err error) {
|
||||
glog.Infof("rocksdb: open %s, version %v", path, dbVersion)
|
||||
func NewRocksDB(path string, cacheSize, maxOpenFiles int, parser bchain.BlockChainParser, metrics *common.Metrics) (d *RocksDB, err error) {
|
||||
glog.Infof("rocksdb: opening %s, required data version %v, cache size %v, max open files %v", path, dbVersion, cacheSize, maxOpenFiles)
|
||||
c := gorocksdb.NewLRUCache(cacheSize)
|
||||
db, cfh, err := openDB(path, c)
|
||||
db, cfh, err := openDB(path, c, maxOpenFiles)
|
||||
wo := gorocksdb.NewDefaultWriteOptions()
|
||||
ro := gorocksdb.NewDefaultReadOptions()
|
||||
return &RocksDB{path, db, wo, ro, cfh, parser, nil, metrics, c}, nil
|
||||
rdb := &RocksDB{
|
||||
path: path,
|
||||
db: db,
|
||||
wo: wo,
|
||||
ro: ro,
|
||||
cfh: cfh,
|
||||
chainParser: parser,
|
||||
metrics: metrics,
|
||||
cache: c,
|
||||
maxOpenFiles: maxOpenFiles,
|
||||
cbs: connectBlockStats{},
|
||||
}
|
||||
rdb.initUpdateBalancesWorker()
|
||||
return rdb, nil
|
||||
}
|
||||
|
||||
func (d *RocksDB) closeDB() error {
|
||||
|
@ -119,7 +144,7 @@ func (d *RocksDB) Reopen() error {
|
|||
return err
|
||||
}
|
||||
d.db = nil
|
||||
db, cfh, err := openDB(d.path, d.cache)
|
||||
db, cfh, err := openDB(d.path, d.cache, d.maxOpenFiles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -265,6 +290,8 @@ func (d *RocksDB) writeBlock(block *bchain.Block, op int) error {
|
|||
txAddressesMap := make(map[string]*TxAddresses)
|
||||
balances := make(map[string]*AddrBalance)
|
||||
if err := d.processAddressesUTXO(block, addresses, txAddressesMap, balances); err != nil {
|
||||
// reinitialize balanceWorker so that there are no left balances in the queue
|
||||
d.initUpdateBalancesWorker()
|
||||
return err
|
||||
}
|
||||
if err := d.storeAddresses(wb, block.Height, addresses); err != nil {
|
||||
|
@ -347,7 +374,86 @@ func (d *RocksDB) resetValueSatToZero(valueSat *big.Int, addrDesc bchain.Address
|
|||
valueSat.SetInt64(0)
|
||||
}
|
||||
|
||||
func (d *RocksDB) GetAndResetConnectBlockStats() string {
|
||||
s := fmt.Sprintf("%+v", d.cbs)
|
||||
d.cbs = connectBlockStats{}
|
||||
return s
|
||||
}
|
||||
|
||||
type updateBalanceData struct {
|
||||
valueSat big.Int
|
||||
strAddrDesc string
|
||||
addrDesc bchain.AddressDescriptor
|
||||
processed, output bool
|
||||
}
|
||||
|
||||
func (d *RocksDB) initUpdateBalancesWorker() {
|
||||
if d.chanUpdateBalance != nil {
|
||||
close(d.chanUpdateBalance)
|
||||
}
|
||||
d.chanUpdateBalance = make(chan updateBalanceData, 16)
|
||||
d.chanUpdateBalanceResult = make(chan error, 16)
|
||||
go d.updateBalancesWorker()
|
||||
}
|
||||
|
||||
// updateBalancesWorker is a single worker used to update balances in parallel to processAddressesUTXO
|
||||
func (d *RocksDB) updateBalancesWorker() {
|
||||
var err error
|
||||
for bd := range d.chanUpdateBalance {
|
||||
ab, e := d.updateBalancesMap[bd.strAddrDesc]
|
||||
if !e {
|
||||
ab, err = d.GetAddrDescBalance(bd.addrDesc)
|
||||
if err != nil {
|
||||
d.chanUpdateBalanceResult <- err
|
||||
continue
|
||||
}
|
||||
if ab == nil {
|
||||
ab = &AddrBalance{}
|
||||
}
|
||||
d.updateBalancesMap[bd.strAddrDesc] = ab
|
||||
d.cbs.balancesMiss++
|
||||
} else {
|
||||
d.cbs.balancesHit++
|
||||
}
|
||||
// add number of trx in balance only once, address can be multiple times in tx
|
||||
if !bd.processed {
|
||||
ab.Txs++
|
||||
}
|
||||
if bd.output {
|
||||
ab.BalanceSat.Add(&ab.BalanceSat, &bd.valueSat)
|
||||
} else {
|
||||
ab.BalanceSat.Sub(&ab.BalanceSat, &bd.valueSat)
|
||||
if ab.BalanceSat.Sign() < 0 {
|
||||
d.resetValueSatToZero(&ab.BalanceSat, bd.addrDesc, "balance")
|
||||
}
|
||||
ab.SentSat.Add(&ab.SentSat, &bd.valueSat)
|
||||
}
|
||||
d.chanUpdateBalanceResult <- nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d *RocksDB) dispatchUpdateBalance(dispatchedBalances int, valueSat *big.Int, strAddrDesc string, addrDesc bchain.AddressDescriptor, processed, output bool) (int, error) {
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
// process as many results as possible
|
||||
case err := <-d.chanUpdateBalanceResult:
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
dispatchedBalances--
|
||||
// send input to be processed
|
||||
case d.chanUpdateBalance <- updateBalanceData{*valueSat, strAddrDesc, addrDesc, processed, output}:
|
||||
dispatchedBalances++
|
||||
break loop
|
||||
}
|
||||
}
|
||||
return dispatchedBalances, nil
|
||||
}
|
||||
|
||||
func (d *RocksDB) processAddressesUTXO(block *bchain.Block, addresses map[string][]outpoint, txAddressesMap map[string]*TxAddresses, balances map[string]*AddrBalance) error {
|
||||
d.updateBalancesMap = balances
|
||||
dispatchedBalances := 0
|
||||
blockTxIDs := make([][]byte, len(block.Txs))
|
||||
blockTxAddresses := make([]*TxAddresses, len(block.Txs))
|
||||
// first process all outputs so that inputs can point to txs in this block
|
||||
|
@ -389,22 +495,10 @@ func (d *RocksDB) processAddressesUTXO(block *bchain.Block, addresses map[string
|
|||
btxID: btxID,
|
||||
index: int32(i),
|
||||
})
|
||||
ab, e := balances[strAddrDesc]
|
||||
if !e {
|
||||
ab, err = d.GetAddrDescBalance(addrDesc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ab == nil {
|
||||
ab = &AddrBalance{}
|
||||
}
|
||||
balances[strAddrDesc] = ab
|
||||
dispatchedBalances, err = d.dispatchUpdateBalance(dispatchedBalances, &output.ValueSat, strAddrDesc, addrDesc, processed, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// add number of trx in balance only once, address can be multiple times in tx
|
||||
if !processed {
|
||||
ab.Txs++
|
||||
}
|
||||
ab.BalanceSat.Add(&ab.BalanceSat, &output.ValueSat)
|
||||
}
|
||||
}
|
||||
// process inputs
|
||||
|
@ -436,6 +530,9 @@ func (d *RocksDB) processAddressesUTXO(block *bchain.Block, addresses map[string
|
|||
continue
|
||||
}
|
||||
txAddressesMap[stxID] = ita
|
||||
d.cbs.txAddressesMiss++
|
||||
} else {
|
||||
d.cbs.txAddressesHit++
|
||||
}
|
||||
if len(ita.Outputs) <= int(input.Vout) {
|
||||
glog.Warningf("rocksdb: height %d, tx %v, input tx %v vout %v is out of bounds of stored tx", block.Height, tx.Txid, input.Txid, input.Vout)
|
||||
|
@ -467,26 +564,16 @@ func (d *RocksDB) processAddressesUTXO(block *bchain.Block, addresses map[string
|
|||
btxID: spendingTxid,
|
||||
index: ^int32(i),
|
||||
})
|
||||
ab, e := balances[strAddrDesc]
|
||||
if !e {
|
||||
ab, err = d.GetAddrDescBalance(ot.AddrDesc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ab == nil {
|
||||
ab = &AddrBalance{}
|
||||
}
|
||||
balances[strAddrDesc] = ab
|
||||
dispatchedBalances, err = d.dispatchUpdateBalance(dispatchedBalances, &ot.ValueSat, strAddrDesc, ot.AddrDesc, processed, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// add number of trx in balance only once, address can be multiple times in tx
|
||||
if !processed {
|
||||
ab.Txs++
|
||||
}
|
||||
ab.BalanceSat.Sub(&ab.BalanceSat, &ot.ValueSat)
|
||||
if ab.BalanceSat.Sign() < 0 {
|
||||
d.resetValueSatToZero(&ab.BalanceSat, ot.AddrDesc, "balance")
|
||||
}
|
||||
ab.SentSat.Add(&ab.SentSat, &ot.ValueSat)
|
||||
}
|
||||
}
|
||||
for i := 0; i < dispatchedBalances; i++ {
|
||||
err := <-d.chanUpdateBalanceResult
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -876,10 +963,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 +1047,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)
|
||||
}
|
||||
|
||||
|
@ -980,8 +1074,10 @@ func (d *RocksDB) writeHeight(wb *gorocksdb.WriteBatch, height uint32, bi *Block
|
|||
return err
|
||||
}
|
||||
wb.PutCF(d.cfh[cfHeight], key, val)
|
||||
d.is.UpdateBestHeight(height)
|
||||
case opDelete:
|
||||
wb.DeleteCF(d.cfh[cfHeight], key)
|
||||
d.is.UpdateBestHeight(height - 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -1214,8 +1310,10 @@ func (d *RocksDB) DisconnectBlockRangeNonUTXO(lower uint32, higher uint32) error
|
|||
func dirSize(path string) (int64, error) {
|
||||
var size int64
|
||||
err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error {
|
||||
if !info.IsDir() {
|
||||
size += info.Size()
|
||||
if err == nil {
|
||||
if !info.IsDir() {
|
||||
size += info.Size()
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
|
@ -1342,6 +1440,12 @@ func (d *RocksDB) LoadInternalState(rpcCoin string) (*common.InternalState, erro
|
|||
}
|
||||
}
|
||||
is.DbColumns = nc
|
||||
// after load, reset the synchronization data
|
||||
is.IsSynchronized = false
|
||||
is.IsMempoolSynchronized = false
|
||||
var t time.Time
|
||||
is.LastMempoolSync = t
|
||||
is.SyncMode = false
|
||||
return is, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ func setupRocksDB(t *testing.T, p bchain.BlockChainParser) *RocksDB {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
d, err := NewRocksDB(tmp, 100000, p, nil)
|
||||
d, err := NewRocksDB(tmp, 100000, -1, p, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -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)
|
||||
|
|
13
db/sync.go
13
db/sync.go
|
@ -47,7 +47,7 @@ var errSynced = errors.New("synced")
|
|||
|
||||
// ResyncIndex synchronizes index to the top of the blockchain
|
||||
// onNewBlock is called when new block is connected, but not in initial parallel sync
|
||||
func (w *SyncWorker) ResyncIndex(onNewBlock func(hash string), initialSync bool) error {
|
||||
func (w *SyncWorker) ResyncIndex(onNewBlock bchain.OnNewBlockFunc, initialSync bool) error {
|
||||
start := time.Now()
|
||||
w.is.StartedSync()
|
||||
|
||||
|
@ -67,6 +67,7 @@ func (w *SyncWorker) ResyncIndex(onNewBlock func(hash string), initialSync bool)
|
|||
case errSynced:
|
||||
// this is not actually error but flag that resync wasn't necessary
|
||||
w.is.FinishedSyncNoChange()
|
||||
w.metrics.IndexDBSize.Set(float64(w.db.DatabaseSizeOnDisk()))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -75,7 +76,7 @@ func (w *SyncWorker) ResyncIndex(onNewBlock func(hash string), initialSync bool)
|
|||
return err
|
||||
}
|
||||
|
||||
func (w *SyncWorker) resyncIndex(onNewBlock func(hash string), initialSync bool) error {
|
||||
func (w *SyncWorker) resyncIndex(onNewBlock bchain.OnNewBlockFunc, initialSync bool) error {
|
||||
remoteBestHash, err := w.chain.GetBestBlockHash()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -135,7 +136,7 @@ func (w *SyncWorker) resyncIndex(onNewBlock func(hash string), initialSync bool)
|
|||
return w.connectBlocks(onNewBlock, initialSync)
|
||||
}
|
||||
|
||||
func (w *SyncWorker) handleFork(localBestHeight uint32, localBestHash string, onNewBlock func(hash string), initialSync bool) error {
|
||||
func (w *SyncWorker) handleFork(localBestHeight uint32, localBestHash string, onNewBlock bchain.OnNewBlockFunc, initialSync bool) error {
|
||||
// find forked blocks, disconnect them and then synchronize again
|
||||
var height uint32
|
||||
hashes := []string{localBestHash}
|
||||
|
@ -163,7 +164,7 @@ func (w *SyncWorker) handleFork(localBestHeight uint32, localBestHash string, on
|
|||
return w.resyncIndex(onNewBlock, initialSync)
|
||||
}
|
||||
|
||||
func (w *SyncWorker) connectBlocks(onNewBlock func(hash string), initialSync bool) error {
|
||||
func (w *SyncWorker) connectBlocks(onNewBlock bchain.OnNewBlockFunc, initialSync bool) error {
|
||||
bch := make(chan blockResult, 8)
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
|
@ -182,7 +183,7 @@ func (w *SyncWorker) connectBlocks(onNewBlock func(hash string), initialSync boo
|
|||
return err
|
||||
}
|
||||
if onNewBlock != nil {
|
||||
onNewBlock(res.block.Hash)
|
||||
onNewBlock(res.block.Hash, res.block.Height)
|
||||
}
|
||||
if res.block.Height > 0 && res.block.Height%1000 == 0 {
|
||||
glog.Info("connected block ", res.block.Height, " ", res.block.Hash)
|
||||
|
@ -333,7 +334,7 @@ ConnectLoop:
|
|||
}
|
||||
hch <- hashHeight{hash, h}
|
||||
if h > 0 && h%1000 == 0 {
|
||||
glog.Info("connecting block ", h, " ", hash, ", elapsed ", time.Since(start))
|
||||
glog.Info("connecting block ", h, " ", hash, ", elapsed ", time.Since(start), " ", w.db.GetAndResetConnectBlockStats())
|
||||
start = time.Now()
|
||||
}
|
||||
if msTime.Before(time.Now()) {
|
||||
|
|
|
@ -2,10 +2,14 @@
|
|||
|
||||
package db
|
||||
|
||||
func ConnectBlocks(w *SyncWorker, onNewBlock func(hash string), initialSync bool) error {
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
)
|
||||
|
||||
func ConnectBlocks(w *SyncWorker, onNewBlock bchain.OnNewBlockFunc, initialSync bool) error {
|
||||
return w.connectBlocks(onNewBlock, initialSync)
|
||||
}
|
||||
|
||||
func HandleFork(w *SyncWorker, localBestHeight uint32, localBestHash string, onNewBlock func(hash string), initialSync bool) error {
|
||||
func HandleFork(w *SyncWorker, localBestHeight uint32, localBestHash string, onNewBlock bchain.OnNewBlockFunc, initialSync bool) error {
|
||||
return w.handleFork(localBestHeight, localBestHash, onNewBlock, initialSync)
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ Good examples of coin configuration are
|
|||
* `system_user` – User used to run Blockbook service. See convention note in [build guide](/docs/build.md#on-naming-conventions-and-versioning).
|
||||
* `internal_binding_template` – Template for *-internal* parameter. See note on templates below.
|
||||
* `public_binding_template` – Template for *-public* parameter. See note on templates below.
|
||||
* `explorer_url` – URL of blockchain explorer.
|
||||
* `explorer_url` – URL of blockchain explorer. Leave empty for internal explorer.
|
||||
* `additional_params` – Additional params of exec command (see [Dogecoin definition](configs/coins/dogecoin.json)).
|
||||
* `block_chain` – Configuration of BlockChain type that ensures communication with back-end service. All options
|
||||
must be tweaked for each individual coin separely.
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"blockbook/api"
|
||||
"blockbook/bchain"
|
||||
"blockbook/common"
|
||||
"blockbook/db"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
|
@ -27,29 +24,21 @@ type InternalServer struct {
|
|||
chain bchain.BlockChain
|
||||
chainParser bchain.BlockChainParser
|
||||
is *common.InternalState
|
||||
}
|
||||
|
||||
type resAboutBlockbookInternal struct {
|
||||
Coin string `json:"coin"`
|
||||
Host string `json:"host"`
|
||||
Version string `json:"version"`
|
||||
GitCommit string `json:"gitcommit"`
|
||||
BuildTime string `json:"buildtime"`
|
||||
InSync bool `json:"inSync"`
|
||||
BestHeight uint32 `json:"bestHeight"`
|
||||
LastBlockTime time.Time `json:"lastBlockTime"`
|
||||
InSyncMempool bool `json:"inSyncMempool"`
|
||||
LastMempoolTime time.Time `json:"lastMempoolTime"`
|
||||
MempoolSize int `json:"mempoolSize"`
|
||||
DbColumns []common.InternalStateColumn `json:"dbColumns"`
|
||||
api *api.Worker
|
||||
}
|
||||
|
||||
// NewInternalServer creates new internal http interface to blockbook and returns its handle
|
||||
func NewInternalServer(httpServerBinding string, certFiles string, db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is *common.InternalState) (*InternalServer, error) {
|
||||
r := mux.NewRouter()
|
||||
func NewInternalServer(binding, certFiles string, db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is *common.InternalState) (*InternalServer, error) {
|
||||
api, err := api.NewWorker(db, chain, txCache, is)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr, path := splitBinding(binding)
|
||||
serveMux := http.NewServeMux()
|
||||
https := &http.Server{
|
||||
Addr: httpServerBinding,
|
||||
Handler: r,
|
||||
Addr: addr,
|
||||
Handler: serveMux,
|
||||
}
|
||||
s := &InternalServer{
|
||||
https: https,
|
||||
|
@ -59,15 +48,12 @@ func NewInternalServer(httpServerBinding string, certFiles string, db *db.RocksD
|
|||
chain: chain,
|
||||
chainParser: chain.GetChainParser(),
|
||||
is: is,
|
||||
api: api,
|
||||
}
|
||||
|
||||
r.HandleFunc("/", s.index)
|
||||
r.HandleFunc("/bestBlockHash", s.bestBlockHash)
|
||||
r.HandleFunc("/blockHash/{height}", s.blockHash)
|
||||
r.HandleFunc("/transactions/{address}/{lower}/{higher}", s.transactions)
|
||||
r.HandleFunc("/confirmedTransactions/{address}/{lower}/{higher}", s.confirmedTransactions)
|
||||
r.HandleFunc("/unconfirmedTransactions/{address}", s.unconfirmedTransactions)
|
||||
r.HandleFunc("/metrics", promhttp.Handler().ServeHTTP)
|
||||
serveMux.Handle(path+"favicon.ico", http.FileServer(http.Dir("./static/")))
|
||||
serveMux.HandleFunc(path+"metrics", promhttp.Handler().ServeHTTP)
|
||||
serveMux.HandleFunc(path, s.index)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
@ -94,142 +80,11 @@ func (s *InternalServer) Shutdown(ctx context.Context) error {
|
|||
return s.https.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func respondError(w http.ResponseWriter, err error, context string) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
glog.Errorf("internal server: (context %s) error: %v", context, err)
|
||||
}
|
||||
|
||||
func respondHashData(w http.ResponseWriter, hash string) {
|
||||
type hashData struct {
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
json.NewEncoder(w).Encode(hashData{
|
||||
Hash: hash,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *InternalServer) index(w http.ResponseWriter, r *http.Request) {
|
||||
vi := common.GetVersionInfo()
|
||||
ss, bh, st := s.is.GetSyncState()
|
||||
ms, mt, msz := s.is.GetMempoolSyncState()
|
||||
a := resAboutBlockbookInternal{
|
||||
Coin: s.is.Coin,
|
||||
Host: s.is.Host,
|
||||
Version: vi.Version,
|
||||
GitCommit: vi.GitCommit,
|
||||
BuildTime: vi.BuildTime,
|
||||
InSync: ss,
|
||||
BestHeight: bh,
|
||||
LastBlockTime: st,
|
||||
InSyncMempool: ms,
|
||||
LastMempoolTime: mt,
|
||||
MempoolSize: msz,
|
||||
DbColumns: s.is.GetAllDBColumnStats(),
|
||||
}
|
||||
buf, err := json.MarshalIndent(a, "", " ")
|
||||
si, err := s.api.GetSystemInfo(true)
|
||||
buf, err := json.MarshalIndent(si, "", " ")
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
func (s *InternalServer) bestBlockHash(w http.ResponseWriter, r *http.Request) {
|
||||
_, hash, err := s.db.GetBestBlock()
|
||||
if err != nil {
|
||||
respondError(w, err, "bestBlockHash")
|
||||
return
|
||||
}
|
||||
respondHashData(w, hash)
|
||||
}
|
||||
|
||||
func (s *InternalServer) blockHash(w http.ResponseWriter, r *http.Request) {
|
||||
heightString := mux.Vars(r)["height"]
|
||||
var hash string
|
||||
height, err := strconv.ParseUint(heightString, 10, 32)
|
||||
if err == nil {
|
||||
hash, err = s.db.GetBlockHash(uint32(height))
|
||||
}
|
||||
if err != nil {
|
||||
respondError(w, err, fmt.Sprintf("blockHash %s", heightString))
|
||||
} else {
|
||||
respondHashData(w, hash)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *InternalServer) getAddress(r *http.Request) (address string, err error) {
|
||||
address, ok := mux.Vars(r)["address"]
|
||||
if !ok {
|
||||
err = errors.New("Empty address")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *InternalServer) getAddressAndHeightRange(r *http.Request) (address string, lower, higher uint32, err error) {
|
||||
address, err = s.getAddress(r)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
higher64, err := strconv.ParseUint(mux.Vars(r)["higher"], 10, 32)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
lower64, err := strconv.ParseUint(mux.Vars(r)["lower"], 10, 32)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return address, uint32(lower64), uint32(higher64), err
|
||||
}
|
||||
|
||||
type transactionList struct {
|
||||
Txid []string `json:"txid"`
|
||||
}
|
||||
|
||||
func (s *InternalServer) unconfirmedTransactions(w http.ResponseWriter, r *http.Request) {
|
||||
address, err := s.getAddress(r)
|
||||
if err != nil {
|
||||
respondError(w, err, fmt.Sprint("unconfirmedTransactions for address", address))
|
||||
}
|
||||
txs, err := s.chain.GetMempoolTransactions(address)
|
||||
if err != nil {
|
||||
respondError(w, err, fmt.Sprint("unconfirmedTransactions for address", address))
|
||||
}
|
||||
txList := transactionList{Txid: txs}
|
||||
json.NewEncoder(w).Encode(txList)
|
||||
}
|
||||
|
||||
func (s *InternalServer) confirmedTransactions(w http.ResponseWriter, r *http.Request) {
|
||||
address, lower, higher, err := s.getAddressAndHeightRange(r)
|
||||
if err != nil {
|
||||
respondError(w, err, fmt.Sprint("confirmedTransactions for address", address))
|
||||
}
|
||||
txList := transactionList{}
|
||||
err = s.db.GetTransactions(address, lower, higher, func(txid string, vout uint32, isOutput bool) error {
|
||||
txList.Txid = append(txList.Txid, txid)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
respondError(w, err, fmt.Sprint("confirmedTransactions for address", address))
|
||||
}
|
||||
json.NewEncoder(w).Encode(txList)
|
||||
}
|
||||
|
||||
func (s *InternalServer) transactions(w http.ResponseWriter, r *http.Request) {
|
||||
address, lower, higher, err := s.getAddressAndHeightRange(r)
|
||||
if err != nil {
|
||||
respondError(w, err, fmt.Sprint("transactions for address", address))
|
||||
}
|
||||
txList := transactionList{}
|
||||
err = s.db.GetTransactions(address, lower, higher, func(txid string, vout uint32, isOutput bool) error {
|
||||
txList.Txid = append(txList.Txid, txid)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
respondError(w, err, fmt.Sprint("transactions for address", address))
|
||||
}
|
||||
txs, err := s.chain.GetMempoolTransactions(address)
|
||||
if err != nil {
|
||||
respondError(w, err, fmt.Sprint("transactions for address", address))
|
||||
}
|
||||
txList.Txid = append(txList.Txid, txs...)
|
||||
json.NewEncoder(w).Encode(txList)
|
||||
}
|
||||
|
|
393
server/public.go
393
server/public.go
|
@ -19,29 +19,31 @@ import (
|
|||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
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
|
||||
type PublicServer struct {
|
||||
binding string
|
||||
certFiles string
|
||||
socketio *SocketIoServer
|
||||
https *http.Server
|
||||
db *db.RocksDB
|
||||
txCache *db.TxCache
|
||||
chain bchain.BlockChain
|
||||
chainParser bchain.BlockChainParser
|
||||
api *api.Worker
|
||||
explorerURL string
|
||||
metrics *common.Metrics
|
||||
is *common.InternalState
|
||||
templates []*template.Template
|
||||
debug bool
|
||||
binding string
|
||||
certFiles string
|
||||
socketio *SocketIoServer
|
||||
https *http.Server
|
||||
db *db.RocksDB
|
||||
txCache *db.TxCache
|
||||
chain bchain.BlockChain
|
||||
chainParser bchain.BlockChainParser
|
||||
api *api.Worker
|
||||
explorerURL string
|
||||
internalExplorer bool
|
||||
metrics *common.Metrics
|
||||
is *common.InternalState
|
||||
templates []*template.Template
|
||||
debug bool
|
||||
}
|
||||
|
||||
// NewPublicServer creates new public server http interface to blockbook and returns its handle
|
||||
// only basic functionality is mapped, to map all functions, call
|
||||
func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, explorerURL string, metrics *common.Metrics, is *common.InternalState, debugMode bool) (*PublicServer, error) {
|
||||
|
||||
api, err := api.NewWorker(db, chain, txCache, is)
|
||||
|
@ -62,43 +64,31 @@ func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bch
|
|||
}
|
||||
|
||||
s := &PublicServer{
|
||||
binding: binding,
|
||||
certFiles: certFiles,
|
||||
https: https,
|
||||
api: api,
|
||||
socketio: socketio,
|
||||
db: db,
|
||||
txCache: txCache,
|
||||
chain: chain,
|
||||
chainParser: chain.GetChainParser(),
|
||||
explorerURL: explorerURL,
|
||||
metrics: metrics,
|
||||
is: is,
|
||||
debug: debugMode,
|
||||
binding: binding,
|
||||
certFiles: certFiles,
|
||||
https: https,
|
||||
api: api,
|
||||
socketio: socketio,
|
||||
db: db,
|
||||
txCache: txCache,
|
||||
chain: chain,
|
||||
chainParser: chain.GetChainParser(),
|
||||
explorerURL: explorerURL,
|
||||
internalExplorer: explorerURL == "",
|
||||
metrics: metrics,
|
||||
is: is,
|
||||
debug: debugMode,
|
||||
}
|
||||
|
||||
// favicon
|
||||
serveMux.Handle(path+"favicon.ico", http.FileServer(http.Dir("./static/")))
|
||||
// support for tests of socket.io interface
|
||||
serveMux.Handle(path+"test.html", http.FileServer(http.Dir("./static/")))
|
||||
// redirect to wallet requests for tx and address, possibly to external site
|
||||
serveMux.HandleFunc(path+"tx/", s.txRedirect)
|
||||
serveMux.HandleFunc(path+"address/", s.addressRedirect)
|
||||
// explorer
|
||||
serveMux.HandleFunc(path+"explorer/tx/", s.htmlTemplateHandler(s.explorerTx))
|
||||
serveMux.HandleFunc(path+"explorer/address/", s.htmlTemplateHandler(s.explorerAddress))
|
||||
serveMux.Handle(path+"static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
|
||||
// API calls
|
||||
serveMux.HandleFunc(path+"api/block-index/", s.jsonHandler(s.apiBlockIndex))
|
||||
serveMux.HandleFunc(path+"api/tx/", s.jsonHandler(s.apiTx))
|
||||
serveMux.HandleFunc(path+"api/address/", s.jsonHandler(s.apiAddress))
|
||||
// handle socket.io
|
||||
serveMux.Handle(path+"socket.io/", socketio.GetHandler())
|
||||
// default handler
|
||||
serveMux.HandleFunc(path, s.index)
|
||||
|
||||
s.templates = parseTemplates()
|
||||
|
||||
// map only basic functions, the rest is enabled by method MapFullPublicInterface
|
||||
serveMux.Handle(path+"favicon.ico", http.FileServer(http.Dir("./static/")))
|
||||
serveMux.Handle(path+"static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
|
||||
// default handler
|
||||
serveMux.HandleFunc(path, s.htmlTemplateHandler(s.explorerIndex))
|
||||
// default API handler
|
||||
serveMux.HandleFunc(path+"api/", s.jsonHandler(s.apiIndex))
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
|
@ -112,6 +102,34 @@ func (s *PublicServer) Run() error {
|
|||
return s.https.ListenAndServeTLS(fmt.Sprint(s.certFiles, ".crt"), fmt.Sprint(s.certFiles, ".key"))
|
||||
}
|
||||
|
||||
// ConnectFullPublicInterface enables complete public functionality
|
||||
func (s *PublicServer) ConnectFullPublicInterface() {
|
||||
serveMux := s.https.Handler.(*http.ServeMux)
|
||||
_, path := splitBinding(s.binding)
|
||||
// support for tests of socket.io interface
|
||||
serveMux.Handle(path+"test.html", http.FileServer(http.Dir("./static/")))
|
||||
if s.internalExplorer {
|
||||
// internal explorer handlers
|
||||
serveMux.HandleFunc(path+"tx/", s.htmlTemplateHandler(s.explorerTx))
|
||||
serveMux.HandleFunc(path+"address/", s.htmlTemplateHandler(s.explorerAddress))
|
||||
serveMux.HandleFunc(path+"search/", s.htmlTemplateHandler(s.explorerSearch))
|
||||
serveMux.HandleFunc(path+"blocks", s.htmlTemplateHandler(s.explorerBlocks))
|
||||
serveMux.HandleFunc(path+"block/", s.htmlTemplateHandler(s.explorerBlock))
|
||||
serveMux.HandleFunc(path+"spending/", s.htmlTemplateHandler(s.explorerSpendingTx))
|
||||
} else {
|
||||
// redirect to wallet requests for tx and address, possibly to external site
|
||||
serveMux.HandleFunc(path+"tx/", s.txRedirect)
|
||||
serveMux.HandleFunc(path+"address/", s.addressRedirect)
|
||||
}
|
||||
// API calls
|
||||
serveMux.HandleFunc(path+"api/block-index/", s.jsonHandler(s.apiBlockIndex))
|
||||
serveMux.HandleFunc(path+"api/tx/", s.jsonHandler(s.apiTx))
|
||||
serveMux.HandleFunc(path+"api/address/", s.jsonHandler(s.apiAddress))
|
||||
serveMux.HandleFunc(path+"api/block/", s.jsonHandler(s.apiBlock))
|
||||
// socket.io interface
|
||||
serveMux.Handle(path+"socket.io/", s.socketio.GetHandler())
|
||||
}
|
||||
|
||||
// Close closes the server
|
||||
func (s *PublicServer) Close() error {
|
||||
glog.Infof("public server: closing")
|
||||
|
@ -124,28 +142,24 @@ func (s *PublicServer) Shutdown(ctx context.Context) error {
|
|||
return s.https.Shutdown(ctx)
|
||||
}
|
||||
|
||||
// OnNewBlockHash notifies users subscribed to bitcoind/hashblock about new block
|
||||
func (s *PublicServer) OnNewBlockHash(hash string) {
|
||||
// OnNewBlock notifies users subscribed to bitcoind/hashblock about new block
|
||||
func (s *PublicServer) OnNewBlock(hash string, height uint32) {
|
||||
s.socketio.OnNewBlockHash(hash)
|
||||
}
|
||||
|
||||
// OnNewTxAddr notifies users subscribed to bitcoind/addresstxid about new block
|
||||
func (s *PublicServer) OnNewTxAddr(txid string, addr string) {
|
||||
s.socketio.OnNewTxAddr(txid, addr)
|
||||
func (s *PublicServer) OnNewTxAddr(txid string, addr string, isOutput bool) {
|
||||
s.socketio.OnNewTxAddr(txid, addr, isOutput)
|
||||
}
|
||||
|
||||
func (s *PublicServer) txRedirect(w http.ResponseWriter, r *http.Request) {
|
||||
if s.explorerURL != "" {
|
||||
http.Redirect(w, r, joinURL(s.explorerURL, r.URL.Path), 302)
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "tx"}).Inc()
|
||||
}
|
||||
http.Redirect(w, r, joinURL(s.explorerURL, r.URL.Path), 302)
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "tx-redirect"}).Inc()
|
||||
}
|
||||
|
||||
func (s *PublicServer) addressRedirect(w http.ResponseWriter, r *http.Request) {
|
||||
if s.explorerURL != "" {
|
||||
http.Redirect(w, r, joinURL(s.explorerURL, r.URL.Path), 302)
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "address"}).Inc()
|
||||
}
|
||||
http.Redirect(w, r, joinURL(s.explorerURL, r.URL.Path), 302)
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "address-redirect"}).Inc()
|
||||
}
|
||||
|
||||
func splitBinding(binding string) (addr string, path string) {
|
||||
|
@ -187,6 +201,9 @@ func (s *PublicServer) jsonHandler(handler func(r *http.Request) (interface{}, e
|
|||
}
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
if _, isError := data.(jsonError); isError {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
json.NewEncoder(w).Encode(data)
|
||||
}()
|
||||
data, err = handler(r)
|
||||
|
@ -209,19 +226,19 @@ func (s *PublicServer) jsonHandler(handler func(r *http.Request) (interface{}, e
|
|||
|
||||
func (s *PublicServer) newTemplateData() *TemplateData {
|
||||
return &TemplateData{
|
||||
CoinName: s.is.Coin,
|
||||
CoinShortcut: s.is.CoinShortcut,
|
||||
CoinName: s.is.Coin,
|
||||
CoinShortcut: s.is.CoinShortcut,
|
||||
InternalExplorer: s.internalExplorer && !s.is.InitialSync,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PublicServer) newTemplateDataWithError(text string) *TemplateData {
|
||||
return &TemplateData{
|
||||
CoinName: s.is.Coin,
|
||||
CoinShortcut: s.is.CoinShortcut,
|
||||
Error: &api.ApiError{Text: text},
|
||||
}
|
||||
td := s.newTemplateData()
|
||||
td.Error = &api.ApiError{Text: text}
|
||||
return td
|
||||
}
|
||||
func (s *PublicServer) htmlTemplateHandler(handler func(r *http.Request) (tpl, *TemplateData, error)) func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (s *PublicServer) htmlTemplateHandler(handler func(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error)) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var t tpl
|
||||
var data *TemplateData
|
||||
|
@ -229,16 +246,23 @@ func (s *PublicServer) htmlTemplateHandler(handler func(r *http.Request) (tpl, *
|
|||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
glog.Error(getFunctionName(handler), " recovered from panic: ", e)
|
||||
t = errorTpl
|
||||
t = errorInternalTpl
|
||||
if s.debug {
|
||||
data = s.newTemplateDataWithError(fmt.Sprint("Internal server error: recovered from panic ", e))
|
||||
} else {
|
||||
data = s.newTemplateDataWithError("Internal server error")
|
||||
}
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if err := s.templates[t].ExecuteTemplate(w, "base.html", data); err != nil {
|
||||
glog.Error(err)
|
||||
// noTpl means the handler completely handled the request
|
||||
if t != noTpl {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
// return 500 Internal Server Error with errorInternalTpl
|
||||
if t == errorInternalTpl {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
if err := s.templates[t].ExecuteTemplate(w, "base.html", data); err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
if s.debug {
|
||||
|
@ -246,12 +270,15 @@ func (s *PublicServer) htmlTemplateHandler(handler func(r *http.Request) (tpl, *
|
|||
// to reflect changes during development
|
||||
s.templates = parseTemplates()
|
||||
}
|
||||
t, data, err = handler(r)
|
||||
if err != nil || data == nil {
|
||||
t = errorTpl
|
||||
t, data, err = handler(w, r)
|
||||
if err != nil || (data == nil && t != noTpl) {
|
||||
t = errorInternalTpl
|
||||
if apiErr, ok := err.(*api.ApiError); ok {
|
||||
data = s.newTemplateData()
|
||||
data.Error = apiErr
|
||||
if apiErr.Public {
|
||||
t = errorTpl
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
glog.Error(getFunctionName(handler), " error: ", err)
|
||||
|
@ -269,28 +296,38 @@ func (s *PublicServer) htmlTemplateHandler(handler func(r *http.Request) (tpl, *
|
|||
type tpl int
|
||||
|
||||
const (
|
||||
errorTpl = tpl(iota)
|
||||
noTpl = tpl(iota)
|
||||
errorTpl
|
||||
errorInternalTpl
|
||||
indexTpl
|
||||
txTpl
|
||||
addressTpl
|
||||
blocksTpl
|
||||
blockTpl
|
||||
|
||||
tplCount
|
||||
)
|
||||
|
||||
type TemplateData struct {
|
||||
CoinName string
|
||||
CoinShortcut string
|
||||
Address *api.Address
|
||||
AddrStr string
|
||||
Tx *api.Tx
|
||||
Error *api.ApiError
|
||||
Page int
|
||||
PrevPage int
|
||||
NextPage int
|
||||
PagingRange []int
|
||||
CoinName string
|
||||
CoinShortcut string
|
||||
InternalExplorer bool
|
||||
Address *api.Address
|
||||
AddrStr string
|
||||
Tx *api.Tx
|
||||
Error *api.ApiError
|
||||
Blocks *api.Blocks
|
||||
Block *api.Block
|
||||
Info *api.SystemInfo
|
||||
Page int
|
||||
PrevPage int
|
||||
NextPage int
|
||||
PagingRange []int
|
||||
}
|
||||
|
||||
func parseTemplates() []*template.Template {
|
||||
templateFuncMap := template.FuncMap{
|
||||
"formatTime": formatTime,
|
||||
"formatUnixTime": formatUnixTime,
|
||||
"formatAmount": formatAmount,
|
||||
"setTxToTemplateData": setTxToTemplateData,
|
||||
|
@ -298,13 +335,21 @@ func parseTemplates() []*template.Template {
|
|||
}
|
||||
t := make([]*template.Template, tplCount)
|
||||
t[errorTpl] = template.Must(template.New("error").Funcs(templateFuncMap).ParseFiles("./static/templates/error.html", "./static/templates/base.html"))
|
||||
t[errorInternalTpl] = template.Must(template.New("error").Funcs(templateFuncMap).ParseFiles("./static/templates/error.html", "./static/templates/base.html"))
|
||||
t[indexTpl] = template.Must(template.New("index").Funcs(templateFuncMap).ParseFiles("./static/templates/index.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"))
|
||||
t[blockTpl] = template.Must(template.New("block").Funcs(templateFuncMap).ParseFiles("./static/templates/block.html", "./static/templates/txdetail.html", "./static/templates/paging.html", "./static/templates/base.html"))
|
||||
return t
|
||||
}
|
||||
|
||||
func formatUnixTime(ut int64) string {
|
||||
return time.Unix(ut, 0).Format(time.RFC1123)
|
||||
return formatTime(time.Unix(ut, 0))
|
||||
}
|
||||
|
||||
func formatTime(t time.Time) string {
|
||||
return t.Format(time.RFC1123)
|
||||
}
|
||||
|
||||
// for now return the string as it is
|
||||
|
@ -319,13 +364,14 @@ func setTxToTemplateData(td *TemplateData, tx *api.Tx) *TemplateData {
|
|||
return td
|
||||
}
|
||||
|
||||
func (s *PublicServer) explorerTx(r *http.Request) (tpl, *TemplateData, error) {
|
||||
func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
|
||||
var tx *api.Tx
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "tx"}).Inc()
|
||||
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||
txid := r.URL.Path[i+1:]
|
||||
bestheight, _, err := s.db.GetBestBlock()
|
||||
if err == nil {
|
||||
tx, err = s.api.GetTransaction(txid, bestheight, true)
|
||||
tx, err = s.api.GetTransaction(txid, bestheight, false)
|
||||
}
|
||||
if err != nil {
|
||||
return errorTpl, nil, err
|
||||
|
@ -336,9 +382,31 @@ func (s *PublicServer) explorerTx(r *http.Request) (tpl, *TemplateData, error) {
|
|||
return txTpl, data, nil
|
||||
}
|
||||
|
||||
func (s *PublicServer) explorerAddress(r *http.Request) (tpl, *TemplateData, error) {
|
||||
func (s *PublicServer) explorerSpendingTx(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "spendingtx"}).Inc()
|
||||
var err error
|
||||
parts := strings.Split(r.URL.Path, "/")
|
||||
if len(parts) > 2 {
|
||||
tx := parts[len(parts)-2]
|
||||
n, ec := strconv.Atoi(parts[len(parts)-1])
|
||||
if ec == nil {
|
||||
spendingTx, err := s.api.GetSpendingTxid(tx, n)
|
||||
if err == nil && spendingTx != "" {
|
||||
http.Redirect(w, r, joinURL("/tx/", spendingTx), 302)
|
||||
return noTpl, nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
err = api.NewApiError("Transaction not found", true)
|
||||
}
|
||||
return errorTpl, nil, err
|
||||
}
|
||||
|
||||
func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
|
||||
var address *api.Address
|
||||
var err error
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "address"}).Inc()
|
||||
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||
page, ec := strconv.Atoi(r.URL.Query().Get("page"))
|
||||
if ec != nil {
|
||||
|
@ -357,6 +425,90 @@ func (s *PublicServer) explorerAddress(r *http.Request) (tpl, *TemplateData, err
|
|||
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) explorerBlock(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
|
||||
var block *api.Block
|
||||
var err error
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "block"}).Inc()
|
||||
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||
page, ec := strconv.Atoi(r.URL.Query().Get("page"))
|
||||
if ec != nil {
|
||||
page = 0
|
||||
}
|
||||
block, err = s.api.GetBlock(r.URL.Path[i+1:], page, txsOnPage)
|
||||
if err != nil {
|
||||
return errorTpl, nil, err
|
||||
}
|
||||
}
|
||||
data := s.newTemplateData()
|
||||
data.Block = block
|
||||
data.Page = block.Page
|
||||
data.PagingRange, data.PrevPage, data.NextPage = getPagingRange(block.Page, block.TotalPages)
|
||||
return blockTpl, data, nil
|
||||
}
|
||||
|
||||
func (s *PublicServer) explorerIndex(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
|
||||
var si *api.SystemInfo
|
||||
var err error
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "index"}).Inc()
|
||||
si, err = s.api.GetSystemInfo(false)
|
||||
if err != nil {
|
||||
return errorTpl, nil, err
|
||||
}
|
||||
data := s.newTemplateData()
|
||||
data.Info = si
|
||||
return indexTpl, 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
|
||||
var address *api.Address
|
||||
var block *api.Block
|
||||
var bestheight uint32
|
||||
var err error
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "search"}).Inc()
|
||||
if len(q) > 0 {
|
||||
block, err = s.api.GetBlock(q, 0, 1)
|
||||
if err == nil {
|
||||
http.Redirect(w, r, joinURL("/block/", block.Hash), 302)
|
||||
return noTpl, nil, nil
|
||||
}
|
||||
bestheight, _, err = s.db.GetBestBlock()
|
||||
if err == nil {
|
||||
tx, err = s.api.GetTransaction(q, bestheight, false)
|
||||
if err == nil {
|
||||
http.Redirect(w, r, joinURL("/tx/", tx.Txid), 302)
|
||||
return noTpl, nil, nil
|
||||
}
|
||||
}
|
||||
address, err = s.api.GetAddress(q, 0, 1, true)
|
||||
if err == nil {
|
||||
http.Redirect(w, r, joinURL("/address/", address.AddrStr), 302)
|
||||
return noTpl, nil, nil
|
||||
}
|
||||
}
|
||||
return errorTpl, nil, api.NewApiError(fmt.Sprintf("No matching records found for '%v'", q), true)
|
||||
}
|
||||
|
||||
func getPagingRange(page int, total int) ([]int, int, int) {
|
||||
if total < 2 {
|
||||
return nil, 0, 0
|
||||
|
@ -411,50 +563,14 @@ func getPagingRange(page int, total int) ([]int, int, int) {
|
|||
return r, pp, np
|
||||
}
|
||||
|
||||
type resAboutBlockbookPublic struct {
|
||||
Coin string `json:"coin"`
|
||||
Host string `json:"host"`
|
||||
Version string `json:"version"`
|
||||
GitCommit string `json:"gitcommit"`
|
||||
BuildTime string `json:"buildtime"`
|
||||
InSync bool `json:"inSync"`
|
||||
BestHeight uint32 `json:"bestHeight"`
|
||||
LastBlockTime time.Time `json:"lastBlockTime"`
|
||||
InSyncMempool bool `json:"inSyncMempool"`
|
||||
LastMempoolTime time.Time `json:"lastMempoolTime"`
|
||||
About string `json:"about"`
|
||||
}
|
||||
|
||||
// TODO - this is temporary, return html status page
|
||||
func (s *PublicServer) index(w http.ResponseWriter, r *http.Request) {
|
||||
vi := common.GetVersionInfo()
|
||||
ss, bh, st := s.is.GetSyncState()
|
||||
ms, mt, _ := s.is.GetMempoolSyncState()
|
||||
a := resAboutBlockbookPublic{
|
||||
Coin: s.is.Coin,
|
||||
Host: s.is.Host,
|
||||
Version: vi.Version,
|
||||
GitCommit: vi.GitCommit,
|
||||
BuildTime: vi.BuildTime,
|
||||
InSync: ss,
|
||||
BestHeight: bh,
|
||||
LastBlockTime: st,
|
||||
InSyncMempool: ms,
|
||||
LastMempoolTime: mt,
|
||||
About: blockbookAbout,
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
buf, err := json.MarshalIndent(a, "", " ")
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
w.Write(buf)
|
||||
func (s *PublicServer) apiIndex(r *http.Request) (interface{}, error) {
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-index"}).Inc()
|
||||
return s.api.GetSystemInfo(false)
|
||||
}
|
||||
|
||||
func (s *PublicServer) apiBlockIndex(r *http.Request) (interface{}, error) {
|
||||
type resBlockIndex struct {
|
||||
BlockHash string `json:"blockHash"`
|
||||
About string `json:"about"`
|
||||
}
|
||||
var err error
|
||||
var hash string
|
||||
|
@ -475,13 +591,13 @@ func (s *PublicServer) apiBlockIndex(r *http.Request) (interface{}, error) {
|
|||
}
|
||||
return resBlockIndex{
|
||||
BlockHash: hash,
|
||||
About: blockbookAbout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *PublicServer) apiTx(r *http.Request) (interface{}, error) {
|
||||
var tx *api.Tx
|
||||
var err error
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-tx"}).Inc()
|
||||
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||
txid := r.URL.Path[i+1:]
|
||||
bestheight, _, err := s.db.GetBestBlock()
|
||||
|
@ -495,6 +611,7 @@ func (s *PublicServer) apiTx(r *http.Request) (interface{}, error) {
|
|||
func (s *PublicServer) apiAddress(r *http.Request) (interface{}, error) {
|
||||
var address *api.Address
|
||||
var err error
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-address"}).Inc()
|
||||
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||
page, ec := strconv.Atoi(r.URL.Query().Get("page"))
|
||||
if ec != nil {
|
||||
|
@ -504,3 +621,17 @@ func (s *PublicServer) apiAddress(r *http.Request) (interface{}, error) {
|
|||
}
|
||||
return address, err
|
||||
}
|
||||
|
||||
func (s *PublicServer) apiBlock(r *http.Request) (interface{}, error) {
|
||||
var block *api.Block
|
||||
var err error
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-block"}).Inc()
|
||||
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||
page, ec := strconv.Atoi(r.URL.Query().Get("page"))
|
||||
if ec != nil {
|
||||
page = 0
|
||||
}
|
||||
block, err = s.api.GetBlock(r.URL.Path[i+1:], page, txsInAPI)
|
||||
}
|
||||
return block, err
|
||||
}
|
||||
|
|
|
@ -551,7 +551,7 @@ func (s *SocketIoServer) getInfo() (res resultGetInfo, err error) {
|
|||
res.Result.Network = s.chain.GetNetworkName()
|
||||
res.Result.Subversion = s.chain.GetSubversion()
|
||||
res.Result.CoinName = s.chain.GetCoinName()
|
||||
res.Result.About = blockbookAbout
|
||||
res.Result.About = api.BlockbookAbout
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -748,8 +748,12 @@ func (s *SocketIoServer) OnNewBlockHash(hash string) {
|
|||
}
|
||||
|
||||
// OnNewTxAddr notifies users subscribed to bitcoind/addresstxid about new block
|
||||
func (s *SocketIoServer) OnNewTxAddr(txid string, addr string) {
|
||||
c := s.server.BroadcastTo("bitcoind/addresstxid-"+addr, "bitcoind/addresstxid", map[string]string{"address": addr, "txid": txid})
|
||||
func (s *SocketIoServer) OnNewTxAddr(txid string, addr string, isOutput bool) {
|
||||
data := map[string]interface{}{"address": addr, "txid": txid}
|
||||
if !isOutput {
|
||||
data["input"] = true
|
||||
}
|
||||
c := s.server.BroadcastTo("bitcoind/addresstxid-"+addr, "bitcoind/addresstxid", data)
|
||||
if c > 0 {
|
||||
glog.Info("broadcasting new txid ", txid, " for addr ", addr, " to ", c, " channels")
|
||||
}
|
||||
|
|
|
@ -31,6 +31,22 @@ h3 {
|
|||
height: 16px;
|
||||
}
|
||||
|
||||
.navbar-form {
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
.navbar-form .form-control {
|
||||
background-color: gray;
|
||||
color: #fff;
|
||||
border-radius: 3px;
|
||||
-webkit-border-radius: 3px;
|
||||
-moz-border-radius: 3px;
|
||||
border: 0;
|
||||
-webkit-box-shadow: 1px 1px 0 0 rgba(255, 255, 255, .41), inset 1px 1px 3px 0 rgba(0, 0, 0, .10);
|
||||
-moz-box-shadow: 1px 1px 0 0 rgba(255, 255, 255, .41), inset 1px 1px 3px 0 rgba(0, 0, 0, .10);
|
||||
box-shadow: 1px 1px 0 0 rgba(255, 255, 255, .41), inset 1px 1px 3px 0 rgba(0, 0, 0, .10);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
max-width: 750px;
|
||||
|
@ -47,6 +63,9 @@ h3 {
|
|||
.octicon {
|
||||
height: 24px;
|
||||
}
|
||||
.navbar-form .form-control {
|
||||
width: 230px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
|
@ -59,6 +78,9 @@ h3 {
|
|||
.octicon {
|
||||
height: 32px;
|
||||
}
|
||||
.navbar-form .form-control {
|
||||
width: 360px;
|
||||
}
|
||||
}
|
||||
|
||||
#header {
|
||||
|
@ -83,7 +105,6 @@ h3 {
|
|||
color: rgba(255, 255, 255);
|
||||
font-weight: bold;
|
||||
font-size: 19px;
|
||||
padding-left: 35px;
|
||||
}
|
||||
|
||||
.trezor-logo-svg-white svg {
|
||||
|
@ -114,7 +135,7 @@ h3 {
|
|||
|
||||
.line-top {
|
||||
border-top: 1px solid #EAEAEA;
|
||||
padding: 15px 0 0;
|
||||
padding: 10px 0 0;
|
||||
}
|
||||
|
||||
.line-mid {
|
||||
|
@ -136,6 +157,11 @@ h3 {
|
|||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
border-radius: .25em;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.txvalues:not(:last-child) {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.txvalues-default {
|
||||
|
@ -157,14 +183,17 @@ h3 {
|
|||
}
|
||||
|
||||
.ellipsis {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.data-div {
|
||||
margin: 20px 0;
|
||||
margin: 20px 0 30px 0;
|
||||
}
|
||||
|
||||
.data-div .col-md-10 {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
|
@ -194,6 +223,22 @@ h3 {
|
|||
padding-left: .25rem;
|
||||
}
|
||||
|
||||
.navbar-text .nav-link {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::-webkit-input-placeholder {
|
||||
color: #CCC!important;
|
||||
font-style: italic;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
::-moz-placeholder {
|
||||
color: #CCC!important;
|
||||
font-style: italic;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.h-container {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -2,31 +2,40 @@
|
|||
<h1>Address
|
||||
<small class="text-muted">{{formatAmount $addr.Balance}} {{$cs}}</small>
|
||||
</h1>
|
||||
<div class="alert alert-data">
|
||||
<span class="ellipsis data">{{$addr.AddrStr}}</span>
|
||||
<div class="alert alert-data ellipsis">
|
||||
<span class="data">{{$addr.AddrStr}}</span>
|
||||
</div>
|
||||
<h3>Confirmed</h3>
|
||||
<div class="data-div">
|
||||
<table class="table data-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 25%;">Total Received</td>
|
||||
<td class="data">{{formatAmount $addr.TotalReceived}} {{$cs}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total Sent</td>
|
||||
<td class="data">{{formatAmount $addr.TotalSent}} {{$cs}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Final Balance</td>
|
||||
<td class="data">{{formatAmount $addr.Balance}} {{$cs}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>No. Transactions</td>
|
||||
<td class="data">{{$addr.TxApperances}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="data-div row">
|
||||
<div class="col-md-10">
|
||||
<table class="table data-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 25%;">Total Received</td>
|
||||
<td class="data">{{formatAmount $addr.TotalReceived}} {{$cs}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Total Sent</td>
|
||||
<td class="data">{{formatAmount $addr.TotalSent}} {{$cs}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Final Balance</td>
|
||||
<td class="data">{{formatAmount $addr.Balance}} {{$cs}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>No. Transactions</td>
|
||||
<td class="data">{{$addr.TxApperances}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<div id="qrcode" style="width: 160px; height: 160px;"></div>
|
||||
<script type="text/javascript" src="/static/js/qrcode.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
new QRCode(document.getElementById("qrcode"), { text: "{{$addr.AddrStr}}", width: 160, height: 160 });
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
{{- if $addr.UnconfirmedTxApperances -}}
|
||||
<h3>Unconfirmed</h3>
|
||||
|
|
|
@ -14,9 +14,8 @@
|
|||
<body>
|
||||
<header id="header">
|
||||
<div class="container">
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-trezor">
|
||||
<!-- <a class="navbar-brand" href="#">Navbar w/ text</a> -->
|
||||
<a class="navbar-brand" href="https://trezor.io/" title="Home">
|
||||
<nav class="navbar navbar-expand navbar-dark bg-trezor">
|
||||
<a class="navbar-brand" href="/" title="Home">
|
||||
<div alt="TREZOR Wallet" class="trezor-logo-svg-white">
|
||||
<svg width="100" height="42" version="1.1" id="logotyp" xmlns="http://www.w3.org/2000/svg" xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 163.7 41.9" space="preserve">
|
||||
<polygon points="101.1,12.8 118.2,12.8 118.2,17.3 108.9,29.9 118.2,29.9 118.2,35.2 101.1,35.2 101.1,30.7 110.4,18.1 101.1,18.1"></polygon>
|
||||
|
@ -29,10 +28,25 @@
|
|||
</svg>
|
||||
</div>
|
||||
</a>
|
||||
<span class="navbar-text">
|
||||
{{.CoinName}} Explorer
|
||||
<span class="navbar-text ml-md-auto">
|
||||
<a href="/" class="nav-link">{{.CoinName}} Explorer</a>
|
||||
</span>
|
||||
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
|
||||
{{- if .InternalExplorer -}}
|
||||
<ul class="navbar-nav flex-row ml-md-auto d-none d-lg-flex">
|
||||
<li class="nav-item">
|
||||
<a href="/blocks" class="nav-link">Blocks</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/" class="nav-link">Status</a>
|
||||
</li>
|
||||
</ul>
|
||||
<span class="d-none ml-md-auto d-md-flex navbar-form navbar-left">
|
||||
<form id="search" action="/search" method="get">
|
||||
<input name="q" type="text" class="form-control" placeholder="Search for block, transaction or address" focus="true">
|
||||
</form>
|
||||
</span>
|
||||
{{- end -}}
|
||||
<ul class="navbar-nav flex-row ml-md-auto d-none d-lg-flex ">
|
||||
<li class="nav-item">
|
||||
<a href="https://trezor.io/" class="nav-link">TREZOR</a>
|
||||
</li>
|
||||
|
@ -48,7 +62,7 @@
|
|||
</header>
|
||||
<main id="wrap">
|
||||
<div class="container">
|
||||
{{template "specific" .}}
|
||||
{{- template "specific" . -}}
|
||||
</div>
|
||||
</main>
|
||||
<footer id="footer" class="footer">
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
{{define "specific"}}{{$cs := .CoinShortcut}}{{$b := .Block}}{{$data := . -}}
|
||||
<h1>Block {{$b.Height}}</h1>
|
||||
<div class="alert alert-data ellipsis">
|
||||
<span class="data">{{$b.Hash}}</span>
|
||||
</div>
|
||||
<div class="h-container">
|
||||
<h3 class="h-container-6">Summary</h3>
|
||||
<nav class="h-container-6">
|
||||
<ul class="pagination justify-content-end">
|
||||
<li class="page-item">{{if $b.Prev}}<a class="page-link" href="/block/{{$b.Prev}}">Previous Block</a>{{else}}<span class="page-link text-muted disabled">Previous Block</span>{{end}}</li>
|
||||
<li class="page-item">{{if $b.Next}}<a class="page-link" href="/block/{{$b.Next}}">Next Block</a>{{else}}<span class="page-link text-muted disabled">Next Block</span>{{end}}</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="data-div row">
|
||||
<div class="col-md-6">
|
||||
<table class="table data-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 25%;">Transactions</td>
|
||||
<td class="data">{{$b.TxCount}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Height</td>
|
||||
<td class="data">{{$b.Height}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Confirmations</td>
|
||||
<td class="data">{{$b.Confirmations}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Timestamp</td>
|
||||
<td class="data">{{formatUnixTime $b.Time}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Size (bytes)</td>
|
||||
<td class="data">{{$b.Size}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<table class="table data-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 25%;">Version</td>
|
||||
<td class="data ellipsis">{{$b.Version}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Merkle Root</td>
|
||||
<td class="data ellipsis">{{$b.MerkleRoot}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nonce</td>
|
||||
<td class="data ellipsis">{{$b.Nonce}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Bits</td>
|
||||
<td class="data ellipsis">{{$b.Bits}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Difficulty</td>
|
||||
<td class="data ellipsis">{{$b.Difficulty}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{{- if $b.Transactions -}}
|
||||
<div class="h-container">
|
||||
<h3 class="h-container-6">Transactions</h3>
|
||||
<nav class="h-container-6">{{template "paging" $data}}</nav>
|
||||
</div>
|
||||
<div class="data-div">
|
||||
{{- range $tx := $b.Transactions}}{{$data := setTxToTemplateData $data $tx}}{{template "txdetail" $data}}{{end -}}
|
||||
</div>
|
||||
<nav>{{template "paging" $data }}</nav>
|
||||
{{end}}{{end}}
|
|
@ -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 style="width: 20%;">Height</th>
|
||||
<th>Timestamp</span></th>
|
||||
<th class="text-right" style="width: 20%;">Transactions</th>
|
||||
<th class="text-right" style="width: 20%;">Size</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{- range $b := $blocks.Blocks -}}
|
||||
<tr>
|
||||
<td><a href="/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}}
|
|
@ -0,0 +1,102 @@
|
|||
{{define "specific"}}{{$cs := .CoinShortcut}}{{$bb := .Info.Blockbook}}{{$be := .Info.Backend}}
|
||||
<h1>Application status</h1>
|
||||
{{- if $bb.InitialSync -}}
|
||||
<h3 class="bg-danger text-white" style="padding: 20px;">Application is now in initial synchronization and does not provide any data.</h3>
|
||||
{{- end -}}
|
||||
{{- if not $bb.SyncMode -}}
|
||||
<h3 class="bg-warning text-white" style="padding: 20px;">Synchronization with backend is disabled, the state of index is not up to date.</h3>
|
||||
{{- end -}}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h3>Blockbook</h3>
|
||||
<table class="table data-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 33%;">Coin</td>
|
||||
<td class="data">{{$bb.Coin}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Host</td>
|
||||
<td class="data">{{$bb.Host}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Version / Commit / Build</td>
|
||||
<td class="data">{{$bb.Version}} / <a href="https://github.com/trezor/blockbook/commit/{{$bb.GitCommit}}" target="_blank" rel="noopener noreferrer">{{$bb.GitCommit}}</a> / {{$bb.BuildTime}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Synchronized</td>
|
||||
<td class="data {{if not $bb.InSync}}text-danger{{else}}text-success{{end}}">{{$bb.InSync}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last Block</td>
|
||||
<td class="data">{{if .InternalExplorer}}<a href="/block/{{$bb.BestHeight}}">{{$bb.BestHeight}}</a>{{else}}{{$bb.BestHeight}}{{end}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last Block Update</td>
|
||||
<td class="data">{{formatTime $bb.LastBlockTime}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Mempool in Sync</td>
|
||||
<td class="data {{if not $bb.InSyncMempool}}text-danger{{else}}text-success{{end}}">{{$bb.InSyncMempool}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last Mempool Update</td>
|
||||
<td class="data">{{formatTime $bb.LastMempoolTime}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Size On Disk</td>
|
||||
<td class="data">{{$bb.DbSize}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h3>Backend</h3>
|
||||
<table class="table data-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="width: 30%;">Chain</td>
|
||||
<td class="data">{{$be.Chain}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Version</td>
|
||||
<td class="data">{{$be.Version}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Subversion</td>
|
||||
<td class="data">{{$be.Subversion}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Protocol Version</td>
|
||||
<td class="data">{{$be.ProtocolVersion}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last Block</td>
|
||||
<td class="data">{{$be.Blocks}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Difficulty</td>
|
||||
<td class="data">{{$be.Difficulty}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Timeoffset</td>
|
||||
<td class="data">{{$be.Timeoffset}}</td>
|
||||
</tr>
|
||||
{{- if $be.SizeOnDisk -}}
|
||||
<tr>
|
||||
<td>Size On Disk</td>
|
||||
<td class="data">{{$be.SizeOnDisk}}</td>
|
||||
</tr>
|
||||
{{- end -}}
|
||||
{{- if $be.Warnings -}}
|
||||
<tr>
|
||||
<td>Warnings</td>
|
||||
<td class="data text-warning">{{$be.Warnings}}</td>
|
||||
</tr>
|
||||
{{- end -}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-muted">{{$bb.About}}</span>
|
||||
{{end}}
|
|
@ -1,7 +1,7 @@
|
|||
{{define "specific"}}{{$cs := .CoinShortcut}}{{$tx := .Tx}}
|
||||
<h1>Transaction</h1>
|
||||
<div class="alert alert-data">
|
||||
<span class="ellipsis data">{{$tx.Txid}}</span>
|
||||
<div class="alert alert-data ellipsis">
|
||||
<span class="data">{{$tx.Txid}}</span>
|
||||
</div>
|
||||
<h3>Summary</h3>
|
||||
<div class="data-div">
|
||||
|
@ -41,4 +41,10 @@
|
|||
<div class="data-div">
|
||||
{{template "txdetail" .}}
|
||||
</div>
|
||||
<div class="data-div">
|
||||
<h5>Hex</h5>
|
||||
<div class="alert alert-data" style="word-wrap: break-word; font-size: smaller;">
|
||||
<span>{{$tx.Hex}}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
|
@ -2,7 +2,7 @@
|
|||
<div class="alert alert-data">
|
||||
<div class="row line-bot">
|
||||
<div class="col-xs-7 col-md-8 ellipsis">
|
||||
<a href="/explorer/tx/{{$tx.Txid}}">{{$tx.Txid}}</a>
|
||||
<a href="/tx/{{$tx.Txid}}">{{$tx.Txid}}</a>
|
||||
</div>
|
||||
{{- if $tx.Confirmations -}}
|
||||
<div class="col-xs-5 col-md-4 text-muted text-right">mined {{formatUnixTime $tx.Blocktime}}</div>
|
||||
|
@ -16,9 +16,12 @@
|
|||
{{- range $vin := $tx.Vin -}}
|
||||
<tr>
|
||||
<td>
|
||||
{{- range $a := $vin.Addresses}}
|
||||
{{- if $vin.Txid -}}
|
||||
<a class="float-left text-muted" href="/tx/{{$vin.Txid}}" title="Outpoint {{$vin.Txid}},{{$vin.Vout}}">➡ </a>
|
||||
{{- end -}}
|
||||
{{- range $a := $vin.Addresses -}}
|
||||
<span class="ellipsis float-left">
|
||||
{{if and (ne $a $addr) $vin.Searchable}}<a href="/explorer/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{end}}
|
||||
{{if and (ne $a $addr) $vin.Searchable}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{end}}
|
||||
</span>
|
||||
{{- else -}}
|
||||
<span class="float-left">No Inputs (Newly Generated Coins)</span>
|
||||
|
@ -27,6 +30,10 @@
|
|||
{{- end -}}
|
||||
</td>
|
||||
</tr>
|
||||
{{- else -}}
|
||||
<tr>
|
||||
<td>No Inputs</td>
|
||||
</tr>
|
||||
{{- end -}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -46,18 +53,22 @@
|
|||
<td>
|
||||
{{- range $a := $vout.ScriptPubKey.Addresses -}}
|
||||
<span class="ellipsis float-left">
|
||||
{{- if and (ne $a $addr) $vout.ScriptPubKey.Searchable}}<a href="/explorer/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{- end -}}
|
||||
{{- if and (ne $a $addr) $vout.ScriptPubKey.Searchable}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{- end -}}
|
||||
</span>
|
||||
{{- else -}}
|
||||
<span class="float-left">Unparsed address</span>
|
||||
{{- end -}}
|
||||
<span class="float-right{{if stringInSlice $addr $vout.ScriptPubKey.Addresses}} text-success{{end}}">
|
||||
{{formatAmount $vout.Value}} {{$cs}}{{if $vout.Spent}}{{if $vout.SpentTxID}}<a class="text-danger" href="/explorer/tx/{{$vout.SpentTxID}}">(S)</a>{{else}}<span class="text-danger">(S)</span>{{end}}{{else -}}
|
||||
<span class="text-success">(U)</span>
|
||||
{{formatAmount $vout.Value}} {{$cs}} {{if $vout.Spent}}<a class="text-danger" href="{{if $vout.SpentTxID}}/tx/{{$vout.SpentTxID}}{{else}}/spending/{{$tx.Txid}}/{{$vout.N}}{{end}}" title="Spent">➡</a>{{else -}}
|
||||
<span class="text-success" title="Unspent"> <b>×</b></span>
|
||||
{{- end -}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{{- else -}}
|
||||
<tr>
|
||||
<td>No Outputs</td>
|
||||
</tr>
|
||||
{{- end -}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -147,7 +147,7 @@ func makeRocksDB(parser bchain.BlockChainParser, m *common.Metrics, is *common.I
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
d, err := db.NewRocksDB(p, 100000, parser, m)
|
||||
d, err := db.NewRocksDB(p, 1<<17, 1<<14, parser, m)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -208,7 +208,7 @@ func testConnectBlocks(t *testing.T, h *TestHandler) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.ConnectBlocks(sw, func(hash string) {
|
||||
err = db.ConnectBlocks(sw, func(hash string, height uint32) {
|
||||
if hash == upperHash {
|
||||
close(ch)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue