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
|
||||
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
|
||||
|
||||
|
||||
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,
|
||||
Txid: "056e3d82e5ffd0e915fb9b62797d76263508c34fe3e5dbed30dd3e943930f204",
|
||||
LockTime: 512115,
|
||||
Version: 1,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
@ -176,6 +177,7 @@ func init() {
|
||||
Blocktime: 1235678901,
|
||||
Txid: "474e6795760ebe81cb4023dc227e5a0efe340e1771c89a0035276361ed733de7",
|
||||
LockTime: 0,
|
||||
Version: 1,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
|
||||
@ -9,8 +9,8 @@ import (
|
||||
"blockbook/bchain/coins/dogecoin"
|
||||
"blockbook/bchain/coins/eth"
|
||||
"blockbook/bchain/coins/litecoin"
|
||||
"blockbook/bchain/coins/vertcoin"
|
||||
"blockbook/bchain/coins/namecoin"
|
||||
"blockbook/bchain/coins/vertcoin"
|
||||
"blockbook/bchain/coins/zec"
|
||||
"blockbook/common"
|
||||
"context"
|
||||
@ -47,20 +47,21 @@ func init() {
|
||||
blockChainFactories["Namecoin"] = namecoin.NewNamecoinRPC
|
||||
}
|
||||
|
||||
// GetCoinNameFromConfig gets coin name from config file
|
||||
func GetCoinNameFromConfig(configfile string) (string, error) {
|
||||
// GetCoinNameFromConfig gets coin name and coin shortcut from config file
|
||||
func GetCoinNameFromConfig(configfile string) (string, string, error) {
|
||||
data, err := ioutil.ReadFile(configfile)
|
||||
if err != nil {
|
||||
return "", errors.Annotatef(err, "Error reading file %v", configfile)
|
||||
return "", "", errors.Annotatef(err, "Error reading file %v", configfile)
|
||||
}
|
||||
var cn struct {
|
||||
CoinName string `json:"coin_name"`
|
||||
CoinName string `json:"coin_name"`
|
||||
CoinShortcut string `json:"coin_shortcut"`
|
||||
}
|
||||
err = json.Unmarshal(data, &cn)
|
||||
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
|
||||
|
||||
@ -130,8 +130,8 @@ func (p *BitcoinParser) TxFromMsgTx(t *wire.MsgTx, parseAddresses bool) bchain.T
|
||||
}
|
||||
}
|
||||
tx := bchain.Tx{
|
||||
Txid: t.TxHash().String(),
|
||||
// skip: Version,
|
||||
Txid: t.TxHash().String(),
|
||||
Version: t.Version,
|
||||
LockTime: t.LockTime,
|
||||
Vin: vin,
|
||||
Vout: vout,
|
||||
|
||||
@ -139,6 +139,7 @@ func init() {
|
||||
Blocktime: 1519053802,
|
||||
Txid: "056e3d82e5ffd0e915fb9b62797d76263508c34fe3e5dbed30dd3e943930f204",
|
||||
LockTime: 512115,
|
||||
Version: 1,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
@ -169,6 +170,7 @@ func init() {
|
||||
Blocktime: 1235678901,
|
||||
Txid: "474e6795760ebe81cb4023dc227e5a0efe340e1771c89a0035276361ed733de7",
|
||||
LockTime: 0,
|
||||
Version: 1,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
|
||||
@ -41,7 +41,7 @@ Create script that runs blockbook *run-btc-blockbook.sh*
|
||||
#!/bin/bash
|
||||
|
||||
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)
|
||||
```
|
||||
|
||||
@ -41,7 +41,7 @@ Create script that runs blockbook *run-testnet-blockbook.sh*
|
||||
#!/bin/bash
|
||||
|
||||
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)
|
||||
```
|
||||
|
||||
@ -98,6 +98,7 @@ func init() {
|
||||
Blocktime: 1519053456,
|
||||
Txid: "097ea09ba284f3f2a9e880e11f837edf7e5cea81c8da2238f5bc7c2c4c407943",
|
||||
LockTime: 0,
|
||||
Version: 1,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
@ -139,6 +140,7 @@ func init() {
|
||||
Blocktime: 1519050987,
|
||||
Txid: "b276545af246e3ed5a4e3e5b60d359942a1808579effc53ff4f343e4f6cfc5a0",
|
||||
LockTime: 0,
|
||||
Version: 1,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
|
||||
@ -17,7 +17,7 @@ Create script that runs blockbook *run-eth-blockbook.sh*
|
||||
#!/bin/bash
|
||||
|
||||
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)
|
||||
```
|
||||
|
||||
@ -17,7 +17,7 @@ Create script that runs blockbook *run-eth-testnet-blockbook.sh*
|
||||
#!/bin/bash
|
||||
|
||||
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)
|
||||
```
|
||||
|
||||
@ -144,6 +144,7 @@ func init() {
|
||||
Blocktime: 1519053456,
|
||||
Txid: "1c50c1770374d7de2f81a87463a5225bb620d25fd467536223a5b715a47c9e32",
|
||||
LockTime: 0,
|
||||
Version: 2,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
ScriptSig: bchain.ScriptSig{
|
||||
|
||||
@ -92,6 +92,7 @@ func init() {
|
||||
Blocktime: 1529925180,
|
||||
Txid: "d58c11aa970449c3e0ee5e0cdf78532435a9d2b28a2da284a8dd4dd6bdd0331c",
|
||||
LockTime: 952180,
|
||||
Version: 1,
|
||||
Vin: []bchain.Vin{
|
||||
{
|
||||
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
|
||||
```
|
||||
#!/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)
|
||||
|
||||
@ -58,9 +58,9 @@ type Vout struct {
|
||||
// Tx is blockchain transaction
|
||||
// unnecessary fields are commented out to avoid overhead
|
||||
type Tx struct {
|
||||
Hex string `json:"hex"`
|
||||
Txid string `json:"txid"`
|
||||
// Version int32 `json:"version"`
|
||||
Hex string `json:"hex"`
|
||||
Txid string `json:"txid"`
|
||||
Version int32 `json:"version"`
|
||||
LockTime uint32 `json:"locktime"`
|
||||
Vin []Vin `json:"vin"`
|
||||
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")
|
||||
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)")
|
||||
|
||||
@ -148,7 +148,7 @@ func main() {
|
||||
glog.Fatal("Missing blockchaincfg configuration parameter")
|
||||
}
|
||||
|
||||
coin, err := coins.GetCoinNameFromConfig(*blockchain)
|
||||
coin, coinShortcut, err := coins.GetCoinNameFromConfig(*blockchain)
|
||||
if err != nil {
|
||||
glog.Fatal("config: ", err)
|
||||
}
|
||||
@ -168,7 +168,7 @@ func main() {
|
||||
}
|
||||
defer index.Close()
|
||||
|
||||
internalState, err = newInternalState(coin, index)
|
||||
internalState, err = newInternalState(coin, coinShortcut, index)
|
||||
if err != nil {
|
||||
glog.Error("internalState: ", err)
|
||||
return
|
||||
@ -232,18 +232,18 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
var httpServer *server.HTTPServer
|
||||
if *httpServerBinding != "" {
|
||||
httpServer, err = server.NewHTTPServer(*httpServerBinding, *certFiles, index, chain, txCache, internalState)
|
||||
var internalServer *server.InternalServer
|
||||
if *internalBinding != "" {
|
||||
internalServer, err = server.NewInternalServer(*internalBinding, *certFiles, index, chain, txCache, internalState)
|
||||
if err != nil {
|
||||
glog.Error("https: ", err)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
err = httpServer.Run()
|
||||
err = internalServer.Run()
|
||||
if err != nil {
|
||||
if err.Error() == "http: Server closed" {
|
||||
glog.Info(err)
|
||||
glog.Info("internal server: closed")
|
||||
} else {
|
||||
glog.Error(err)
|
||||
return
|
||||
@ -263,27 +263,26 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
var socketIoServer *server.SocketIoServer
|
||||
if *socketIoBinding != "" {
|
||||
socketIoServer, err = server.NewSocketIoServer(
|
||||
*socketIoBinding, *certFiles, index, chain, txCache, *explorerURL, metrics, internalState)
|
||||
var publicServer *server.PublicServer
|
||||
if *publicBinding != "" {
|
||||
publicServer, err = server.NewPublicServer(*publicBinding, *certFiles, index, chain, txCache, *explorerURL, metrics, internalState)
|
||||
if err != nil {
|
||||
glog.Error("socketio: ", err)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
err = socketIoServer.Run()
|
||||
err = publicServer.Run()
|
||||
if err != nil {
|
||||
if err.Error() == "http: Server closed" {
|
||||
glog.Info(err)
|
||||
glog.Info("public server: closed")
|
||||
} else {
|
||||
glog.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
callbacksOnNewBlockHash = append(callbacksOnNewBlockHash, socketIoServer.OnNewBlockHash)
|
||||
callbacksOnNewTxAddr = append(callbacksOnNewTxAddr, socketIoServer.OnNewTxAddr)
|
||||
callbacksOnNewBlockHash = append(callbacksOnNewBlockHash, publicServer.OnNewBlockHash)
|
||||
callbacksOnNewTxAddr = append(callbacksOnNewTxAddr, publicServer.OnNewTxAddr)
|
||||
}
|
||||
|
||||
if *synchronize {
|
||||
@ -314,8 +313,8 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
if httpServer != nil || socketIoServer != nil || chain != nil {
|
||||
waitForSignalAndShutdown(httpServer, socketIoServer, chain, 10*time.Second)
|
||||
if internalServer != nil || publicServer != nil || chain != nil {
|
||||
waitForSignalAndShutdown(internalServer, publicServer, chain, 10*time.Second)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
is.CoinShortcut = coinShortcut
|
||||
name, err := os.Hostname()
|
||||
if err != nil {
|
||||
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
|
||||
atomic.StoreInt32(&inShutdown, 1)
|
||||
glog.Infof("Shutdown: %v", sig)
|
||||
glog.Infof("shutdown: %v", sig)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
if https != nil {
|
||||
if err := https.Shutdown(ctx); err != nil {
|
||||
glog.Error("HttpServer.Shutdown error: ", err)
|
||||
if internal != nil {
|
||||
if err := internal.Shutdown(ctx); err != nil {
|
||||
glog.Error("internal server: shutdown error: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
if socketio != nil {
|
||||
if err := socketio.Shutdown(ctx); err != nil {
|
||||
glog.Error("SocketIo.Shutdown error: ", err)
|
||||
if public != nil {
|
||||
if err := public.Shutdown(ctx); err != nil {
|
||||
glog.Error("public server: shutdown error: ", err)
|
||||
}
|
||||
}
|
||||
|
||||
if chain != 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
|
||||
|
||||
[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
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
|
||||
@ -10,7 +10,7 @@ After=network.target
|
||||
Wants=backend-bcash.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
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
|
||||
@ -10,7 +10,7 @@ After=network.target
|
||||
Wants=backend-bgold.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
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
|
||||
@ -10,7 +10,7 @@ After=network.target
|
||||
Wants=backend-bitcoin-testnet.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
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
|
||||
@ -10,7 +10,7 @@ After=network.target
|
||||
Wants=backend-bitcoin.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
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
|
||||
@ -10,7 +10,7 @@ After=network.target
|
||||
Wants=backend-dash-testnet.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
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
|
||||
@ -10,7 +10,7 @@ After=network.target
|
||||
Wants=backend-dash.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
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
|
||||
@ -10,7 +10,7 @@ After=network.target
|
||||
Wants=backend-dogecoin.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
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
|
||||
@ -10,7 +10,7 @@ After=network.target
|
||||
Wants=backend-ethereum-testnet-ropsten.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
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
|
||||
@ -10,7 +10,7 @@ After=network.target
|
||||
Wants=backend-ethereum.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
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
|
||||
@ -10,7 +10,7 @@ After=network.target
|
||||
Wants=backend-litecoin-testnet.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
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
|
||||
@ -10,7 +10,7 @@ After=network.target
|
||||
Wants=backend-litecoin.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
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
|
||||
@ -10,7 +10,7 @@ After=network.target
|
||||
Wants=backend-namecoin.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
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
|
||||
@ -10,7 +10,7 @@ After=network.target
|
||||
Wants=backend-vertcoin-testnet.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
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
|
||||
@ -10,7 +10,7 @@ After=network.target
|
||||
Wants=backend-vertcoin.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
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
|
||||
@ -10,7 +10,7 @@ After=network.target
|
||||
Wants=backend-zcash-testnet.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
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
|
||||
@ -10,7 +10,7 @@ After=network.target
|
||||
Wants=backend-zcash.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
|
||||
Type=simple
|
||||
Restart=on-failure
|
||||
|
||||
@ -27,8 +27,9 @@ type InternalStateColumn struct {
|
||||
type InternalState struct {
|
||||
mux sync.Mutex
|
||||
|
||||
Coin string `json:"coin"`
|
||||
Host string `json:"host"`
|
||||
Coin string `json:"coin"`
|
||||
CoinShortcut string `json:"coinShortcut"`
|
||||
Host string `json:"host"`
|
||||
|
||||
DbState uint32 `json:"dbState"`
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"coin_name": "Bcash",
|
||||
"coin_shortcut": "BCH",
|
||||
"rpcURL": "http://127.0.0.1:8031",
|
||||
"rpcUser": "rpc",
|
||||
"rpcPass": "rpc",
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"coin_name": "Bcash Testnet",
|
||||
"coin_shortcut": "TBCH",
|
||||
"rpcURL": "http://localhost:18031",
|
||||
"rpcUser": "rpc",
|
||||
"rpcPass": "rpc",
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"coin_name": "Bgold",
|
||||
"coin_shortcut": "BTG",
|
||||
"rpcURL": "http://127.0.0.1:8035",
|
||||
"rpcUser": "rpc",
|
||||
"rpcPass": "rpc",
|
||||
@ -10,4 +11,4 @@
|
||||
"mempoolWorkers": 8,
|
||||
"mempoolSubWorkers": 2,
|
||||
"blockAddressesToKeep": 300
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"coin_name": "Bitcoin",
|
||||
"coin_shortcut": "BTC",
|
||||
"rpcURL": "http://127.0.0.1:8030",
|
||||
"rpcUser": "rpc",
|
||||
"rpcPass": "rpc",
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"coin_name": "Testnet",
|
||||
"coin_shortcut": "TEST",
|
||||
"rpcURL": "http://localhost:18030",
|
||||
"rpcUser": "rpc",
|
||||
"rpcPass": "rpc",
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"coin_name": "Dash",
|
||||
"coin_shortcut": "DASH",
|
||||
"rpcURL": "http://127.0.0.1:8033",
|
||||
"rpcUser": "rpc",
|
||||
"rpcPass": "rpc",
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"coin_name": "Dash Testnet",
|
||||
"coin_shortcut": "tDASH",
|
||||
"rpcURL": "http://localhost:18033",
|
||||
"rpcUser": "rpc",
|
||||
"rpcPass": "rpc",
|
||||
@ -10,4 +11,4 @@
|
||||
"mempoolWorkers": 8,
|
||||
"mempoolSubWorkers": 2,
|
||||
"blockAddressesToKeep": 300
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"coin_name": "Dogecoin",
|
||||
"coin_shortcut": "DOGE",
|
||||
"rpcURL": "http://127.0.0.1:8038",
|
||||
"rpcUser": "rpc",
|
||||
"rpcPass": "rpcp",
|
||||
@ -9,4 +10,4 @@
|
||||
"mempoolWorkers": 8,
|
||||
"mempoolSubWorkers": 2,
|
||||
"blockAddressesToKeep": 300
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"coin_name": "Ethereum",
|
||||
"coin_shortcut": "ETH",
|
||||
"rpcURL": "ws://localhost:8036",
|
||||
"rpcTimeout": 25
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"coin_name": "Ethereum Testnet Ropsten",
|
||||
"coin_shortcut": "tETH",
|
||||
"rpcURL": "ws://localhost:18036",
|
||||
"rpcTimeout": 25
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"coin_name": "Litecoin",
|
||||
"coin_shortcut": "LTC",
|
||||
"rpcURL": "http://localhost:8034",
|
||||
"rpcUser": "rpc",
|
||||
"rpcPass": "rpc",
|
||||
@ -9,4 +10,4 @@
|
||||
"mempoolWorkers": 8,
|
||||
"mempoolSubWorkers": 2,
|
||||
"blockAddressesToKeep": 300
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"coin_name": "Litecoin Testnet",
|
||||
"coin_shortcut": "TLTC",
|
||||
"rpcURL": "http://localhost:18034",
|
||||
"rpcUser": "rpc",
|
||||
"rpcPass": "rpc",
|
||||
@ -9,4 +10,4 @@
|
||||
"mempoolWorkers": 8,
|
||||
"mempoolSubWorkers": 2,
|
||||
"blockAddressesToKeep": 300
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"coin_name": "Namecoin",
|
||||
"coin_shortcut": "NMC",
|
||||
"rpcURL": "http://127.0.0.1:8039",
|
||||
"rpcUser": "rpc",
|
||||
"rpcPass": "rpc",
|
||||
@ -9,4 +10,4 @@
|
||||
"mempoolWorkers": 8,
|
||||
"mempoolSubWorkers": 2,
|
||||
"blockAddressesToKeep": 300
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"coin_name": "Vertcoin",
|
||||
"coin_shortcut": "VTC",
|
||||
"rpcURL": "http://localhost:8040",
|
||||
"rpcUser": "rpc",
|
||||
"rpcPass": "rpc",
|
||||
@ -9,4 +10,4 @@
|
||||
"mempoolWorkers": 8,
|
||||
"mempoolSubWorkers": 2,
|
||||
"blockAddressesToKeep": 300
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"coin_name": "Vertcoin Testnet",
|
||||
"coin_shortcut": "tVTC",
|
||||
"rpcURL": "http://localhost:18040",
|
||||
"rpcUser": "rpc",
|
||||
"rpcPass": "rpc",
|
||||
@ -9,4 +10,4 @@
|
||||
"mempoolWorkers": 8,
|
||||
"mempoolSubWorkers": 2,
|
||||
"blockAddressesToKeep": 300
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"coin_name": "Zcash",
|
||||
"coin_shortcut": "ZEC",
|
||||
"rpcURL": "http://127.0.0.1:8032",
|
||||
"rpcUser": "rpc",
|
||||
"rpcPass": "rpc",
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
{
|
||||
"coin_name": "Zcash Testnet",
|
||||
"coin_shortcut": "TAZ",
|
||||
"rpcURL": "http://127.0.0.1:18032",
|
||||
"rpcUser": "rpc",
|
||||
"rpcPass": "rpc",
|
||||
|
||||
@ -18,8 +18,8 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
// HTTPServer is handle to HttpServer
|
||||
type HTTPServer struct {
|
||||
// InternalServer is handle to internal http server
|
||||
type InternalServer struct {
|
||||
https *http.Server
|
||||
certFiles string
|
||||
db *db.RocksDB
|
||||
@ -44,14 +44,14 @@ type resAboutBlockbookInternal struct {
|
||||
DbColumns []common.InternalStateColumn `json:"dbColumns"`
|
||||
}
|
||||
|
||||
// NewHTTPServer creates new REST 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) {
|
||||
// NewInternalServer creates new internal http interface to blockbook and returns its handle
|
||||
func NewInternalServer(httpServerBinding string, certFiles string, db *db.RocksDB, chain bchain.BlockChain, txCache *db.TxCache, is *common.InternalState) (*InternalServer, error) {
|
||||
r := mux.NewRouter()
|
||||
https := &http.Server{
|
||||
Addr: httpServerBinding,
|
||||
Handler: r,
|
||||
}
|
||||
s := &HTTPServer{
|
||||
s := &InternalServer{
|
||||
https: https,
|
||||
certFiles: certFiles,
|
||||
db: db,
|
||||
@ -73,30 +73,30 @@ func NewHTTPServer(httpServerBinding string, certFiles string, db *db.RocksDB, c
|
||||
}
|
||||
|
||||
// Run starts the server
|
||||
func (s *HTTPServer) Run() error {
|
||||
func (s *InternalServer) Run() error {
|
||||
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()
|
||||
}
|
||||
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"))
|
||||
}
|
||||
|
||||
// Close closes the server
|
||||
func (s *HTTPServer) Close() error {
|
||||
glog.Infof("http server closing")
|
||||
func (s *InternalServer) Close() error {
|
||||
glog.Infof("internal server: closing")
|
||||
return s.https.Close()
|
||||
}
|
||||
|
||||
// Shutdown shuts down the server
|
||||
func (s *HTTPServer) Shutdown(ctx context.Context) error {
|
||||
glog.Infof("http server shutdown")
|
||||
func (s *InternalServer) Shutdown(ctx context.Context) error {
|
||||
glog.Infof("internal server: shutdown")
|
||||
return s.https.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func respondError(w http.ResponseWriter, err error, context string) {
|
||||
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) {
|
||||
@ -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()
|
||||
ss, bh, st := s.is.GetSyncState()
|
||||
ms, mt, msz := s.is.GetMempoolSyncState()
|
||||
@ -133,7 +133,7 @@ func (s *HTTPServer) index(w http.ResponseWriter, r *http.Request) {
|
||||
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()
|
||||
if err != nil {
|
||||
respondError(w, err, "bestBlockHash")
|
||||
@ -142,7 +142,7 @@ func (s *HTTPServer) bestBlockHash(w http.ResponseWriter, r *http.Request) {
|
||||
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"]
|
||||
var hash string
|
||||
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"]
|
||||
if !ok {
|
||||
err = errors.New("Empty address")
|
||||
@ -164,7 +164,7 @@ func (s *HTTPServer) getAddress(r *http.Request) (address string, err error) {
|
||||
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)
|
||||
if err != nil {
|
||||
return
|
||||
@ -184,7 +184,7 @@ type transactionList struct {
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
|
||||
import (
|
||||
"blockbook/api"
|
||||
"blockbook/bchain"
|
||||
"blockbook/common"
|
||||
"blockbook/db"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -19,25 +17,19 @@ import (
|
||||
"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
|
||||
type SocketIoServer struct {
|
||||
binding string
|
||||
certFiles string
|
||||
server *gosocketio.Server
|
||||
https *http.Server
|
||||
db *db.RocksDB
|
||||
txCache *db.TxCache
|
||||
chain bchain.BlockChain
|
||||
chainParser bchain.BlockChainParser
|
||||
explorerURL string
|
||||
metrics *common.Metrics
|
||||
is *common.InternalState
|
||||
}
|
||||
|
||||
// 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.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"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
addr, path := splitBinding(binding)
|
||||
serveMux := http.NewServeMux()
|
||||
https := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: serveMux,
|
||||
}
|
||||
|
||||
s := &SocketIoServer{
|
||||
binding: binding,
|
||||
certFiles: certFiles,
|
||||
https: https,
|
||||
server: server,
|
||||
db: db,
|
||||
txCache: txCache,
|
||||
chain: chain,
|
||||
chainParser: chain.GetChainParser(),
|
||||
explorerURL: explorerURL,
|
||||
metrics: metrics,
|
||||
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("subscribe", s.onSubscribe)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func splitBinding(binding string) (addr string, path string) {
|
||||
i := strings.Index(binding, "/")
|
||||
if i >= 0 {
|
||||
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)
|
||||
}
|
||||
// GetHandler returns socket.io http handler
|
||||
func (s *SocketIoServer) GetHandler() http.Handler {
|
||||
return s.server
|
||||
}
|
||||
|
||||
type addrOpts struct {
|
||||
@ -346,22 +197,6 @@ func unmarshalGetAddressRequest(params []byte) (addr []string, opts addrOpts, er
|
||||
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 {
|
||||
Result []string `json:"result"`
|
||||
}
|
||||
@ -386,7 +221,7 @@ func (s *SocketIoServer) getAddressTxids(addr []string, opts *addrOpts) (res res
|
||||
txids = append(txids, m...)
|
||||
}
|
||||
}
|
||||
res.Result = uniqueTxidsInReverse(txids)
|
||||
res.Result = api.UniqueTxidsInReverse(txids)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@ -748,7 +583,7 @@ func (s *SocketIoServer) getDetailedTransaction(txid string) (res resultGetDetai
|
||||
return res, err
|
||||
}
|
||||
if len(otx.Vout) > int(vin.Vout) {
|
||||
vout := otx.Vout[vin.Vout]
|
||||
vout := &otx.Vout[vin.Vout]
|
||||
if vout.Address != nil {
|
||||
a := vout.Address.String()
|
||||
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>
|
||||
<meta charset="utf-8">
|
||||
<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"
|
||||
crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
|
||||
<style>
|
||||
.row {
|
||||
margin-top: 1%;
|
||||
@ -415,4 +414,4 @@
|
||||
document.getElementById('serverAddress').value = window.location.protocol.replace("http", "ws") + "//" + window.location.host;
|
||||
</script>
|
||||
|
||||
</html>
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user