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:
Martin Boehm 2018-07-23 13:42:52 +02:00
commit 64b34c1dd8
62 changed files with 1228 additions and 271 deletions

View File

@ -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
View 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
View 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
}

View File

@ -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{

View File

@ -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

View File

@ -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,

View File

@ -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{

View File

@ -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)
```

View File

@ -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)
```

View File

@ -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{

View File

@ -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)
```

View File

@ -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)
```

View File

@ -144,6 +144,7 @@ func init() {
Blocktime: 1519053456,
Txid: "1c50c1770374d7de2f81a87463a5225bb620d25fd467536223a5b715a47c9e32",
LockTime: 0,
Version: 2,
Vin: []bchain.Vin{
{
ScriptSig: bchain.ScriptSig{

View File

@ -92,6 +92,7 @@ func init() {
Blocktime: 1529925180,
Txid: "d58c11aa970449c3e0ee5e0cdf78532435a9d2b28a2da284a8dd4dd6bdd0331c",
LockTime: 952180,
Version: 1,
Vin: []bchain.Vin{
{
ScriptSig: bchain.ScriptSig{

View File

@ -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)

View File

@ -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"`

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"`

View File

@ -1,5 +1,6 @@
{
"coin_name": "Bcash",
"coin_shortcut": "BCH",
"rpcURL": "http://127.0.0.1:8031",
"rpcUser": "rpc",
"rpcPass": "rpc",

View File

@ -1,5 +1,6 @@
{
"coin_name": "Bcash Testnet",
"coin_shortcut": "TBCH",
"rpcURL": "http://localhost:18031",
"rpcUser": "rpc",
"rpcPass": "rpc",

View File

@ -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
}
}

View File

@ -1,5 +1,6 @@
{
"coin_name": "Bitcoin",
"coin_shortcut": "BTC",
"rpcURL": "http://127.0.0.1:8030",
"rpcUser": "rpc",
"rpcPass": "rpc",

View File

@ -1,5 +1,6 @@
{
"coin_name": "Testnet",
"coin_shortcut": "TEST",
"rpcURL": "http://localhost:18030",
"rpcUser": "rpc",
"rpcPass": "rpc",

View File

@ -1,5 +1,6 @@
{
"coin_name": "Dash",
"coin_shortcut": "DASH",
"rpcURL": "http://127.0.0.1:8033",
"rpcUser": "rpc",
"rpcPass": "rpc",

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -1,5 +1,6 @@
{
"coin_name": "Ethereum",
"coin_shortcut": "ETH",
"rpcURL": "ws://localhost:8036",
"rpcTimeout": 25
}

View File

@ -1,5 +1,6 @@
{
"coin_name": "Ethereum Testnet Ropsten",
"coin_shortcut": "tETH",
"rpcURL": "ws://localhost:18036",
"rpcTimeout": 25
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -1,5 +1,6 @@
{
"coin_name": "Zcash",
"coin_shortcut": "ZEC",
"rpcURL": "http://127.0.0.1:8032",
"rpcUser": "rpc",
"rpcPass": "rpc",

View File

@ -1,5 +1,6 @@
{
"coin_name": "Zcash Testnet",
"coin_shortcut": "TAZ",
"rpcURL": "http://127.0.0.1:18032",
"rpcUser": "rpc",
"rpcPass": "rpc",

View File

@ -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
View 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)
}
}

View File

@ -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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

View 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}}

View 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
View 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}}

View 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}}

View File

@ -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>