Merge branch 'explorer'
Explorer is not yet finished but brings changes in coin json configs and blockbook flags which should be propagated.
This commit is contained in:
commit
64b34c1dd8
@ -67,7 +67,7 @@ go build
|
|||||||
## Example command
|
## Example command
|
||||||
To run blockbook with fast synchronization, connection to ZeroMQ and providing https and socket.io interface, with database in local directory *data* and connected to local bitcoind with configuration specified by parameter *-blockchaincfg*:
|
To run blockbook with fast synchronization, connection to ZeroMQ and providing https and socket.io interface, with database in local directory *data* and connected to local bitcoind with configuration specified by parameter *-blockchaincfg*:
|
||||||
```
|
```
|
||||||
./blockbook -sync -blockchaincfg=configs/bitcoin_testnet.json -httpserver=127.0.0.1:8333 -socketio=127.0.01:8334 -certfile=server/testcert -logtostderr
|
./blockbook -sync -blockchaincfg=configs/bitcoin_testnet.json -internal=127.0.0.1:8333 -public=127.0.01:8334 -certfile=server/testcert -logtostderr
|
||||||
```
|
```
|
||||||
Blockbook logs to stderr *-logtostderr* or to directory specified by parameter *-log_dir* . Verbosity of logs can be tuned by command line parameters *-v* and *-vmodule*, details at https://godoc.org/github.com/golang/glog
|
Blockbook logs to stderr *-logtostderr* or to directory specified by parameter *-log_dir* . Verbosity of logs can be tuned by command line parameters *-v* and *-vmodule*, details at https://godoc.org/github.com/golang/glog
|
||||||
|
|
||||||
|
|||||||
65
api/types.go
Normal file
65
api/types.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
type ScriptSig struct {
|
||||||
|
Hex string `json:"hex"`
|
||||||
|
Asm string `json:"asm,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Vin struct {
|
||||||
|
Txid string `json:"txid"`
|
||||||
|
Vout uint32 `json:"vout"`
|
||||||
|
Sequence int64 `json:"sequence,omitempty"`
|
||||||
|
N int `json:"n"`
|
||||||
|
ScriptSig ScriptSig `json:"scriptSig"`
|
||||||
|
Addr string `json:"addr"`
|
||||||
|
ValueSat int64 `json:"valueSat"`
|
||||||
|
Value float64 `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScriptPubKey struct {
|
||||||
|
Hex string `json:"hex"`
|
||||||
|
Asm string `json:"asm,omitempty"`
|
||||||
|
Addresses []string `json:"addresses"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
}
|
||||||
|
type Vout struct {
|
||||||
|
Value float64 `json:"value"`
|
||||||
|
N int `json:"n"`
|
||||||
|
ScriptPubKey ScriptPubKey `json:"scriptPubKey"`
|
||||||
|
SpentTxID string `json:"spentTxId,omitempty"`
|
||||||
|
SpentIndex int `json:"spentIndex,omitempty"`
|
||||||
|
SpentHeight int `json:"spentHeight,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Tx struct {
|
||||||
|
Txid string `json:"txid"`
|
||||||
|
Version int32 `json:"version,omitempty"`
|
||||||
|
Locktime uint32 `json:"locktime,omitempty"`
|
||||||
|
Vin []Vin `json:"vin"`
|
||||||
|
Vout []Vout `json:"vout"`
|
||||||
|
Blockhash string `json:"blockhash,omitempty"`
|
||||||
|
Blockheight int `json:"blockheight"`
|
||||||
|
Confirmations uint32 `json:"confirmations"`
|
||||||
|
Time int64 `json:"time,omitempty"`
|
||||||
|
Blocktime int64 `json:"blocktime"`
|
||||||
|
ValueOut float64 `json:"valueOut"`
|
||||||
|
Size int `json:"size,omitempty"`
|
||||||
|
ValueIn float64 `json:"valueIn"`
|
||||||
|
Fees float64 `json:"fees"`
|
||||||
|
WithSpends bool `json:"withSpends,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Address struct {
|
||||||
|
AddrStr string `json:"addrStr"`
|
||||||
|
Balance float64 `json:"balance"`
|
||||||
|
BalanceSat int64 `json:"balanceSat"`
|
||||||
|
TotalReceived float64 `json:"totalReceived"`
|
||||||
|
TotalReceivedSat int64 `json:"totalReceivedSat"`
|
||||||
|
TotalSent float64 `json:"totalSent"`
|
||||||
|
TotalSentSat int64 `json:"totalSentSat"`
|
||||||
|
UnconfirmedBalance float64 `json:"unconfirmedBalance"`
|
||||||
|
UnconfirmedBalanceSat int64 `json:"unconfirmedBalanceSat"`
|
||||||
|
UnconfirmedTxApperances int `json:"unconfirmedTxApperances"`
|
||||||
|
TxApperances int `json:"txApperances"`
|
||||||
|
Transactions []*Tx `json:"transactions"`
|
||||||
|
}
|
||||||
243
api/worker.go
Normal file
243
api/worker.go
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"blockbook/bchain"
|
||||||
|
"blockbook/common"
|
||||||
|
"blockbook/db"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
const txsOnPage = 30
|
||||||
|
|
||||||
|
// Worker is handle to api worker
|
||||||
|
type Worker struct {
|
||||||
|
db *db.RocksDB
|
||||||
|
txCache *db.TxCache
|
||||||
|
chain bchain.BlockChain
|
||||||
|
chainParser bchain.BlockChainParser
|
||||||
|
is *common.InternalState
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWorker creates new api worker
|
||||||
|
func NewWorker(db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is *common.InternalState) (*Worker, error) {
|
||||||
|
w := &Worker{
|
||||||
|
db: db,
|
||||||
|
txCache: txCache,
|
||||||
|
chain: chain,
|
||||||
|
chainParser: chain.GetChainParser(),
|
||||||
|
is: is,
|
||||||
|
}
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransaction reads transaction data from txid
|
||||||
|
func (w *Worker) GetTransaction(txid string, bestheight uint32, spendingTx bool) (*Tx, error) {
|
||||||
|
bchainTx, height, err := w.txCache.GetTransaction(txid, bestheight)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var blockhash string
|
||||||
|
if bchainTx.Confirmations > 0 {
|
||||||
|
blockhash, err = w.db.GetBlockHash(height)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var valIn, valOut, fees float64
|
||||||
|
vins := make([]Vin, len(bchainTx.Vin))
|
||||||
|
for i := range bchainTx.Vin {
|
||||||
|
bchainVin := &bchainTx.Vin[i]
|
||||||
|
vin := &vins[i]
|
||||||
|
vin.Txid = bchainVin.Txid
|
||||||
|
vin.N = i
|
||||||
|
vin.Vout = bchainVin.Vout
|
||||||
|
vin.ScriptSig.Hex = bchainVin.ScriptSig.Hex
|
||||||
|
// bchainVin.Txid=="" is coinbase transaction
|
||||||
|
if bchainVin.Txid != "" {
|
||||||
|
otx, _, err := w.txCache.GetTransaction(bchainVin.Txid, bestheight)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(otx.Vout) > int(vin.Vout) {
|
||||||
|
vout := &otx.Vout[vin.Vout]
|
||||||
|
vin.Value = vout.Value
|
||||||
|
valIn += vout.Value
|
||||||
|
vin.ValueSat = int64(vout.Value*1E8 + 0.5)
|
||||||
|
if vout.Address != nil {
|
||||||
|
a := vout.Address.String()
|
||||||
|
vin.Addr = a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vouts := make([]Vout, len(bchainTx.Vout))
|
||||||
|
for i := range bchainTx.Vout {
|
||||||
|
bchainVout := &bchainTx.Vout[i]
|
||||||
|
vout := &vouts[i]
|
||||||
|
vout.N = i
|
||||||
|
vout.Value = bchainVout.Value
|
||||||
|
valOut += bchainVout.Value
|
||||||
|
vout.ScriptPubKey.Hex = bchainVout.ScriptPubKey.Hex
|
||||||
|
vout.ScriptPubKey.Addresses = bchainVout.ScriptPubKey.Addresses
|
||||||
|
if spendingTx {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// for coinbase transactions valIn is 0
|
||||||
|
fees = valIn - valOut
|
||||||
|
if fees < 0 {
|
||||||
|
fees = 0
|
||||||
|
}
|
||||||
|
// for now do not return size, we would have to compute vsize of segwit transactions
|
||||||
|
// size:=len(bchainTx.Hex) / 2
|
||||||
|
r := &Tx{
|
||||||
|
Blockhash: blockhash,
|
||||||
|
Blockheight: int(height),
|
||||||
|
Blocktime: bchainTx.Blocktime,
|
||||||
|
Confirmations: bchainTx.Confirmations,
|
||||||
|
Fees: fees,
|
||||||
|
Locktime: bchainTx.LockTime,
|
||||||
|
WithSpends: spendingTx,
|
||||||
|
Time: bchainTx.Time,
|
||||||
|
Txid: bchainTx.Txid,
|
||||||
|
ValueIn: valIn,
|
||||||
|
ValueOut: valOut,
|
||||||
|
Version: bchainTx.Version,
|
||||||
|
Vin: vins,
|
||||||
|
Vout: vouts,
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Worker) getAddressTxids(address string, mempool bool) ([]string, error) {
|
||||||
|
var err error
|
||||||
|
txids := make([]string, 0)
|
||||||
|
if !mempool {
|
||||||
|
err = s.db.GetTransactions(address, 0, ^uint32(0), func(txid string, vout uint32, isOutput bool) error {
|
||||||
|
txids = append(txids, txid)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m, err := s.chain.GetMempoolTransactions(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
txids = append(txids, m...)
|
||||||
|
}
|
||||||
|
return txids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tx) getAddrVoutValue(addrID string) float64 {
|
||||||
|
var val float64
|
||||||
|
for _, vout := range t.Vout {
|
||||||
|
for _, a := range vout.ScriptPubKey.Addresses {
|
||||||
|
if a == addrID {
|
||||||
|
val += vout.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Tx) getAddrVinValue(addrID string) float64 {
|
||||||
|
var val float64
|
||||||
|
for _, vin := range t.Vin {
|
||||||
|
if vin.Addr == addrID {
|
||||||
|
val += vin.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// UniqueTxidsInReverse reverts the order of transactions (so that newest are first) and removes duplicate transactions
|
||||||
|
func UniqueTxidsInReverse(txids []string) []string {
|
||||||
|
i := len(txids)
|
||||||
|
ut := make([]string, i)
|
||||||
|
txidsMap := make(map[string]struct{})
|
||||||
|
for _, txid := range txids {
|
||||||
|
_, e := txidsMap[txid]
|
||||||
|
if !e {
|
||||||
|
i--
|
||||||
|
ut[i] = txid
|
||||||
|
txidsMap[txid] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ut[i:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAddress computes address value and gets transactions for given address
|
||||||
|
func (w *Worker) GetAddress(addrID string, page int) (*Address, error) {
|
||||||
|
glog.Info(addrID, " start")
|
||||||
|
txc, err := w.getAddressTxids(addrID, false)
|
||||||
|
txc = UniqueTxidsInReverse(txc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
txm, err := w.getAddressTxids(addrID, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bestheight, _, err := w.db.GetBestBlock()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lc := len(txc)
|
||||||
|
if lc > txsOnPage {
|
||||||
|
lc = txsOnPage
|
||||||
|
}
|
||||||
|
txs := make([]*Tx, len(txm)+lc)
|
||||||
|
txi := 0
|
||||||
|
var uBal, bal, totRecv, totSent float64
|
||||||
|
for _, tx := range txm {
|
||||||
|
tx, err := w.GetTransaction(tx, bestheight, false)
|
||||||
|
// mempool transaction may fail
|
||||||
|
if err != nil {
|
||||||
|
glog.Error("GetTransaction ", tx, ": ", err)
|
||||||
|
} else {
|
||||||
|
uBal = tx.getAddrVoutValue(addrID) - tx.getAddrVinValue(addrID)
|
||||||
|
txs[txi] = tx
|
||||||
|
txi++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if page < 0 {
|
||||||
|
page = 0
|
||||||
|
}
|
||||||
|
from := page * txsOnPage
|
||||||
|
if from > len(txc) {
|
||||||
|
from = 0
|
||||||
|
}
|
||||||
|
to := from + txsOnPage
|
||||||
|
for i, tx := range txc {
|
||||||
|
tx, err := w.GetTransaction(tx, bestheight, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
totRecv += tx.getAddrVoutValue(addrID)
|
||||||
|
totSent += tx.getAddrVinValue(addrID)
|
||||||
|
if i >= from && i < to {
|
||||||
|
txs[txi] = tx
|
||||||
|
txi++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bal = totRecv - totSent
|
||||||
|
r := &Address{
|
||||||
|
AddrStr: addrID,
|
||||||
|
Balance: bal,
|
||||||
|
BalanceSat: int64(bal*1E8 + 0.5),
|
||||||
|
TotalReceived: totRecv,
|
||||||
|
TotalReceivedSat: int64(totRecv*1E8 + 0.5),
|
||||||
|
TotalSent: totSent,
|
||||||
|
TotalSentSat: int64(totSent*1E8 + 0.5),
|
||||||
|
Transactions: txs[:txi],
|
||||||
|
TxApperances: len(txc),
|
||||||
|
UnconfirmedBalance: uBal,
|
||||||
|
UnconfirmedTxApperances: len(txm),
|
||||||
|
}
|
||||||
|
glog.Info(addrID, " finished")
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
@ -146,6 +146,7 @@ func init() {
|
|||||||
Blocktime: 1519053802,
|
Blocktime: 1519053802,
|
||||||
Txid: "056e3d82e5ffd0e915fb9b62797d76263508c34fe3e5dbed30dd3e943930f204",
|
Txid: "056e3d82e5ffd0e915fb9b62797d76263508c34fe3e5dbed30dd3e943930f204",
|
||||||
LockTime: 512115,
|
LockTime: 512115,
|
||||||
|
Version: 1,
|
||||||
Vin: []bchain.Vin{
|
Vin: []bchain.Vin{
|
||||||
{
|
{
|
||||||
ScriptSig: bchain.ScriptSig{
|
ScriptSig: bchain.ScriptSig{
|
||||||
@ -176,6 +177,7 @@ func init() {
|
|||||||
Blocktime: 1235678901,
|
Blocktime: 1235678901,
|
||||||
Txid: "474e6795760ebe81cb4023dc227e5a0efe340e1771c89a0035276361ed733de7",
|
Txid: "474e6795760ebe81cb4023dc227e5a0efe340e1771c89a0035276361ed733de7",
|
||||||
LockTime: 0,
|
LockTime: 0,
|
||||||
|
Version: 1,
|
||||||
Vin: []bchain.Vin{
|
Vin: []bchain.Vin{
|
||||||
{
|
{
|
||||||
ScriptSig: bchain.ScriptSig{
|
ScriptSig: bchain.ScriptSig{
|
||||||
|
|||||||
@ -9,8 +9,8 @@ import (
|
|||||||
"blockbook/bchain/coins/dogecoin"
|
"blockbook/bchain/coins/dogecoin"
|
||||||
"blockbook/bchain/coins/eth"
|
"blockbook/bchain/coins/eth"
|
||||||
"blockbook/bchain/coins/litecoin"
|
"blockbook/bchain/coins/litecoin"
|
||||||
"blockbook/bchain/coins/vertcoin"
|
|
||||||
"blockbook/bchain/coins/namecoin"
|
"blockbook/bchain/coins/namecoin"
|
||||||
|
"blockbook/bchain/coins/vertcoin"
|
||||||
"blockbook/bchain/coins/zec"
|
"blockbook/bchain/coins/zec"
|
||||||
"blockbook/common"
|
"blockbook/common"
|
||||||
"context"
|
"context"
|
||||||
@ -47,20 +47,21 @@ func init() {
|
|||||||
blockChainFactories["Namecoin"] = namecoin.NewNamecoinRPC
|
blockChainFactories["Namecoin"] = namecoin.NewNamecoinRPC
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCoinNameFromConfig gets coin name from config file
|
// GetCoinNameFromConfig gets coin name and coin shortcut from config file
|
||||||
func GetCoinNameFromConfig(configfile string) (string, error) {
|
func GetCoinNameFromConfig(configfile string) (string, string, error) {
|
||||||
data, err := ioutil.ReadFile(configfile)
|
data, err := ioutil.ReadFile(configfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Annotatef(err, "Error reading file %v", configfile)
|
return "", "", errors.Annotatef(err, "Error reading file %v", configfile)
|
||||||
}
|
}
|
||||||
var cn struct {
|
var cn struct {
|
||||||
CoinName string `json:"coin_name"`
|
CoinName string `json:"coin_name"`
|
||||||
|
CoinShortcut string `json:"coin_shortcut"`
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(data, &cn)
|
err = json.Unmarshal(data, &cn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Annotatef(err, "Error parsing file %v", configfile)
|
return "", "", errors.Annotatef(err, "Error parsing file %v", configfile)
|
||||||
}
|
}
|
||||||
return cn.CoinName, nil
|
return cn.CoinName, cn.CoinShortcut, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBlockChain creates bchain.BlockChain of type defined by parameter coin
|
// NewBlockChain creates bchain.BlockChain of type defined by parameter coin
|
||||||
|
|||||||
@ -130,8 +130,8 @@ func (p *BitcoinParser) TxFromMsgTx(t *wire.MsgTx, parseAddresses bool) bchain.T
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
tx := bchain.Tx{
|
tx := bchain.Tx{
|
||||||
Txid: t.TxHash().String(),
|
Txid: t.TxHash().String(),
|
||||||
// skip: Version,
|
Version: t.Version,
|
||||||
LockTime: t.LockTime,
|
LockTime: t.LockTime,
|
||||||
Vin: vin,
|
Vin: vin,
|
||||||
Vout: vout,
|
Vout: vout,
|
||||||
|
|||||||
@ -139,6 +139,7 @@ func init() {
|
|||||||
Blocktime: 1519053802,
|
Blocktime: 1519053802,
|
||||||
Txid: "056e3d82e5ffd0e915fb9b62797d76263508c34fe3e5dbed30dd3e943930f204",
|
Txid: "056e3d82e5ffd0e915fb9b62797d76263508c34fe3e5dbed30dd3e943930f204",
|
||||||
LockTime: 512115,
|
LockTime: 512115,
|
||||||
|
Version: 1,
|
||||||
Vin: []bchain.Vin{
|
Vin: []bchain.Vin{
|
||||||
{
|
{
|
||||||
ScriptSig: bchain.ScriptSig{
|
ScriptSig: bchain.ScriptSig{
|
||||||
@ -169,6 +170,7 @@ func init() {
|
|||||||
Blocktime: 1235678901,
|
Blocktime: 1235678901,
|
||||||
Txid: "474e6795760ebe81cb4023dc227e5a0efe340e1771c89a0035276361ed733de7",
|
Txid: "474e6795760ebe81cb4023dc227e5a0efe340e1771c89a0035276361ed733de7",
|
||||||
LockTime: 0,
|
LockTime: 0,
|
||||||
|
Version: 1,
|
||||||
Vin: []bchain.Vin{
|
Vin: []bchain.Vin{
|
||||||
{
|
{
|
||||||
ScriptSig: bchain.ScriptSig{
|
ScriptSig: bchain.ScriptSig{
|
||||||
|
|||||||
@ -41,7 +41,7 @@ Create script that runs blockbook *run-btc-blockbook.sh*
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
cd go/src/blockbook
|
cd go/src/blockbook
|
||||||
./blockbook -coin=btc -blockchaincfg=/data/btc/blockbook/btc.json -datadir=/data/btc/blockbook/db -sync -httpserver=:9030 -socketio=:9130 -certfile=server/testcert -explorer=https://bitcore1.trezor.io/ $1
|
./blockbook -coin=btc -blockchaincfg=/data/btc/blockbook/btc.json -datadir=/data/btc/blockbook/db -sync -internal=:9030 -public=:9130 -certfile=server/testcert -explorer=https://bitcore1.trezor.io/ $1
|
||||||
```
|
```
|
||||||
To run blockbook with logging to file (run with nohup or daemonize or using screen)
|
To run blockbook with logging to file (run with nohup or daemonize or using screen)
|
||||||
```
|
```
|
||||||
|
|||||||
@ -41,7 +41,7 @@ Create script that runs blockbook *run-testnet-blockbook.sh*
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
cd go/src/blockbook
|
cd go/src/blockbook
|
||||||
./blockbook -coin=btc-testnet -blockchaincfg=/data/testnet/blockbook/btc-testnet.json -datadir=/data/testnet/blockbook/db -sync -httpserver=:19030 -socketio=:19130 -certfile=server/testcert -explorer=https://testnet-bitcore1.trezor.io $1
|
./blockbook -coin=btc-testnet -blockchaincfg=/data/testnet/blockbook/btc-testnet.json -datadir=/data/testnet/blockbook/db -sync -internal=:19030 -public=:19130 -certfile=server/testcert -explorer=https://testnet-bitcore1.trezor.io $1
|
||||||
```
|
```
|
||||||
To run blockbook with logging to file (run with nohup or daemonize or using screen)
|
To run blockbook with logging to file (run with nohup or daemonize or using screen)
|
||||||
```
|
```
|
||||||
|
|||||||
@ -98,6 +98,7 @@ func init() {
|
|||||||
Blocktime: 1519053456,
|
Blocktime: 1519053456,
|
||||||
Txid: "097ea09ba284f3f2a9e880e11f837edf7e5cea81c8da2238f5bc7c2c4c407943",
|
Txid: "097ea09ba284f3f2a9e880e11f837edf7e5cea81c8da2238f5bc7c2c4c407943",
|
||||||
LockTime: 0,
|
LockTime: 0,
|
||||||
|
Version: 1,
|
||||||
Vin: []bchain.Vin{
|
Vin: []bchain.Vin{
|
||||||
{
|
{
|
||||||
ScriptSig: bchain.ScriptSig{
|
ScriptSig: bchain.ScriptSig{
|
||||||
@ -139,6 +140,7 @@ func init() {
|
|||||||
Blocktime: 1519050987,
|
Blocktime: 1519050987,
|
||||||
Txid: "b276545af246e3ed5a4e3e5b60d359942a1808579effc53ff4f343e4f6cfc5a0",
|
Txid: "b276545af246e3ed5a4e3e5b60d359942a1808579effc53ff4f343e4f6cfc5a0",
|
||||||
LockTime: 0,
|
LockTime: 0,
|
||||||
|
Version: 1,
|
||||||
Vin: []bchain.Vin{
|
Vin: []bchain.Vin{
|
||||||
{
|
{
|
||||||
ScriptSig: bchain.ScriptSig{
|
ScriptSig: bchain.ScriptSig{
|
||||||
|
|||||||
@ -17,7 +17,7 @@ Create script that runs blockbook *run-eth-blockbook.sh*
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
cd go/src/blockbook
|
cd go/src/blockbook
|
||||||
./blockbook -coin=eth -blockchaincfg=/data/eth/blockbook/eth.json -datadir=/data/eth/blockbook/db -sync -httpserver=:8555 -socketio=:8556 -certfile=server/testcert $1
|
./blockbook -coin=eth -blockchaincfg=/data/eth/blockbook/eth.json -datadir=/data/eth/blockbook/db -sync -internal=:8555 -public=:8556 -certfile=server/testcert $1
|
||||||
```
|
```
|
||||||
To run blockbook with logging to file (run with nohup or daemonize or using screen)
|
To run blockbook with logging to file (run with nohup or daemonize or using screen)
|
||||||
```
|
```
|
||||||
|
|||||||
@ -17,7 +17,7 @@ Create script that runs blockbook *run-eth-testnet-blockbook.sh*
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
cd go/src/blockbook
|
cd go/src/blockbook
|
||||||
./blockbook -coin=eth-testnet -blockchaincfg=/data/eth-testnet/blockbook/eth-testnet.json -datadir=/data/eth-testnet/blockbook/db -sync -httpserver=:18555 -socketio=:18556 -certfile=server/testcert $1
|
./blockbook -coin=eth-testnet -blockchaincfg=/data/eth-testnet/blockbook/eth-testnet.json -datadir=/data/eth-testnet/blockbook/db -sync -internal=:18555 -public=:18556 -certfile=server/testcert $1
|
||||||
```
|
```
|
||||||
To run blockbook with logging to file (run with nohup or daemonize or using screen)
|
To run blockbook with logging to file (run with nohup or daemonize or using screen)
|
||||||
```
|
```
|
||||||
|
|||||||
@ -144,6 +144,7 @@ func init() {
|
|||||||
Blocktime: 1519053456,
|
Blocktime: 1519053456,
|
||||||
Txid: "1c50c1770374d7de2f81a87463a5225bb620d25fd467536223a5b715a47c9e32",
|
Txid: "1c50c1770374d7de2f81a87463a5225bb620d25fd467536223a5b715a47c9e32",
|
||||||
LockTime: 0,
|
LockTime: 0,
|
||||||
|
Version: 2,
|
||||||
Vin: []bchain.Vin{
|
Vin: []bchain.Vin{
|
||||||
{
|
{
|
||||||
ScriptSig: bchain.ScriptSig{
|
ScriptSig: bchain.ScriptSig{
|
||||||
|
|||||||
@ -92,6 +92,7 @@ func init() {
|
|||||||
Blocktime: 1529925180,
|
Blocktime: 1529925180,
|
||||||
Txid: "d58c11aa970449c3e0ee5e0cdf78532435a9d2b28a2da284a8dd4dd6bdd0331c",
|
Txid: "d58c11aa970449c3e0ee5e0cdf78532435a9d2b28a2da284a8dd4dd6bdd0331c",
|
||||||
LockTime: 952180,
|
LockTime: 952180,
|
||||||
|
Version: 1,
|
||||||
Vin: []bchain.Vin{
|
Vin: []bchain.Vin{
|
||||||
{
|
{
|
||||||
ScriptSig: bchain.ScriptSig{
|
ScriptSig: bchain.ScriptSig{
|
||||||
@ -48,7 +48,7 @@ Create blockchain configuration file */data/zec/blockbook/zec.json*
|
|||||||
Create *run-zec-blockbook.sh* script that starts blockbook
|
Create *run-zec-blockbook.sh* script that starts blockbook
|
||||||
```
|
```
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
./blockbook -coin=zec -blockchaincfg=/data/zec/blockbook/zec.json -datadir=/data/zec/blockbook/db -sync -httpserver=:9032 -socketio=:9132 -certfile=server/testcert -explorer=https://zec-bitcore1.trezor.io $1
|
./blockbook -coin=zec -blockchaincfg=/data/zec/blockbook/zec.json -datadir=/data/zec/blockbook/db -sync -internal=:9032 -public=:9132 -certfile=server/testcert -explorer=https://zec-bitcore1.trezor.io $1
|
||||||
```
|
```
|
||||||
|
|
||||||
To run blockbook with logging to file (run with nohup or daemonize using screen)
|
To run blockbook with logging to file (run with nohup or daemonize using screen)
|
||||||
|
|||||||
@ -58,9 +58,9 @@ type Vout struct {
|
|||||||
// Tx is blockchain transaction
|
// Tx is blockchain transaction
|
||||||
// unnecessary fields are commented out to avoid overhead
|
// unnecessary fields are commented out to avoid overhead
|
||||||
type Tx struct {
|
type Tx struct {
|
||||||
Hex string `json:"hex"`
|
Hex string `json:"hex"`
|
||||||
Txid string `json:"txid"`
|
Txid string `json:"txid"`
|
||||||
// Version int32 `json:"version"`
|
Version int32 `json:"version"`
|
||||||
LockTime uint32 `json:"locktime"`
|
LockTime uint32 `json:"locktime"`
|
||||||
Vin []Vin `json:"vin"`
|
Vin []Vin `json:"vin"`
|
||||||
Vout []Vout `json:"vout"`
|
Vout []Vout `json:"vout"`
|
||||||
|
|||||||
58
blockbook.go
58
blockbook.go
@ -54,9 +54,9 @@ var (
|
|||||||
syncWorkers = flag.Int("workers", 8, "number of workers to process blocks")
|
syncWorkers = flag.Int("workers", 8, "number of workers to process blocks")
|
||||||
dryRun = flag.Bool("dryrun", false, "do not index blocks, only download")
|
dryRun = flag.Bool("dryrun", false, "do not index blocks, only download")
|
||||||
|
|
||||||
httpServerBinding = flag.String("httpserver", "", "http server binding [address]:port, (default no http server)")
|
internalBinding = flag.String("internal", "", "internal http server binding [address]:port, (default no internal server)")
|
||||||
|
|
||||||
socketIoBinding = flag.String("socketio", "", "socketio server binding [address]:port[/path], (default no socket.io 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)")
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ func main() {
|
|||||||
glog.Fatal("Missing blockchaincfg configuration parameter")
|
glog.Fatal("Missing blockchaincfg configuration parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
coin, err := coins.GetCoinNameFromConfig(*blockchain)
|
coin, coinShortcut, err := coins.GetCoinNameFromConfig(*blockchain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Fatal("config: ", err)
|
glog.Fatal("config: ", err)
|
||||||
}
|
}
|
||||||
@ -168,7 +168,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer index.Close()
|
defer index.Close()
|
||||||
|
|
||||||
internalState, err = newInternalState(coin, index)
|
internalState, err = newInternalState(coin, coinShortcut, index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error("internalState: ", err)
|
glog.Error("internalState: ", err)
|
||||||
return
|
return
|
||||||
@ -232,18 +232,18 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var httpServer *server.HTTPServer
|
var internalServer *server.InternalServer
|
||||||
if *httpServerBinding != "" {
|
if *internalBinding != "" {
|
||||||
httpServer, err = server.NewHTTPServer(*httpServerBinding, *certFiles, index, chain, txCache, internalState)
|
internalServer, err = server.NewInternalServer(*internalBinding, *certFiles, index, chain, txCache, internalState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error("https: ", err)
|
glog.Error("https: ", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
err = httpServer.Run()
|
err = internalServer.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "http: Server closed" {
|
if err.Error() == "http: Server closed" {
|
||||||
glog.Info(err)
|
glog.Info("internal server: closed")
|
||||||
} else {
|
} else {
|
||||||
glog.Error(err)
|
glog.Error(err)
|
||||||
return
|
return
|
||||||
@ -263,27 +263,26 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var socketIoServer *server.SocketIoServer
|
var publicServer *server.PublicServer
|
||||||
if *socketIoBinding != "" {
|
if *publicBinding != "" {
|
||||||
socketIoServer, err = server.NewSocketIoServer(
|
publicServer, err = server.NewPublicServer(*publicBinding, *certFiles, index, chain, txCache, *explorerURL, metrics, internalState)
|
||||||
*socketIoBinding, *certFiles, index, chain, txCache, *explorerURL, metrics, internalState)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error("socketio: ", err)
|
glog.Error("socketio: ", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
err = socketIoServer.Run()
|
err = publicServer.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "http: Server closed" {
|
if err.Error() == "http: Server closed" {
|
||||||
glog.Info(err)
|
glog.Info("public server: closed")
|
||||||
} else {
|
} else {
|
||||||
glog.Error(err)
|
glog.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
callbacksOnNewBlockHash = append(callbacksOnNewBlockHash, socketIoServer.OnNewBlockHash)
|
callbacksOnNewBlockHash = append(callbacksOnNewBlockHash, publicServer.OnNewBlockHash)
|
||||||
callbacksOnNewTxAddr = append(callbacksOnNewTxAddr, socketIoServer.OnNewTxAddr)
|
callbacksOnNewTxAddr = append(callbacksOnNewTxAddr, publicServer.OnNewTxAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *synchronize {
|
if *synchronize {
|
||||||
@ -314,8 +313,8 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if httpServer != nil || socketIoServer != nil || chain != nil {
|
if internalServer != nil || publicServer != nil || chain != nil {
|
||||||
waitForSignalAndShutdown(httpServer, socketIoServer, chain, 10*time.Second)
|
waitForSignalAndShutdown(internalServer, publicServer, chain, 10*time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *synchronize {
|
if *synchronize {
|
||||||
@ -328,11 +327,12 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newInternalState(coin string, d *db.RocksDB) (*common.InternalState, error) {
|
func newInternalState(coin string, coinShortcut string, d *db.RocksDB) (*common.InternalState, error) {
|
||||||
is, err := d.LoadInternalState(coin)
|
is, err := d.LoadInternalState(coin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
is.CoinShortcut = coinShortcut
|
||||||
name, err := os.Hostname()
|
name, err := os.Hostname()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Error("get hostname ", err)
|
glog.Error("get hostname ", err)
|
||||||
@ -464,29 +464,29 @@ func pushSynchronizationHandler(nt bchain.NotificationType) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitForSignalAndShutdown(https *server.HTTPServer, socketio *server.SocketIoServer, chain bchain.BlockChain, timeout time.Duration) {
|
func waitForSignalAndShutdown(internal *server.InternalServer, public *server.PublicServer, chain bchain.BlockChain, timeout time.Duration) {
|
||||||
sig := <-chanOsSignal
|
sig := <-chanOsSignal
|
||||||
atomic.StoreInt32(&inShutdown, 1)
|
atomic.StoreInt32(&inShutdown, 1)
|
||||||
glog.Infof("Shutdown: %v", sig)
|
glog.Infof("shutdown: %v", sig)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if https != nil {
|
if internal != nil {
|
||||||
if err := https.Shutdown(ctx); err != nil {
|
if err := internal.Shutdown(ctx); err != nil {
|
||||||
glog.Error("HttpServer.Shutdown error: ", err)
|
glog.Error("internal server: shutdown error: ", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if socketio != nil {
|
if public != nil {
|
||||||
if err := socketio.Shutdown(ctx); err != nil {
|
if err := public.Shutdown(ctx); err != nil {
|
||||||
glog.Error("SocketIo.Shutdown error: ", err)
|
glog.Error("public server: shutdown error: ", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if chain != nil {
|
if chain != nil {
|
||||||
if err := chain.Shutdown(ctx); err != nil {
|
if err := chain.Shutdown(ctx); err != nil {
|
||||||
glog.Error("BlockChain.Shutdown error: ", err)
|
glog.Error("rpc: shutdown error: ", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ After=network.target
|
|||||||
Wants=backend-bcash-testnet.service
|
Wants=backend-bcash-testnet.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/opt/coins/blockbook/bcash_testnet/bin/blockbook -blockchaincfg=/opt/coins/blockbook/bcash_testnet/config/blockchaincfg.json -datadir=/opt/coins/data/bcash_testnet/blockbook/db -sync -httpserver=:19031 -socketio=:19131 -certfile=/opt/coins/blockbook/bcash_testnet/cert/blockbook -explorer=https://bitcoincash.blockexplorer.com/ -log_dir=/opt/coins/blockbook/bcash_testnet/logs
|
ExecStart=/opt/coins/blockbook/bcash_testnet/bin/blockbook -blockchaincfg=/opt/coins/blockbook/bcash_testnet/config/blockchaincfg.json -datadir=/opt/coins/data/bcash_testnet/blockbook/db -sync -internal=:19031 -public=:19131 -certfile=/opt/coins/blockbook/bcash_testnet/cert/blockbook -explorer=https://bitcoincash.blockexplorer.com/ -log_dir=/opt/coins/blockbook/bcash_testnet/logs
|
||||||
User=blockbook-bcash
|
User=blockbook-bcash
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
@ -10,7 +10,7 @@ After=network.target
|
|||||||
Wants=backend-bcash.service
|
Wants=backend-bcash.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/opt/coins/blockbook/bcash/bin/blockbook -blockchaincfg=/opt/coins/blockbook/bcash/config/blockchaincfg.json -datadir=/opt/coins/data/bcash/blockbook/db -sync -httpserver=:9031 -socketio=:9131 -certfile=/opt/coins/blockbook/bcash/cert/blockbook -explorer=https://bitcoincash.blockexplorer.com/ -log_dir=/opt/coins/blockbook/bcash/logs
|
ExecStart=/opt/coins/blockbook/bcash/bin/blockbook -blockchaincfg=/opt/coins/blockbook/bcash/config/blockchaincfg.json -datadir=/opt/coins/data/bcash/blockbook/db -sync -internal=:9031 -public=:9131 -certfile=/opt/coins/blockbook/bcash/cert/blockbook -explorer=https://bitcoincash.blockexplorer.com/ -log_dir=/opt/coins/blockbook/bcash/logs
|
||||||
User=blockbook-bcash
|
User=blockbook-bcash
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
@ -10,7 +10,7 @@ After=network.target
|
|||||||
Wants=backend-bgold.service
|
Wants=backend-bgold.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/opt/coins/blockbook/bgold/bin/blockbook -blockchaincfg=/opt/coins/blockbook/bgold/config/blockchaincfg.json -datadir=/opt/coins/data/bgold/blockbook/db -sync -httpserver=:9035 -socketio=:9135 -certfile=/opt/coins/blockbook/bgold/cert/blockbook -explorer=https://explorer.bitcoingold.org/ -log_dir=/opt/coins/blockbook/bgold/logs
|
ExecStart=/opt/coins/blockbook/bgold/bin/blockbook -blockchaincfg=/opt/coins/blockbook/bgold/config/blockchaincfg.json -datadir=/opt/coins/data/bgold/blockbook/db -sync -internal=:9035 -public=:9135 -certfile=/opt/coins/blockbook/bgold/cert/blockbook -explorer=/explorer -log_dir=/opt/coins/blockbook/bgold/logs
|
||||||
User=blockbook-bgold
|
User=blockbook-bgold
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
@ -10,7 +10,7 @@ After=network.target
|
|||||||
Wants=backend-bitcoin-testnet.service
|
Wants=backend-bitcoin-testnet.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/opt/coins/blockbook/bitcoin_testnet/bin/blockbook -blockchaincfg=/opt/coins/blockbook/bitcoin_testnet/config/blockchaincfg.json -datadir=/opt/coins/data/bitcoin_testnet/blockbook/db -sync -httpserver=:19030 -socketio=:19130 -certfile=/opt/coins/blockbook/bitcoin_testnet/cert/blockbook -explorer=https://btc-testnet-explorer.trezor.io/ -log_dir=/opt/coins/blockbook/bitcoin_testnet/logs
|
ExecStart=/opt/coins/blockbook/bitcoin_testnet/bin/blockbook -blockchaincfg=/opt/coins/blockbook/bitcoin_testnet/config/blockchaincfg.json -datadir=/opt/coins/data/bitcoin_testnet/blockbook/db -sync -internal=:19030 -public=:19130 -certfile=/opt/coins/blockbook/bitcoin_testnet/cert/blockbook -explorer=/explorer -log_dir=/opt/coins/blockbook/bitcoin_testnet/logs
|
||||||
User=blockbook-bitcoin
|
User=blockbook-bitcoin
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
@ -10,7 +10,7 @@ After=network.target
|
|||||||
Wants=backend-bitcoin.service
|
Wants=backend-bitcoin.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/opt/coins/blockbook/bitcoin/bin/blockbook -blockchaincfg=/opt/coins/blockbook/bitcoin/config/blockchaincfg.json -datadir=/opt/coins/data/bitcoin/blockbook/db -sync -httpserver=:9030 -socketio=:9130 -certfile=/opt/coins/blockbook/bitcoin/cert/blockbook -explorer=https://btc-explorer.trezor.io/ -log_dir=/opt/coins/blockbook/bitcoin/logs
|
ExecStart=/opt/coins/blockbook/bitcoin/bin/blockbook -blockchaincfg=/opt/coins/blockbook/bitcoin/config/blockchaincfg.json -datadir=/opt/coins/data/bitcoin/blockbook/db -sync -internal=:9030 -public=:9130 -certfile=/opt/coins/blockbook/bitcoin/cert/blockbook -explorer=/explorer -log_dir=/opt/coins/blockbook/bitcoin/logs
|
||||||
User=blockbook-bitcoin
|
User=blockbook-bitcoin
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
@ -10,7 +10,7 @@ After=network.target
|
|||||||
Wants=backend-dash-testnet.service
|
Wants=backend-dash-testnet.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/opt/coins/blockbook/dash_testnet/bin/blockbook -blockchaincfg=/opt/coins/blockbook/dash_testnet/config/blockchaincfg.json -datadir=/opt/coins/data/dash_testnet/blockbook/db -sync -httpserver=:19033 -socketio=:19133 -certfile=/opt/coins/blockbook/dash_testnet/cert/blockbook -explorer=https://dash-explorer.trezor.io/ -log_dir=/opt/coins/blockbook/dash_testnet/logs
|
ExecStart=/opt/coins/blockbook/dash_testnet/bin/blockbook -blockchaincfg=/opt/coins/blockbook/dash_testnet/config/blockchaincfg.json -datadir=/opt/coins/data/dash_testnet/blockbook/db -sync -internal=:19033 -public=:19133 -certfile=/opt/coins/blockbook/dash_testnet/cert/blockbook -explorer=https://dash-explorer.trezor.io/ -log_dir=/opt/coins/blockbook/dash_testnet/logs
|
||||||
User=blockbook-dash
|
User=blockbook-dash
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
@ -10,7 +10,7 @@ After=network.target
|
|||||||
Wants=backend-dash.service
|
Wants=backend-dash.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/opt/coins/blockbook/dash/bin/blockbook -blockchaincfg=/opt/coins/blockbook/dash/config/blockchaincfg.json -datadir=/opt/coins/data/dash/blockbook/db -sync -httpserver=:9033 -socketio=:9133 -certfile=/opt/coins/blockbook/dash/cert/blockbook -explorer=https://dash-explorer.trezor.io/ -log_dir=/opt/coins/blockbook/dash/logs
|
ExecStart=/opt/coins/blockbook/dash/bin/blockbook -blockchaincfg=/opt/coins/blockbook/dash/config/blockchaincfg.json -datadir=/opt/coins/data/dash/blockbook/db -sync -internal=:9033 -public=:9133 -certfile=/opt/coins/blockbook/dash/cert/blockbook -explorer=https://dash-explorer.trezor.io/ -log_dir=/opt/coins/blockbook/dash/logs
|
||||||
User=blockbook-dash
|
User=blockbook-dash
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
@ -10,7 +10,7 @@ After=network.target
|
|||||||
Wants=backend-dogecoin.service
|
Wants=backend-dogecoin.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/opt/coins/blockbook/dogecoin/bin/blockbook -blockchaincfg=/opt/coins/blockbook/dogecoin/config/blockchaincfg.json -datadir=/opt/coins/data/dogecoin/blockbook/db -sync -httpserver=:9038 -socketio=:9138 -certfile=/opt/coins/blockbook/dogecoin/cert/blockbook -explorer=https://dogechain.info/ -resyncindexperiod=30011 -resyncmempoolperiod=2011 -log_dir=/opt/coins/blockbook/dogecoin/logs
|
ExecStart=/opt/coins/blockbook/dogecoin/bin/blockbook -blockchaincfg=/opt/coins/blockbook/dogecoin/config/blockchaincfg.json -datadir=/opt/coins/data/dogecoin/blockbook/db -sync -internal=:9038 -public=:9138 -certfile=/opt/coins/blockbook/dogecoin/cert/blockbook -explorer=https://dogechain.info/ -resyncindexperiod=30011 -resyncmempoolperiod=2011 -log_dir=/opt/coins/blockbook/dogecoin/logs
|
||||||
User=blockbook-dogecoin
|
User=blockbook-dogecoin
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
@ -10,7 +10,7 @@ After=network.target
|
|||||||
Wants=backend-ethereum-testnet-ropsten.service
|
Wants=backend-ethereum-testnet-ropsten.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/opt/coins/blockbook/ethereum_testnet_ropsten/bin/blockbook -blockchaincfg=/opt/coins/blockbook/ethereum_testnet_ropsten/config/blockchaincfg.json -datadir=/opt/coins/data/ethereum_testnet_ropsten/blockbook/db -sync -httpserver=:19036 -socketio=:19136 -certfile=/opt/coins/blockbook/ethereum_testnet_ropsten/cert/blockbook -explorer=https://ropsten.etherscan.io/ -log_dir=/opt/coins/blockbook/ethereum_testnet_ropsten/logs
|
ExecStart=/opt/coins/blockbook/ethereum_testnet_ropsten/bin/blockbook -blockchaincfg=/opt/coins/blockbook/ethereum_testnet_ropsten/config/blockchaincfg.json -datadir=/opt/coins/data/ethereum_testnet_ropsten/blockbook/db -sync -internal=:19036 -public=:19136 -certfile=/opt/coins/blockbook/ethereum_testnet_ropsten/cert/blockbook -explorer=https://ropsten.etherscan.io/ -log_dir=/opt/coins/blockbook/ethereum_testnet_ropsten/logs
|
||||||
User=blockbook-ethereum
|
User=blockbook-ethereum
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
@ -10,7 +10,7 @@ After=network.target
|
|||||||
Wants=backend-ethereum.service
|
Wants=backend-ethereum.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/opt/coins/blockbook/ethereum/bin/blockbook -blockchaincfg=/opt/coins/blockbook/ethereum/config/blockchaincfg.json -datadir=/opt/coins/data/ethereum/blockbook/db -sync -httpserver=:9036 -socketio=:9136 -certfile=/opt/coins/blockbook/ethereum/cert/blockbook -explorer=https://etherscan.io/ -log_dir=/opt/coins/blockbook/ethereum/logs
|
ExecStart=/opt/coins/blockbook/ethereum/bin/blockbook -blockchaincfg=/opt/coins/blockbook/ethereum/config/blockchaincfg.json -datadir=/opt/coins/data/ethereum/blockbook/db -sync -internal=:9036 -public=:9136 -certfile=/opt/coins/blockbook/ethereum/cert/blockbook -explorer=https://etherscan.io/ -log_dir=/opt/coins/blockbook/ethereum/logs
|
||||||
User=blockbook-ethereum
|
User=blockbook-ethereum
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
@ -10,7 +10,7 @@ After=network.target
|
|||||||
Wants=backend-litecoin-testnet.service
|
Wants=backend-litecoin-testnet.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/opt/coins/blockbook/litecoin_testnet/bin/blockbook -blockchaincfg=/opt/coins/blockbook/litecoin_testnet/config/blockchaincfg.json -datadir=/opt/coins/data/litecoin_testnet/blockbook/db -sync -httpserver=:19034 -socketio=:19134 -certfile=/opt/coins/blockbook/litecoin_testnet/cert/blockbook -explorer=http://explorer.litecointools.com/ -log_dir=/opt/coins/blockbook/litecoin_testnet/logs
|
ExecStart=/opt/coins/blockbook/litecoin_testnet/bin/blockbook -blockchaincfg=/opt/coins/blockbook/litecoin_testnet/config/blockchaincfg.json -datadir=/opt/coins/data/litecoin_testnet/blockbook/db -sync -internal=:19034 -public=:19134 -certfile=/opt/coins/blockbook/litecoin_testnet/cert/blockbook -explorer=http://explorer.litecointools.com/ -log_dir=/opt/coins/blockbook/litecoin_testnet/logs
|
||||||
User=blockbook-litecoin
|
User=blockbook-litecoin
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
@ -10,7 +10,7 @@ After=network.target
|
|||||||
Wants=backend-litecoin.service
|
Wants=backend-litecoin.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/opt/coins/blockbook/litecoin/bin/blockbook -blockchaincfg=/opt/coins/blockbook/litecoin/config/blockchaincfg.json -datadir=/opt/coins/data/litecoin/blockbook/db -sync -httpserver=:9034 -socketio=:9134 -certfile=/opt/coins/blockbook/litecoin/cert/blockbook -explorer=https://ltc-explorer.trezor.io/ -log_dir=/opt/coins/blockbook/litecoin/logs
|
ExecStart=/opt/coins/blockbook/litecoin/bin/blockbook -blockchaincfg=/opt/coins/blockbook/litecoin/config/blockchaincfg.json -datadir=/opt/coins/data/litecoin/blockbook/db -sync -internal=:9034 -public=:9134 -certfile=/opt/coins/blockbook/litecoin/cert/blockbook -explorer=/explorer -log_dir=/opt/coins/blockbook/litecoin/logs
|
||||||
User=blockbook-litecoin
|
User=blockbook-litecoin
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
@ -10,7 +10,7 @@ After=network.target
|
|||||||
Wants=backend-namecoin.service
|
Wants=backend-namecoin.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/opt/coins/blockbook/namecoin/bin/blockbook -blockchaincfg=/opt/coins/blockbook/namecoin/config/blockchaincfg.json -datadir=/opt/coins/data/namecoin/blockbook/db -sync -httpserver=:9039 -socketio=:9139 -certfile=/opt/coins/blockbook/namecoin/cert/blockbook -explorer=https://namecha.in/ -log_dir=/opt/coins/blockbook/namecoin/logs
|
ExecStart=/opt/coins/blockbook/namecoin/bin/blockbook -blockchaincfg=/opt/coins/blockbook/namecoin/config/blockchaincfg.json -datadir=/opt/coins/data/namecoin/blockbook/db -sync -internal=:9039 -public=:9139 -certfile=/opt/coins/blockbook/namecoin/cert/blockbook -explorer=https://namecha.in/ -log_dir=/opt/coins/blockbook/namecoin/logs
|
||||||
User=blockbook-namecoin
|
User=blockbook-namecoin
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
@ -10,7 +10,7 @@ After=network.target
|
|||||||
Wants=backend-vertcoin-testnet.service
|
Wants=backend-vertcoin-testnet.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/opt/coins/blockbook/vertcoin_testnet/bin/blockbook -blockchaincfg=/opt/coins/blockbook/vertcoin_testnet/config/blockchaincfg.json -datadir=/opt/coins/data/vertcoin_testnet/blockbook/db -sync -httpserver=:19040 -socketio=:19140 -certfile=/opt/coins/blockbook/vertcoin_testnet/cert/blockbook -explorer=http://explorer.vertcointools.com/ -log_dir=/opt/coins/blockbook/vertcoin_testnet/logs
|
ExecStart=/opt/coins/blockbook/vertcoin_testnet/bin/blockbook -blockchaincfg=/opt/coins/blockbook/vertcoin_testnet/config/blockchaincfg.json -datadir=/opt/coins/data/vertcoin_testnet/blockbook/db -sync -internal=:19040 -public=:19140 -certfile=/opt/coins/blockbook/vertcoin_testnet/cert/blockbook -explorer=http://explorer.vertcointools.com/ -log_dir=/opt/coins/blockbook/vertcoin_testnet/logs
|
||||||
User=blockbook-vertcoin
|
User=blockbook-vertcoin
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
@ -10,7 +10,7 @@ After=network.target
|
|||||||
Wants=backend-vertcoin.service
|
Wants=backend-vertcoin.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/opt/coins/blockbook/vertcoin/bin/blockbook -blockchaincfg=/opt/coins/blockbook/vertcoin/config/blockchaincfg.json -datadir=/opt/coins/data/vertcoin/blockbook/db -sync -httpserver=:9040 -socketio=:9140 -certfile=/opt/coins/blockbook/vertcoin/cert/blockbook -explorer=https://insight.vertcoin.org/ -log_dir=/opt/coins/blockbook/vertcoin/logs
|
ExecStart=/opt/coins/blockbook/vertcoin/bin/blockbook -blockchaincfg=/opt/coins/blockbook/vertcoin/config/blockchaincfg.json -datadir=/opt/coins/data/vertcoin/blockbook/db -sync -internal=:9040 -public=:9140 -certfile=/opt/coins/blockbook/vertcoin/cert/blockbook -explorer=https://insight.vertcoin.org/ -log_dir=/opt/coins/blockbook/vertcoin/logs
|
||||||
User=blockbook-vertcoin
|
User=blockbook-vertcoin
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
@ -10,7 +10,7 @@ After=network.target
|
|||||||
Wants=backend-zcash-testnet.service
|
Wants=backend-zcash-testnet.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/opt/coins/blockbook/zcash_testnet/bin/blockbook -blockchaincfg=/opt/coins/blockbook/zcash_testnet/config/blockchaincfg.json -datadir=/opt/coins/data/zcash_testnet/blockbook/db -sync -httpserver=:19032 -socketio=:19132 -certfile=/opt/coins/blockbook/zcash_testnet/cert/blockbook -explorer=https://explorer.testnet.z.cash/ -log_dir=/opt/coins/blockbook/zcash_testnet/logs
|
ExecStart=/opt/coins/blockbook/zcash_testnet/bin/blockbook -blockchaincfg=/opt/coins/blockbook/zcash_testnet/config/blockchaincfg.json -datadir=/opt/coins/data/zcash_testnet/blockbook/db -sync -internal=:19032 -public=:19132 -certfile=/opt/coins/blockbook/zcash_testnet/cert/blockbook -explorer=https://explorer.testnet.z.cash/ -log_dir=/opt/coins/blockbook/zcash_testnet/logs
|
||||||
User=blockbook-zcash
|
User=blockbook-zcash
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
@ -10,7 +10,7 @@ After=network.target
|
|||||||
Wants=backend-zcash.service
|
Wants=backend-zcash.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
ExecStart=/opt/coins/blockbook/zcash/bin/blockbook -blockchaincfg=/opt/coins/blockbook/zcash/config/blockchaincfg.json -datadir=/opt/coins/data/zcash/blockbook/db -sync -httpserver=:9032 -socketio=:9132 -certfile=/opt/coins/blockbook/zcash/cert/blockbook -explorer=https://zcash.blockexplorer.com/ -log_dir=/opt/coins/blockbook/zcash/logs
|
ExecStart=/opt/coins/blockbook/zcash/bin/blockbook -blockchaincfg=/opt/coins/blockbook/zcash/config/blockchaincfg.json -datadir=/opt/coins/data/zcash/blockbook/db -sync -internal=:9032 -public=:9132 -certfile=/opt/coins/blockbook/zcash/cert/blockbook -explorer=https://zcash.blockexplorer.com/ -log_dir=/opt/coins/blockbook/zcash/logs
|
||||||
User=blockbook-zcash
|
User=blockbook-zcash
|
||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
|
|||||||
@ -27,8 +27,9 @@ type InternalStateColumn struct {
|
|||||||
type InternalState struct {
|
type InternalState struct {
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
|
|
||||||
Coin string `json:"coin"`
|
Coin string `json:"coin"`
|
||||||
Host string `json:"host"`
|
CoinShortcut string `json:"coinShortcut"`
|
||||||
|
Host string `json:"host"`
|
||||||
|
|
||||||
DbState uint32 `json:"dbState"`
|
DbState uint32 `json:"dbState"`
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"coin_name": "Bcash",
|
"coin_name": "Bcash",
|
||||||
|
"coin_shortcut": "BCH",
|
||||||
"rpcURL": "http://127.0.0.1:8031",
|
"rpcURL": "http://127.0.0.1:8031",
|
||||||
"rpcUser": "rpc",
|
"rpcUser": "rpc",
|
||||||
"rpcPass": "rpc",
|
"rpcPass": "rpc",
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"coin_name": "Bcash Testnet",
|
"coin_name": "Bcash Testnet",
|
||||||
|
"coin_shortcut": "TBCH",
|
||||||
"rpcURL": "http://localhost:18031",
|
"rpcURL": "http://localhost:18031",
|
||||||
"rpcUser": "rpc",
|
"rpcUser": "rpc",
|
||||||
"rpcPass": "rpc",
|
"rpcPass": "rpc",
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"coin_name": "Bgold",
|
"coin_name": "Bgold",
|
||||||
|
"coin_shortcut": "BTG",
|
||||||
"rpcURL": "http://127.0.0.1:8035",
|
"rpcURL": "http://127.0.0.1:8035",
|
||||||
"rpcUser": "rpc",
|
"rpcUser": "rpc",
|
||||||
"rpcPass": "rpc",
|
"rpcPass": "rpc",
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"coin_name": "Bitcoin",
|
"coin_name": "Bitcoin",
|
||||||
|
"coin_shortcut": "BTC",
|
||||||
"rpcURL": "http://127.0.0.1:8030",
|
"rpcURL": "http://127.0.0.1:8030",
|
||||||
"rpcUser": "rpc",
|
"rpcUser": "rpc",
|
||||||
"rpcPass": "rpc",
|
"rpcPass": "rpc",
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"coin_name": "Testnet",
|
"coin_name": "Testnet",
|
||||||
|
"coin_shortcut": "TEST",
|
||||||
"rpcURL": "http://localhost:18030",
|
"rpcURL": "http://localhost:18030",
|
||||||
"rpcUser": "rpc",
|
"rpcUser": "rpc",
|
||||||
"rpcPass": "rpc",
|
"rpcPass": "rpc",
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"coin_name": "Dash",
|
"coin_name": "Dash",
|
||||||
|
"coin_shortcut": "DASH",
|
||||||
"rpcURL": "http://127.0.0.1:8033",
|
"rpcURL": "http://127.0.0.1:8033",
|
||||||
"rpcUser": "rpc",
|
"rpcUser": "rpc",
|
||||||
"rpcPass": "rpc",
|
"rpcPass": "rpc",
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"coin_name": "Dash Testnet",
|
"coin_name": "Dash Testnet",
|
||||||
|
"coin_shortcut": "tDASH",
|
||||||
"rpcURL": "http://localhost:18033",
|
"rpcURL": "http://localhost:18033",
|
||||||
"rpcUser": "rpc",
|
"rpcUser": "rpc",
|
||||||
"rpcPass": "rpc",
|
"rpcPass": "rpc",
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"coin_name": "Dogecoin",
|
"coin_name": "Dogecoin",
|
||||||
|
"coin_shortcut": "DOGE",
|
||||||
"rpcURL": "http://127.0.0.1:8038",
|
"rpcURL": "http://127.0.0.1:8038",
|
||||||
"rpcUser": "rpc",
|
"rpcUser": "rpc",
|
||||||
"rpcPass": "rpcp",
|
"rpcPass": "rpcp",
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"coin_name": "Ethereum",
|
"coin_name": "Ethereum",
|
||||||
|
"coin_shortcut": "ETH",
|
||||||
"rpcURL": "ws://localhost:8036",
|
"rpcURL": "ws://localhost:8036",
|
||||||
"rpcTimeout": 25
|
"rpcTimeout": 25
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"coin_name": "Ethereum Testnet Ropsten",
|
"coin_name": "Ethereum Testnet Ropsten",
|
||||||
|
"coin_shortcut": "tETH",
|
||||||
"rpcURL": "ws://localhost:18036",
|
"rpcURL": "ws://localhost:18036",
|
||||||
"rpcTimeout": 25
|
"rpcTimeout": 25
|
||||||
}
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"coin_name": "Litecoin",
|
"coin_name": "Litecoin",
|
||||||
|
"coin_shortcut": "LTC",
|
||||||
"rpcURL": "http://localhost:8034",
|
"rpcURL": "http://localhost:8034",
|
||||||
"rpcUser": "rpc",
|
"rpcUser": "rpc",
|
||||||
"rpcPass": "rpc",
|
"rpcPass": "rpc",
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"coin_name": "Litecoin Testnet",
|
"coin_name": "Litecoin Testnet",
|
||||||
|
"coin_shortcut": "TLTC",
|
||||||
"rpcURL": "http://localhost:18034",
|
"rpcURL": "http://localhost:18034",
|
||||||
"rpcUser": "rpc",
|
"rpcUser": "rpc",
|
||||||
"rpcPass": "rpc",
|
"rpcPass": "rpc",
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"coin_name": "Namecoin",
|
"coin_name": "Namecoin",
|
||||||
|
"coin_shortcut": "NMC",
|
||||||
"rpcURL": "http://127.0.0.1:8039",
|
"rpcURL": "http://127.0.0.1:8039",
|
||||||
"rpcUser": "rpc",
|
"rpcUser": "rpc",
|
||||||
"rpcPass": "rpc",
|
"rpcPass": "rpc",
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"coin_name": "Vertcoin",
|
"coin_name": "Vertcoin",
|
||||||
|
"coin_shortcut": "VTC",
|
||||||
"rpcURL": "http://localhost:8040",
|
"rpcURL": "http://localhost:8040",
|
||||||
"rpcUser": "rpc",
|
"rpcUser": "rpc",
|
||||||
"rpcPass": "rpc",
|
"rpcPass": "rpc",
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"coin_name": "Vertcoin Testnet",
|
"coin_name": "Vertcoin Testnet",
|
||||||
|
"coin_shortcut": "tVTC",
|
||||||
"rpcURL": "http://localhost:18040",
|
"rpcURL": "http://localhost:18040",
|
||||||
"rpcUser": "rpc",
|
"rpcUser": "rpc",
|
||||||
"rpcPass": "rpc",
|
"rpcPass": "rpc",
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"coin_name": "Zcash",
|
"coin_name": "Zcash",
|
||||||
|
"coin_shortcut": "ZEC",
|
||||||
"rpcURL": "http://127.0.0.1:8032",
|
"rpcURL": "http://127.0.0.1:8032",
|
||||||
"rpcUser": "rpc",
|
"rpcUser": "rpc",
|
||||||
"rpcPass": "rpc",
|
"rpcPass": "rpc",
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"coin_name": "Zcash Testnet",
|
"coin_name": "Zcash Testnet",
|
||||||
|
"coin_shortcut": "TAZ",
|
||||||
"rpcURL": "http://127.0.0.1:18032",
|
"rpcURL": "http://127.0.0.1:18032",
|
||||||
"rpcUser": "rpc",
|
"rpcUser": "rpc",
|
||||||
"rpcPass": "rpc",
|
"rpcPass": "rpc",
|
||||||
|
|||||||
@ -18,8 +18,8 @@ import (
|
|||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTPServer is handle to HttpServer
|
// InternalServer is handle to internal http server
|
||||||
type HTTPServer struct {
|
type InternalServer struct {
|
||||||
https *http.Server
|
https *http.Server
|
||||||
certFiles string
|
certFiles string
|
||||||
db *db.RocksDB
|
db *db.RocksDB
|
||||||
@ -44,14 +44,14 @@ type resAboutBlockbookInternal struct {
|
|||||||
DbColumns []common.InternalStateColumn `json:"dbColumns"`
|
DbColumns []common.InternalStateColumn `json:"dbColumns"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHTTPServer creates new REST interface to blockbook and returns its handle
|
// NewInternalServer creates new internal http interface to blockbook and returns its handle
|
||||||
func NewHTTPServer(httpServerBinding string, certFiles string, db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is *common.InternalState) (*HTTPServer, error) {
|
func NewInternalServer(httpServerBinding string, certFiles string, db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is *common.InternalState) (*InternalServer, error) {
|
||||||
r := mux.NewRouter()
|
r := mux.NewRouter()
|
||||||
https := &http.Server{
|
https := &http.Server{
|
||||||
Addr: httpServerBinding,
|
Addr: httpServerBinding,
|
||||||
Handler: r,
|
Handler: r,
|
||||||
}
|
}
|
||||||
s := &HTTPServer{
|
s := &InternalServer{
|
||||||
https: https,
|
https: https,
|
||||||
certFiles: certFiles,
|
certFiles: certFiles,
|
||||||
db: db,
|
db: db,
|
||||||
@ -73,30 +73,30 @@ func NewHTTPServer(httpServerBinding string, certFiles string, db *db.RocksDB, c
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run starts the server
|
// Run starts the server
|
||||||
func (s *HTTPServer) Run() error {
|
func (s *InternalServer) Run() error {
|
||||||
if s.certFiles == "" {
|
if s.certFiles == "" {
|
||||||
glog.Info("http server starting to listen on http://", s.https.Addr)
|
glog.Info("internal server: starting to listen on http://", s.https.Addr)
|
||||||
return s.https.ListenAndServe()
|
return s.https.ListenAndServe()
|
||||||
}
|
}
|
||||||
glog.Info("http server starting to listen on https://", s.https.Addr)
|
glog.Info("internal server: starting to listen on https://", s.https.Addr)
|
||||||
return s.https.ListenAndServeTLS(fmt.Sprint(s.certFiles, ".crt"), fmt.Sprint(s.certFiles, ".key"))
|
return s.https.ListenAndServeTLS(fmt.Sprint(s.certFiles, ".crt"), fmt.Sprint(s.certFiles, ".key"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the server
|
// Close closes the server
|
||||||
func (s *HTTPServer) Close() error {
|
func (s *InternalServer) Close() error {
|
||||||
glog.Infof("http server closing")
|
glog.Infof("internal server: closing")
|
||||||
return s.https.Close()
|
return s.https.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown shuts down the server
|
// Shutdown shuts down the server
|
||||||
func (s *HTTPServer) Shutdown(ctx context.Context) error {
|
func (s *InternalServer) Shutdown(ctx context.Context) error {
|
||||||
glog.Infof("http server shutdown")
|
glog.Infof("internal server: shutdown")
|
||||||
return s.https.Shutdown(ctx)
|
return s.https.Shutdown(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func respondError(w http.ResponseWriter, err error, context string) {
|
func respondError(w http.ResponseWriter, err error, context string) {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
glog.Errorf("http server (context %s) error: %v", context, err)
|
glog.Errorf("internal server: (context %s) error: %v", context, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func respondHashData(w http.ResponseWriter, hash string) {
|
func respondHashData(w http.ResponseWriter, hash string) {
|
||||||
@ -108,7 +108,7 @@ func respondHashData(w http.ResponseWriter, hash string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPServer) index(w http.ResponseWriter, r *http.Request) {
|
func (s *InternalServer) index(w http.ResponseWriter, r *http.Request) {
|
||||||
vi := common.GetVersionInfo()
|
vi := common.GetVersionInfo()
|
||||||
ss, bh, st := s.is.GetSyncState()
|
ss, bh, st := s.is.GetSyncState()
|
||||||
ms, mt, msz := s.is.GetMempoolSyncState()
|
ms, mt, msz := s.is.GetMempoolSyncState()
|
||||||
@ -133,7 +133,7 @@ func (s *HTTPServer) index(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write(buf)
|
w.Write(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPServer) bestBlockHash(w http.ResponseWriter, r *http.Request) {
|
func (s *InternalServer) bestBlockHash(w http.ResponseWriter, r *http.Request) {
|
||||||
_, hash, err := s.db.GetBestBlock()
|
_, hash, err := s.db.GetBestBlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, err, "bestBlockHash")
|
respondError(w, err, "bestBlockHash")
|
||||||
@ -142,7 +142,7 @@ func (s *HTTPServer) bestBlockHash(w http.ResponseWriter, r *http.Request) {
|
|||||||
respondHashData(w, hash)
|
respondHashData(w, hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPServer) blockHash(w http.ResponseWriter, r *http.Request) {
|
func (s *InternalServer) blockHash(w http.ResponseWriter, r *http.Request) {
|
||||||
heightString := mux.Vars(r)["height"]
|
heightString := mux.Vars(r)["height"]
|
||||||
var hash string
|
var hash string
|
||||||
height, err := strconv.ParseUint(heightString, 10, 32)
|
height, err := strconv.ParseUint(heightString, 10, 32)
|
||||||
@ -156,7 +156,7 @@ func (s *HTTPServer) blockHash(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPServer) getAddress(r *http.Request) (address string, err error) {
|
func (s *InternalServer) getAddress(r *http.Request) (address string, err error) {
|
||||||
address, ok := mux.Vars(r)["address"]
|
address, ok := mux.Vars(r)["address"]
|
||||||
if !ok {
|
if !ok {
|
||||||
err = errors.New("Empty address")
|
err = errors.New("Empty address")
|
||||||
@ -164,7 +164,7 @@ func (s *HTTPServer) getAddress(r *http.Request) (address string, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPServer) getAddressAndHeightRange(r *http.Request) (address string, lower, higher uint32, err error) {
|
func (s *InternalServer) getAddressAndHeightRange(r *http.Request) (address string, lower, higher uint32, err error) {
|
||||||
address, err = s.getAddress(r)
|
address, err = s.getAddress(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -184,7 +184,7 @@ type transactionList struct {
|
|||||||
Txid []string `json:"txid"`
|
Txid []string `json:"txid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPServer) unconfirmedTransactions(w http.ResponseWriter, r *http.Request) {
|
func (s *InternalServer) unconfirmedTransactions(w http.ResponseWriter, r *http.Request) {
|
||||||
address, err := s.getAddress(r)
|
address, err := s.getAddress(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, err, fmt.Sprint("unconfirmedTransactions for address", address))
|
respondError(w, err, fmt.Sprint("unconfirmedTransactions for address", address))
|
||||||
@ -197,7 +197,7 @@ func (s *HTTPServer) unconfirmedTransactions(w http.ResponseWriter, r *http.Requ
|
|||||||
json.NewEncoder(w).Encode(txList)
|
json.NewEncoder(w).Encode(txList)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPServer) confirmedTransactions(w http.ResponseWriter, r *http.Request) {
|
func (s *InternalServer) confirmedTransactions(w http.ResponseWriter, r *http.Request) {
|
||||||
address, lower, higher, err := s.getAddressAndHeightRange(r)
|
address, lower, higher, err := s.getAddressAndHeightRange(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, err, fmt.Sprint("confirmedTransactions for address", address))
|
respondError(w, err, fmt.Sprint("confirmedTransactions for address", address))
|
||||||
@ -213,7 +213,7 @@ func (s *HTTPServer) confirmedTransactions(w http.ResponseWriter, r *http.Reques
|
|||||||
json.NewEncoder(w).Encode(txList)
|
json.NewEncoder(w).Encode(txList)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *HTTPServer) transactions(w http.ResponseWriter, r *http.Request) {
|
func (s *InternalServer) transactions(w http.ResponseWriter, r *http.Request) {
|
||||||
address, lower, higher, err := s.getAddressAndHeightRange(r)
|
address, lower, higher, err := s.getAddressAndHeightRange(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, err, fmt.Sprint("transactions for address", address))
|
respondError(w, err, fmt.Sprint("transactions for address", address))
|
||||||
361
server/public.go
Normal file
361
server/public.go
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"blockbook/api"
|
||||||
|
"blockbook/bchain"
|
||||||
|
"blockbook/common"
|
||||||
|
"blockbook/db"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
)
|
||||||
|
|
||||||
|
const blockbookAbout = "Blockbook - blockchain indexer for TREZOR wallet https://trezor.io/. Do not use for any other purpose."
|
||||||
|
|
||||||
|
// PublicServer is 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
|
||||||
|
txTpl *template.Template
|
||||||
|
addressTpl *template.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPublicServerS creates new public server http interface to blockbook and returns its handle
|
||||||
|
func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, explorerURL string, metrics *common.Metrics, is *common.InternalState) (*PublicServer, error) {
|
||||||
|
|
||||||
|
api, err := api.NewWorker(db, chain, txCache, is)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
socketio, err := NewSocketIoServer(db, chain, txCache, metrics, is)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, path := splitBinding(binding)
|
||||||
|
serveMux := http.NewServeMux()
|
||||||
|
https := &http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
Handler: serveMux,
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 Bitcore for details of transaction
|
||||||
|
serveMux.HandleFunc(path+"tx/", s.txRedirect)
|
||||||
|
serveMux.HandleFunc(path+"address/", s.addressRedirect)
|
||||||
|
// explorer
|
||||||
|
serveMux.HandleFunc(path+"explorer/tx/", s.explorerTx)
|
||||||
|
serveMux.HandleFunc(path+"explorer/address/", s.explorerAddress)
|
||||||
|
serveMux.Handle(path+"static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
|
||||||
|
// API calls
|
||||||
|
serveMux.HandleFunc(path+"api/block-index/", s.apiBlockIndex)
|
||||||
|
serveMux.HandleFunc(path+"api/tx/", s.apiTx)
|
||||||
|
serveMux.HandleFunc(path+"api/address/", s.apiAddress)
|
||||||
|
// handle socket.io
|
||||||
|
serveMux.Handle(path+"socket.io/", socketio.GetHandler())
|
||||||
|
// default handler
|
||||||
|
serveMux.HandleFunc(path, s.index)
|
||||||
|
|
||||||
|
s.txTpl, s.addressTpl = parseTemplates()
|
||||||
|
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTemplates() (txTpl, addressTpl *template.Template) {
|
||||||
|
templateFuncMap := template.FuncMap{
|
||||||
|
"formatUnixTime": formatUnixTime,
|
||||||
|
"formatAmount": formatAmount,
|
||||||
|
"setTxToTemplateData": setTxToTemplateData,
|
||||||
|
"stringInSlice": stringInSlice,
|
||||||
|
}
|
||||||
|
txTpl = template.Must(template.New("tx").Funcs(templateFuncMap).ParseFiles("./static/templates/tx.html", "./static/templates/txdetail.html", "./static/templates/base.html"))
|
||||||
|
addressTpl = template.Must(template.New("address").Funcs(templateFuncMap).ParseFiles("./static/templates/address.html", "./static/templates/txdetail.html", "./static/templates/base.html"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatUnixTime(ut int64) string {
|
||||||
|
return time.Unix(ut, 0).Format(time.RFC1123)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatAmount(a float64) string {
|
||||||
|
return strings.TrimRight(strings.TrimRight(fmt.Sprintf("%0.8f", a), "0"), ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run starts the server
|
||||||
|
func (s *PublicServer) Run() error {
|
||||||
|
if s.certFiles == "" {
|
||||||
|
glog.Info("public server: starting to listen on http://", s.https.Addr)
|
||||||
|
return s.https.ListenAndServe()
|
||||||
|
}
|
||||||
|
glog.Info("public server starting to listen on https://", s.https.Addr)
|
||||||
|
return s.https.ListenAndServeTLS(fmt.Sprint(s.certFiles, ".crt"), fmt.Sprint(s.certFiles, ".key"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the server
|
||||||
|
func (s *PublicServer) Close() error {
|
||||||
|
glog.Infof("public server: closing")
|
||||||
|
return s.https.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown shuts down the server
|
||||||
|
func (s *PublicServer) Shutdown(ctx context.Context) error {
|
||||||
|
glog.Infof("public server: shutdown")
|
||||||
|
return s.https.Shutdown(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnNewBlockHash notifies users subscribed to bitcoind/hashblock about new block
|
||||||
|
func (s *PublicServer) OnNewBlockHash(hash string) {
|
||||||
|
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 splitBinding(binding string) (addr string, path string) {
|
||||||
|
i := strings.Index(binding, "/")
|
||||||
|
if i >= 0 {
|
||||||
|
return binding[0:i], binding[i:]
|
||||||
|
}
|
||||||
|
return binding, "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinURL(base string, part string) string {
|
||||||
|
if len(base) > 0 {
|
||||||
|
if len(base) > 0 && base[len(base)-1] == '/' && len(part) > 0 && part[0] == '/' {
|
||||||
|
return base + part[1:]
|
||||||
|
}
|
||||||
|
return base + part
|
||||||
|
}
|
||||||
|
return part
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type TemplateData struct {
|
||||||
|
CoinName string
|
||||||
|
CoinShortcut string
|
||||||
|
Address *api.Address
|
||||||
|
AddrStr string
|
||||||
|
Tx *api.Tx
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTxToTemplateData(td *TemplateData, tx *api.Tx) *TemplateData {
|
||||||
|
td.Tx = tx
|
||||||
|
return td
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var tx *api.Tx
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
glog.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
|
||||||
|
// temporarily reread the template on each request
|
||||||
|
// to reflect changes during development
|
||||||
|
s.txTpl, s.addressTpl = parseTemplates()
|
||||||
|
|
||||||
|
data := &TemplateData{
|
||||||
|
CoinName: s.is.Coin,
|
||||||
|
CoinShortcut: s.is.CoinShortcut,
|
||||||
|
Tx: tx,
|
||||||
|
}
|
||||||
|
if err := s.txTpl.ExecuteTemplate(w, "base.html", data); err != nil {
|
||||||
|
glog.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var address *api.Address
|
||||||
|
var err error
|
||||||
|
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||||
|
page, ec := strconv.Atoi(r.URL.Query().Get("page"))
|
||||||
|
if ec != nil {
|
||||||
|
page = 0
|
||||||
|
}
|
||||||
|
addrID := r.URL.Path[i+1:]
|
||||||
|
address, err = s.api.GetAddress(addrID, page)
|
||||||
|
if err != nil {
|
||||||
|
glog.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
|
||||||
|
// temporarily reread the template on each request
|
||||||
|
// to reflect changes during development
|
||||||
|
s.txTpl, s.addressTpl = parseTemplates()
|
||||||
|
|
||||||
|
data := &TemplateData{
|
||||||
|
CoinName: s.is.Coin,
|
||||||
|
CoinShortcut: s.is.CoinShortcut,
|
||||||
|
AddrStr: address.AddrStr,
|
||||||
|
Address: address,
|
||||||
|
}
|
||||||
|
if err := s.addressTpl.ExecuteTemplate(w, "base.html", data); err != nil {
|
||||||
|
glog.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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) apiBlockIndex(w http.ResponseWriter, r *http.Request) {
|
||||||
|
type resBlockIndex struct {
|
||||||
|
BlockHash string `json:"blockHash"`
|
||||||
|
About string `json:"about"`
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var hash string
|
||||||
|
height := -1
|
||||||
|
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||||
|
if h, err := strconv.Atoi(r.URL.Path[i+1:]); err == nil {
|
||||||
|
height = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if height >= 0 {
|
||||||
|
hash, err = s.db.GetBlockHash(uint32(height))
|
||||||
|
} else {
|
||||||
|
_, hash, err = s.db.GetBestBlock()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
glog.Error(err)
|
||||||
|
} else {
|
||||||
|
r := resBlockIndex{
|
||||||
|
BlockHash: hash,
|
||||||
|
About: blockbookAbout,
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
json.NewEncoder(w).Encode(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublicServer) apiTx(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var tx *api.Tx
|
||||||
|
var err error
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
glog.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
json.NewEncoder(w).Encode(tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublicServer) apiAddress(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var address *api.Address
|
||||||
|
var err error
|
||||||
|
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||||
|
page, ec := strconv.Atoi(r.URL.Query().Get("page"))
|
||||||
|
if ec != nil {
|
||||||
|
page = 0
|
||||||
|
}
|
||||||
|
addrID := r.URL.Path[i+1:]
|
||||||
|
address, err = s.api.GetAddress(addrID, page)
|
||||||
|
if err != nil {
|
||||||
|
glog.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
json.NewEncoder(w).Encode(address)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,14 +1,12 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"blockbook/api"
|
||||||
"blockbook/bchain"
|
"blockbook/bchain"
|
||||||
"blockbook/common"
|
"blockbook/common"
|
||||||
"blockbook/db"
|
"blockbook/db"
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -19,25 +17,19 @@ import (
|
|||||||
"github.com/martinboehm/golang-socketio/transport"
|
"github.com/martinboehm/golang-socketio/transport"
|
||||||
)
|
)
|
||||||
|
|
||||||
const blockbookAbout = "Blockbook - blockchain indexer for TREZOR wallet https://trezor.io/. Do not use for any other purpose."
|
|
||||||
|
|
||||||
// SocketIoServer is handle to SocketIoServer
|
// SocketIoServer is handle to SocketIoServer
|
||||||
type SocketIoServer struct {
|
type SocketIoServer struct {
|
||||||
binding string
|
|
||||||
certFiles string
|
|
||||||
server *gosocketio.Server
|
server *gosocketio.Server
|
||||||
https *http.Server
|
|
||||||
db *db.RocksDB
|
db *db.RocksDB
|
||||||
txCache *db.TxCache
|
txCache *db.TxCache
|
||||||
chain bchain.BlockChain
|
chain bchain.BlockChain
|
||||||
chainParser bchain.BlockChainParser
|
chainParser bchain.BlockChainParser
|
||||||
explorerURL string
|
|
||||||
metrics *common.Metrics
|
metrics *common.Metrics
|
||||||
is *common.InternalState
|
is *common.InternalState
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSocketIoServer creates new SocketIo interface to blockbook and returns its handle
|
// NewSocketIoServer creates new SocketIo interface to blockbook and returns its handle
|
||||||
func NewSocketIoServer(binding string, certFiles string, db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, explorerURL string, metrics *common.Metrics, is *common.InternalState) (*SocketIoServer, error) {
|
func NewSocketIoServer(db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, metrics *common.Metrics, is *common.InternalState) (*SocketIoServer, error) {
|
||||||
server := gosocketio.NewServer(transport.GetDefaultWebsocketTransport())
|
server := gosocketio.NewServer(transport.GetDefaultWebsocketTransport())
|
||||||
|
|
||||||
server.On(gosocketio.OnConnection, func(c *gosocketio.Channel) {
|
server.On(gosocketio.OnConnection, func(c *gosocketio.Channel) {
|
||||||
@ -58,166 +50,25 @@ func NewSocketIoServer(binding string, certFiles string, db *db.RocksDB, chain b
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, path := splitBinding(binding)
|
|
||||||
serveMux := http.NewServeMux()
|
|
||||||
https := &http.Server{
|
|
||||||
Addr: addr,
|
|
||||||
Handler: serveMux,
|
|
||||||
}
|
|
||||||
|
|
||||||
s := &SocketIoServer{
|
s := &SocketIoServer{
|
||||||
binding: binding,
|
|
||||||
certFiles: certFiles,
|
|
||||||
https: https,
|
|
||||||
server: server,
|
server: server,
|
||||||
db: db,
|
db: db,
|
||||||
txCache: txCache,
|
txCache: txCache,
|
||||||
chain: chain,
|
chain: chain,
|
||||||
chainParser: chain.GetChainParser(),
|
chainParser: chain.GetChainParser(),
|
||||||
explorerURL: explorerURL,
|
|
||||||
metrics: metrics,
|
metrics: metrics,
|
||||||
is: is,
|
is: is,
|
||||||
}
|
}
|
||||||
|
|
||||||
// support for tests of socket.io interface
|
|
||||||
serveMux.Handle(path+"test.html", http.FileServer(http.Dir("./static/")))
|
|
||||||
// redirect to Bitcore for details of transaction
|
|
||||||
serveMux.HandleFunc(path+"tx/", s.txRedirect)
|
|
||||||
serveMux.HandleFunc(path+"address/", s.addressRedirect)
|
|
||||||
// API call used to detect state of Blockbook
|
|
||||||
serveMux.HandleFunc(path+"api/block-index/", s.apiBlockIndex)
|
|
||||||
// handle socket.io
|
|
||||||
serveMux.Handle(path+"socket.io/", server)
|
|
||||||
// default handler
|
|
||||||
serveMux.HandleFunc(path, s.index)
|
|
||||||
|
|
||||||
server.On("message", s.onMessage)
|
server.On("message", s.onMessage)
|
||||||
server.On("subscribe", s.onSubscribe)
|
server.On("subscribe", s.onSubscribe)
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitBinding(binding string) (addr string, path string) {
|
// GetHandler returns socket.io http handler
|
||||||
i := strings.Index(binding, "/")
|
func (s *SocketIoServer) GetHandler() http.Handler {
|
||||||
if i >= 0 {
|
return s.server
|
||||||
return binding[0:i], binding[i:]
|
|
||||||
}
|
|
||||||
return binding, "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run starts the server
|
|
||||||
func (s *SocketIoServer) Run() error {
|
|
||||||
if s.certFiles == "" {
|
|
||||||
glog.Info("socketio server starting to listen on ws://", s.https.Addr)
|
|
||||||
return s.https.ListenAndServe()
|
|
||||||
}
|
|
||||||
glog.Info("socketio server starting to listen on wss://", s.https.Addr)
|
|
||||||
return s.https.ListenAndServeTLS(fmt.Sprint(s.certFiles, ".crt"), fmt.Sprint(s.certFiles, ".key"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the server
|
|
||||||
func (s *SocketIoServer) Close() error {
|
|
||||||
glog.Infof("socketio server closing")
|
|
||||||
return s.https.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown shuts down the server
|
|
||||||
func (s *SocketIoServer) Shutdown(ctx context.Context) error {
|
|
||||||
glog.Infof("socketio server shutdown")
|
|
||||||
return s.https.Shutdown(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func joinURL(base string, part string) string {
|
|
||||||
if len(base) > 0 {
|
|
||||||
if len(base) > 0 && base[len(base)-1] == '/' && len(part) > 0 && part[0] == '/' {
|
|
||||||
return base + part[1:]
|
|
||||||
} else {
|
|
||||||
return base + part
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return part
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SocketIoServer) 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SocketIoServer) 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SocketIoServer) 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,
|
|
||||||
}
|
|
||||||
buf, err := json.MarshalIndent(a, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
glog.Error(err)
|
|
||||||
}
|
|
||||||
w.Write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SocketIoServer) apiBlockIndex(w http.ResponseWriter, r *http.Request) {
|
|
||||||
type resBlockIndex struct {
|
|
||||||
BlockHash string `json:"blockHash"`
|
|
||||||
About string `json:"about"`
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
var hash string
|
|
||||||
height := -1
|
|
||||||
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
|
||||||
if h, err := strconv.Atoi(r.URL.Path[i+1:]); err == nil {
|
|
||||||
height = h
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if height >= 0 {
|
|
||||||
hash, err = s.db.GetBlockHash(uint32(height))
|
|
||||||
} else {
|
|
||||||
_, hash, err = s.db.GetBestBlock()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
glog.Error(err)
|
|
||||||
} else {
|
|
||||||
r := resBlockIndex{
|
|
||||||
BlockHash: hash,
|
|
||||||
About: blockbookAbout,
|
|
||||||
}
|
|
||||||
json.NewEncoder(w).Encode(r)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type addrOpts struct {
|
type addrOpts struct {
|
||||||
@ -346,22 +197,6 @@ func unmarshalGetAddressRequest(params []byte) (addr []string, opts addrOpts, er
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// bitcore returns txids from the newest to the oldest, we have to revert the order
|
|
||||||
func uniqueTxidsInReverse(txids []string) []string {
|
|
||||||
i := len(txids)
|
|
||||||
ut := make([]string, i)
|
|
||||||
txidsMap := make(map[string]struct{})
|
|
||||||
for _, txid := range txids {
|
|
||||||
_, e := txidsMap[txid]
|
|
||||||
if !e {
|
|
||||||
i--
|
|
||||||
ut[i] = txid
|
|
||||||
txidsMap[txid] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ut[i:]
|
|
||||||
}
|
|
||||||
|
|
||||||
type resultAddressTxids struct {
|
type resultAddressTxids struct {
|
||||||
Result []string `json:"result"`
|
Result []string `json:"result"`
|
||||||
}
|
}
|
||||||
@ -386,7 +221,7 @@ func (s *SocketIoServer) getAddressTxids(addr []string, opts *addrOpts) (res res
|
|||||||
txids = append(txids, m...)
|
txids = append(txids, m...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
res.Result = uniqueTxidsInReverse(txids)
|
res.Result = api.UniqueTxidsInReverse(txids)
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -748,7 +583,7 @@ func (s *SocketIoServer) getDetailedTransaction(txid string) (res resultGetDetai
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
if len(otx.Vout) > int(vin.Vout) {
|
if len(otx.Vout) > int(vin.Vout) {
|
||||||
vout := otx.Vout[vin.Vout]
|
vout := &otx.Vout[vin.Vout]
|
||||||
if vout.Address != nil {
|
if vout.Address != nil {
|
||||||
a := vout.Address.String()
|
a := vout.Address.String()
|
||||||
ai.Address = &a
|
ai.Address = &a
|
||||||
|
|||||||
178
static/css/main.css
Normal file
178
static/css/main.css
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
background-color: #ffffff;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", Arial, sans-serif;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #428bca;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.octicon {
|
||||||
|
color: #777;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: text-top;
|
||||||
|
fill: currentColor;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.container {
|
||||||
|
max-width: 750px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
body {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 970px;
|
||||||
|
}
|
||||||
|
.octicon {
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
body {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 1170px;
|
||||||
|
}
|
||||||
|
.octicon {
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#header {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 53px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
background-color: #212121;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-trezor {
|
||||||
|
background-color: #212121!important;
|
||||||
|
height: 53px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-trezor .navbar-text {
|
||||||
|
color: rgba(255, 255, 255);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 19px;
|
||||||
|
padding-left: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trezor-logo-svg-white svg {
|
||||||
|
fill: #FFFFFF;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#wrap {
|
||||||
|
min-height: 100%;
|
||||||
|
height: auto;
|
||||||
|
padding: 75px 0;
|
||||||
|
margin: 0 auto -53px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer {
|
||||||
|
background-color: #212121;
|
||||||
|
color: #fff;
|
||||||
|
height: 53px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-data {
|
||||||
|
color: #383d41;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
border-color: #d6d8db;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-top {
|
||||||
|
border-top: 1px solid #EAEAEA;
|
||||||
|
padding: 15px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-mid {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line-bot {
|
||||||
|
border-bottom: 2px solid #EAEAEA;
|
||||||
|
padding: 0 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.txvalues {
|
||||||
|
display: inline-block;
|
||||||
|
padding: .7em 2em;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 100;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: baseline;
|
||||||
|
border-radius: .25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.txvalues-default {
|
||||||
|
background-color: #EBEBEB;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.txvalues-success {
|
||||||
|
background-color: dimgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.txvalues-primary {
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.txvalues-danger {
|
||||||
|
background-color: #AC0015;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ellipsis {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-div {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table {
|
||||||
|
table-layout: fixed;
|
||||||
|
border-radius: .25rem;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table td, .data-table th {
|
||||||
|
padding: .4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert .data-table {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
BIN
static/favicon.ico
Normal file
BIN
static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 856 B |
52
static/templates/address.html
Normal file
52
static/templates/address.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{{define "specific"}}{{$cs := .CoinShortcut}}{{$addr := .Address}}{{$data := .}}
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
{{if $addr.UnconfirmedTxApperances}}
|
||||||
|
<h3>Unconfirmed</h3>
|
||||||
|
<div class="data-div">
|
||||||
|
<table class="table data-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 25%;">Unconfirmed Balance</td>
|
||||||
|
<td class="data">{{formatAmount $addr.UnconfirmedBalance}} {{$cs}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>No. Transactions</td>
|
||||||
|
<td class="data">{{$addr.UnconfirmedTxApperances}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{{end}} {{if $addr.Transactions}}
|
||||||
|
<h3>Transactions</h3>
|
||||||
|
<div class="data-div">
|
||||||
|
{{range $tx := $addr.Transactions}}{{$data := setTxToTemplateData $data $tx}}{{template "txdetail" $data }}{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}} {{end}}
|
||||||
75
static/templates/base.html
Normal file
75
static/templates/base.html
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0,shrink-to-fit=no">
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="/static/css/main.css">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="description" content="TREZOR {{.CoinName}} Explorer">
|
||||||
|
<title>TREZOR {{.CoinName}} Explorer</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<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">
|
||||||
|
<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>
|
||||||
|
<path d="M158.8,26.9c2.1-0.8,4.3-2.9,4.3-6.6c0-4.5-3.1-7.4-7.7-7.4h-10.5v22.3h5.8v-7.5h2.2l4.1,7.5h6.7L158.8,26.9z M154.7,22.5h-4V18h4c1.5,0,2.5,0.9,2.5,2.2C157.2,21.6,156.2,22.5,154.7,22.5z"></path>
|
||||||
|
<path d="M130.8,12.5c-6.8,0-11.6,4.9-11.6,11.5s4.9,11.5,11.6,11.5s11.7-4.9,11.7-11.5S137.6,12.5,130.8,12.5z M130.8,30.3c-3.4,0-5.7-2.6-5.7-6.3c0-3.8,2.3-6.3,5.7-6.3c3.4,0,5.8,2.6,5.8,6.3C136.6,27.7,134.2,30.3,130.8,30.3z"></path>
|
||||||
|
<polygon points="82.1,12.8 98.3,12.8 98.3,18 87.9,18 87.9,21.3 98,21.3 98,26.4 87.9,26.4 87.9,30 98.3,30 98.3,35.2 82.1,35.2"></polygon>
|
||||||
|
<path d="M24.6,9.7C24.6,4.4,20,0,14.4,0S4.2,4.4,4.2,9.7v3.1H0v22.3h0l14.4,6.7l14.4-6.7h0V12.9h-4.2V9.7z M9.4,9.7c0-2.5,2.2-4.5,5-4.5s5,2,5,4.5v3.1H9.4V9.7z M23,31.5l-8.6,4l-8.6-4V18.1H23V31.5z"></path>
|
||||||
|
<path d="M79.4,20.3c0-4.5-3.1-7.4-7.7-7.4H61.2v22.3H67v-7.5h2.2l4.1,7.5H80l-4.9-8.3C77.2,26.1,79.4,24,79.4,20.3z M71,22.5h-4V18h4c1.5,0,2.5,0.9,2.5,2.2C73.5,21.6,72.5,22.5,71,22.5z"></path>
|
||||||
|
<polygon points="40.5,12.8 58.6,12.8 58.6,18.1 52.4,18.1 52.4,35.2 46.6,35.2 46.6,18.1 40.5,18.1"></polygon>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<span class="navbar-text">
|
||||||
|
{{.CoinName}} Explorer
|
||||||
|
</span>
|
||||||
|
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="https://trezor.io/" class="nav-link">TREZOR</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="https://wallet.trezor.io/" class="nav-link">Wallet</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="https://trezor.io/support" class="nav-link">Support</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main id="wrap">
|
||||||
|
<div class="container">
|
||||||
|
{{template "specific" .}}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<footer id="footer" class="footer">
|
||||||
|
<div class="container">
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-trezor">
|
||||||
|
<ul class="navbar-nav flex-row">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="http://satoshilabs.com/" target="_blank" rel="noopener noreferrer">© 2018 SatoshiLabs</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="https://wallet.trezor.io/tos.pdf" target="_blank" rel="noopener noreferrer">Terms</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul class="navbar-nav flex-row ml-md-auto d-none d-md-flex">
|
||||||
|
<li class="nav-item active">
|
||||||
|
<a class="nav-link" rel="noopener noreferrer" href="http://shop.trezor.io">Don't have a TREZOR? Get one!</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
44
static/templates/tx.html
Normal file
44
static/templates/tx.html
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
{{define "specific"}}{{$cs := .CoinShortcut}}{{$tx := .Tx}}
|
||||||
|
<h1>Transaction</h1>
|
||||||
|
<div class="alert alert-data">
|
||||||
|
<span class="ellipsis data">{{$tx.Txid}}</span>
|
||||||
|
</div>
|
||||||
|
<h3>Summary</h3>
|
||||||
|
<div class="data-div">
|
||||||
|
<table class="table data-table">
|
||||||
|
<tbody>
|
||||||
|
{{if $tx.Confirmations}}
|
||||||
|
<tr>
|
||||||
|
<td style="width: 25%;">Mined Time</td>
|
||||||
|
<td class="data">{{formatUnixTime $tx.Blocktime}}</td>
|
||||||
|
</tr>{{end}}
|
||||||
|
<tr>
|
||||||
|
<td style="width: 25%;">In Block</td>
|
||||||
|
<td class="ellipsis data">{{if $tx.Confirmations}}{{$tx.Blockhash}}{{else}}Unconfirmed{{end}}</td>
|
||||||
|
</tr>
|
||||||
|
{{if $tx.Confirmations}}
|
||||||
|
<tr>
|
||||||
|
<td>In Block Height</td>
|
||||||
|
<td class="data">{{$tx.Blockheight}}</td>
|
||||||
|
</tr>{{end}}
|
||||||
|
<tr>
|
||||||
|
<td>Total Input</td>
|
||||||
|
<td class="data">{{formatAmount $tx.ValueIn}} {{$cs}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Total Output</td>
|
||||||
|
<td class="data">{{formatAmount $tx.ValueOut}} {{$cs}}</td>
|
||||||
|
</tr>
|
||||||
|
{{if $tx.Fees}}
|
||||||
|
<tr>
|
||||||
|
<td>Fees</td>
|
||||||
|
<td class="data">{{formatAmount $tx.Fees}} {{$cs}}</td>
|
||||||
|
</tr>{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<h3>Details</h3>
|
||||||
|
<div class="data-div">
|
||||||
|
{{template "txdetail" .}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
78
static/templates/txdetail.html
Normal file
78
static/templates/txdetail.html
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
{{define "txdetail"}}{{$cs := .CoinShortcut}}{{$addr := .AddrStr}}{{$tx := .Tx}}
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
{{if $tx.Confirmations}}
|
||||||
|
<div class="col-xs-5 col-md-4 text-muted text-right">mined {{formatUnixTime $tx.Blocktime}}</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div class="row line-mid">
|
||||||
|
<div class="col-md-5">
|
||||||
|
<div class="row">
|
||||||
|
<table class="table data-table">
|
||||||
|
<tbody>
|
||||||
|
{{range $vin := $tx.Vin}}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{if $vin.Txid}}
|
||||||
|
<span class="float-left ellipsis">
|
||||||
|
{{if $vin.Addr}}{{if eq $vin.Addr $addr}}{{$vin.Addr}}{{else}}
|
||||||
|
<a href="/explorer/address/{{$vin.Addr}}">{{$vin.Addr}}</a>{{end}}{{else}}Unparsed address{{end}}
|
||||||
|
</span>
|
||||||
|
<span class="float-right{{if eq $vin.Addr $addr}} text-danger{{end}}">{{formatAmount $vin.Value}} {{$cs}}</span>
|
||||||
|
{{else}}No Inputs (Newly Generated Coins){{end}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-1 col-xs-12 text-center">
|
||||||
|
<svg class="octicon" viewBox="0 0 8 16">
|
||||||
|
<path fill-rule="evenodd" d="M7.5 8l-5 5L1 11.5 4.75 8 1 4.5 2.5 3l5 5z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="row">
|
||||||
|
<table class="table data-table">
|
||||||
|
<tbody>
|
||||||
|
{{range $vout := $tx.Vout}}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{range $a := $vout.ScriptPubKey.Addresses}}
|
||||||
|
<span class="ellipsis float-left">
|
||||||
|
{{if eq $a $addr}}{{$a}}{{else}}
|
||||||
|
<a href="/explorer/address/{{$a}}">{{$a}}</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}}</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row line-top">
|
||||||
|
<div class="col-xs-6 col-sm-4 col-md-4">
|
||||||
|
{{if $tx.Fees}}
|
||||||
|
<span class="txvalues txvalues-default">Fee: {{formatAmount $tx.Fees}} {{$cs}}</span>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-6 col-sm-8 col-md-8 text-right">
|
||||||
|
{{if $tx.Confirmations}}
|
||||||
|
<span class="txvalues txvalues-success">{{$tx.Confirmations}} Confirmations</span>
|
||||||
|
{{else}}
|
||||||
|
<span class="txvalues txvalues-danger ng-hide">Unconfirmed Transaction!</span>
|
||||||
|
{{end}}
|
||||||
|
<span class="txvalues txvalues-primary">{{formatAmount $tx.ValueOut}} {{$cs}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
@ -4,8 +4,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
|
||||||
crossorigin="anonymous">
|
|
||||||
<style>
|
<style>
|
||||||
.row {
|
.row {
|
||||||
margin-top: 1%;
|
margin-top: 1%;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user