diff --git a/README.md b/README.md index 5a2f7240..b379d23a 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/api/types.go b/api/types.go new file mode 100644 index 00000000..b605bd30 --- /dev/null +++ b/api/types.go @@ -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"` +} diff --git a/api/worker.go b/api/worker.go new file mode 100644 index 00000000..4ad421d3 --- /dev/null +++ b/api/worker.go @@ -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 +} diff --git a/bchain/coins/bch/bcashparser_test.go b/bchain/coins/bch/bcashparser_test.go index 1d042b9f..3e1cb4c1 100644 --- a/bchain/coins/bch/bcashparser_test.go +++ b/bchain/coins/bch/bcashparser_test.go @@ -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{ diff --git a/bchain/coins/blockchain.go b/bchain/coins/blockchain.go index 84e08655..b1611161 100644 --- a/bchain/coins/blockchain.go +++ b/bchain/coins/blockchain.go @@ -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 diff --git a/bchain/coins/btc/bitcoinparser.go b/bchain/coins/btc/bitcoinparser.go index 41c52759..861c7f31 100644 --- a/bchain/coins/btc/bitcoinparser.go +++ b/bchain/coins/btc/bitcoinparser.go @@ -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, diff --git a/bchain/coins/btc/bitcoinparser_test.go b/bchain/coins/btc/bitcoinparser_test.go index 46490f6c..d605f45b 100644 --- a/bchain/coins/btc/bitcoinparser_test.go +++ b/bchain/coins/btc/bitcoinparser_test.go @@ -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{ diff --git a/bchain/coins/btc/btc.md b/bchain/coins/btc/btc.md index f6770962..2be2fdd7 100644 --- a/bchain/coins/btc/btc.md +++ b/bchain/coins/btc/btc.md @@ -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) ``` diff --git a/bchain/coins/btc/btctestnet.md b/bchain/coins/btc/btctestnet.md index c2543ca9..bbb59ac0 100644 --- a/bchain/coins/btc/btctestnet.md +++ b/bchain/coins/btc/btctestnet.md @@ -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) ``` diff --git a/bchain/coins/dogecoin/dogecoinparser_test.go b/bchain/coins/dogecoin/dogecoinparser_test.go index 658d4b1c..b8ea585f 100644 --- a/bchain/coins/dogecoin/dogecoinparser_test.go +++ b/bchain/coins/dogecoin/dogecoinparser_test.go @@ -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{ diff --git a/bchain/coins/eth/eth.md b/bchain/coins/eth/eth.md index 16fb3ba0..1db56280 100644 --- a/bchain/coins/eth/eth.md +++ b/bchain/coins/eth/eth.md @@ -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) ``` diff --git a/bchain/coins/eth/ethropsten.md b/bchain/coins/eth/ethropsten.md index 604bc220..0e4bb161 100644 --- a/bchain/coins/eth/ethropsten.md +++ b/bchain/coins/eth/ethropsten.md @@ -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) ``` diff --git a/bchain/coins/litecoin/litecoinparser_test.go b/bchain/coins/litecoin/litecoinparser_test.go index a28a4a35..11c55f1e 100644 --- a/bchain/coins/litecoin/litecoinparser_test.go +++ b/bchain/coins/litecoin/litecoinparser_test.go @@ -144,6 +144,7 @@ func init() { Blocktime: 1519053456, Txid: "1c50c1770374d7de2f81a87463a5225bb620d25fd467536223a5b715a47c9e32", LockTime: 0, + Version: 2, Vin: []bchain.Vin{ { ScriptSig: bchain.ScriptSig{ diff --git a/bchain/coins/vertcoin/vertcoin_test.go b/bchain/coins/vertcoin/vertcoinparser_test.go similarity index 99% rename from bchain/coins/vertcoin/vertcoin_test.go rename to bchain/coins/vertcoin/vertcoinparser_test.go index 38cc70f4..558ae51f 100644 --- a/bchain/coins/vertcoin/vertcoin_test.go +++ b/bchain/coins/vertcoin/vertcoinparser_test.go @@ -92,6 +92,7 @@ func init() { Blocktime: 1529925180, Txid: "d58c11aa970449c3e0ee5e0cdf78532435a9d2b28a2da284a8dd4dd6bdd0331c", LockTime: 952180, + Version: 1, Vin: []bchain.Vin{ { ScriptSig: bchain.ScriptSig{ diff --git a/bchain/coins/zec/zec.md b/bchain/coins/zec/zec.md index 22c96687..e7cf690d 100644 --- a/bchain/coins/zec/zec.md +++ b/bchain/coins/zec/zec.md @@ -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) diff --git a/bchain/types.go b/bchain/types.go index 40cff440..e2aa7276 100644 --- a/bchain/types.go +++ b/bchain/types.go @@ -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"` diff --git a/blockbook.go b/blockbook.go index 41fbfb34..cea2c5b6 100644 --- a/blockbook.go +++ b/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 .crt and .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) } } } diff --git a/build/deb/debian/blockbook-bcash-testnet.service b/build/deb/debian/blockbook-bcash-testnet.service index bf662598..165fe74c 100644 --- a/build/deb/debian/blockbook-bcash-testnet.service +++ b/build/deb/debian/blockbook-bcash-testnet.service @@ -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 diff --git a/build/deb/debian/blockbook-bcash.service b/build/deb/debian/blockbook-bcash.service index 0dc29e39..6c2121a5 100644 --- a/build/deb/debian/blockbook-bcash.service +++ b/build/deb/debian/blockbook-bcash.service @@ -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 diff --git a/build/deb/debian/blockbook-bgold.service b/build/deb/debian/blockbook-bgold.service index d3e00985..83e0b5f6 100644 --- a/build/deb/debian/blockbook-bgold.service +++ b/build/deb/debian/blockbook-bgold.service @@ -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 diff --git a/build/deb/debian/blockbook-bitcoin-testnet.service b/build/deb/debian/blockbook-bitcoin-testnet.service index a9de2f1b..53030fb8 100644 --- a/build/deb/debian/blockbook-bitcoin-testnet.service +++ b/build/deb/debian/blockbook-bitcoin-testnet.service @@ -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 diff --git a/build/deb/debian/blockbook-bitcoin.service b/build/deb/debian/blockbook-bitcoin.service index 1da3f0fe..6a8f9d80 100644 --- a/build/deb/debian/blockbook-bitcoin.service +++ b/build/deb/debian/blockbook-bitcoin.service @@ -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 diff --git a/build/deb/debian/blockbook-dash-testnet.service b/build/deb/debian/blockbook-dash-testnet.service index 5f3eb719..45f0e3b6 100644 --- a/build/deb/debian/blockbook-dash-testnet.service +++ b/build/deb/debian/blockbook-dash-testnet.service @@ -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 diff --git a/build/deb/debian/blockbook-dash.service b/build/deb/debian/blockbook-dash.service index f4838f63..7bc2936f 100644 --- a/build/deb/debian/blockbook-dash.service +++ b/build/deb/debian/blockbook-dash.service @@ -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 diff --git a/build/deb/debian/blockbook-dogecoin.service b/build/deb/debian/blockbook-dogecoin.service index 00d2dd19..00f7c647 100644 --- a/build/deb/debian/blockbook-dogecoin.service +++ b/build/deb/debian/blockbook-dogecoin.service @@ -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 diff --git a/build/deb/debian/blockbook-ethereum-testnet-ropsten.service b/build/deb/debian/blockbook-ethereum-testnet-ropsten.service index 9769553d..35c9f45f 100644 --- a/build/deb/debian/blockbook-ethereum-testnet-ropsten.service +++ b/build/deb/debian/blockbook-ethereum-testnet-ropsten.service @@ -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 diff --git a/build/deb/debian/blockbook-ethereum.service b/build/deb/debian/blockbook-ethereum.service index 9d6dc97d..627c00e4 100644 --- a/build/deb/debian/blockbook-ethereum.service +++ b/build/deb/debian/blockbook-ethereum.service @@ -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 diff --git a/build/deb/debian/blockbook-litecoin-testnet.service b/build/deb/debian/blockbook-litecoin-testnet.service index d19c9f0a..f7a7dfc6 100644 --- a/build/deb/debian/blockbook-litecoin-testnet.service +++ b/build/deb/debian/blockbook-litecoin-testnet.service @@ -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 diff --git a/build/deb/debian/blockbook-litecoin.service b/build/deb/debian/blockbook-litecoin.service index 9833d60d..614bdf46 100644 --- a/build/deb/debian/blockbook-litecoin.service +++ b/build/deb/debian/blockbook-litecoin.service @@ -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 diff --git a/build/deb/debian/blockbook-namecoin.service b/build/deb/debian/blockbook-namecoin.service index db5f440b..a8f2b1c1 100644 --- a/build/deb/debian/blockbook-namecoin.service +++ b/build/deb/debian/blockbook-namecoin.service @@ -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 diff --git a/build/deb/debian/blockbook-vertcoin-testnet.service b/build/deb/debian/blockbook-vertcoin-testnet.service index 9d391f83..1d5d2aed 100644 --- a/build/deb/debian/blockbook-vertcoin-testnet.service +++ b/build/deb/debian/blockbook-vertcoin-testnet.service @@ -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 diff --git a/build/deb/debian/blockbook-vertcoin.service b/build/deb/debian/blockbook-vertcoin.service index 9f19ba13..cd849216 100644 --- a/build/deb/debian/blockbook-vertcoin.service +++ b/build/deb/debian/blockbook-vertcoin.service @@ -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 diff --git a/build/deb/debian/blockbook-zcash-testnet.service b/build/deb/debian/blockbook-zcash-testnet.service index cd190610..dc460551 100644 --- a/build/deb/debian/blockbook-zcash-testnet.service +++ b/build/deb/debian/blockbook-zcash-testnet.service @@ -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 diff --git a/build/deb/debian/blockbook-zcash.service b/build/deb/debian/blockbook-zcash.service index e7933e0c..d2de00cc 100644 --- a/build/deb/debian/blockbook-zcash.service +++ b/build/deb/debian/blockbook-zcash.service @@ -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 diff --git a/common/internalstate.go b/common/internalstate.go index 15b8f26a..8e7b2b61 100644 --- a/common/internalstate.go +++ b/common/internalstate.go @@ -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"` diff --git a/configs/bcash.json b/configs/bcash.json index 7782bb76..a18b5f94 100644 --- a/configs/bcash.json +++ b/configs/bcash.json @@ -1,5 +1,6 @@ { "coin_name": "Bcash", + "coin_shortcut": "BCH", "rpcURL": "http://127.0.0.1:8031", "rpcUser": "rpc", "rpcPass": "rpc", diff --git a/configs/bcash_testnet.json b/configs/bcash_testnet.json index ebe41a9d..e5cdbe47 100644 --- a/configs/bcash_testnet.json +++ b/configs/bcash_testnet.json @@ -1,5 +1,6 @@ { "coin_name": "Bcash Testnet", + "coin_shortcut": "TBCH", "rpcURL": "http://localhost:18031", "rpcUser": "rpc", "rpcPass": "rpc", diff --git a/configs/bgold.json b/configs/bgold.json index b77bd7e0..52d9c098 100644 --- a/configs/bgold.json +++ b/configs/bgold.json @@ -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 -} +} \ No newline at end of file diff --git a/configs/bitcoin.json b/configs/bitcoin.json index 97534f9c..ffa15701 100644 --- a/configs/bitcoin.json +++ b/configs/bitcoin.json @@ -1,5 +1,6 @@ { "coin_name": "Bitcoin", + "coin_shortcut": "BTC", "rpcURL": "http://127.0.0.1:8030", "rpcUser": "rpc", "rpcPass": "rpc", diff --git a/configs/bitcoin_testnet.json b/configs/bitcoin_testnet.json index 0bb00bb4..3ca273a3 100644 --- a/configs/bitcoin_testnet.json +++ b/configs/bitcoin_testnet.json @@ -1,5 +1,6 @@ { "coin_name": "Testnet", + "coin_shortcut": "TEST", "rpcURL": "http://localhost:18030", "rpcUser": "rpc", "rpcPass": "rpc", diff --git a/configs/dash.json b/configs/dash.json index 8ecf4818..fd97c74b 100644 --- a/configs/dash.json +++ b/configs/dash.json @@ -1,5 +1,6 @@ { "coin_name": "Dash", + "coin_shortcut": "DASH", "rpcURL": "http://127.0.0.1:8033", "rpcUser": "rpc", "rpcPass": "rpc", diff --git a/configs/dash_testnet.json b/configs/dash_testnet.json index 7a613a4d..35555196 100644 --- a/configs/dash_testnet.json +++ b/configs/dash_testnet.json @@ -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 -} +} \ No newline at end of file diff --git a/configs/dogecoin.json b/configs/dogecoin.json index c16fdc3a..10c1b551 100644 --- a/configs/dogecoin.json +++ b/configs/dogecoin.json @@ -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 -} +} \ No newline at end of file diff --git a/configs/ethereum.json b/configs/ethereum.json index 1a3ec103..d9b692b7 100644 --- a/configs/ethereum.json +++ b/configs/ethereum.json @@ -1,5 +1,6 @@ { "coin_name": "Ethereum", + "coin_shortcut": "ETH", "rpcURL": "ws://localhost:8036", "rpcTimeout": 25 } diff --git a/configs/ethereum_testnet_ropsten.json b/configs/ethereum_testnet_ropsten.json index d89fc039..b7c9b41a 100644 --- a/configs/ethereum_testnet_ropsten.json +++ b/configs/ethereum_testnet_ropsten.json @@ -1,5 +1,6 @@ { "coin_name": "Ethereum Testnet Ropsten", + "coin_shortcut": "tETH", "rpcURL": "ws://localhost:18036", "rpcTimeout": 25 -} +} \ No newline at end of file diff --git a/configs/litecoin.json b/configs/litecoin.json index 2a351101..6899770b 100644 --- a/configs/litecoin.json +++ b/configs/litecoin.json @@ -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 -} +} \ No newline at end of file diff --git a/configs/litecoin_testnet.json b/configs/litecoin_testnet.json index 67232ebb..99df3d18 100644 --- a/configs/litecoin_testnet.json +++ b/configs/litecoin_testnet.json @@ -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 -} +} \ No newline at end of file diff --git a/configs/namecoin.json b/configs/namecoin.json index 8fadb00d..b62a4489 100644 --- a/configs/namecoin.json +++ b/configs/namecoin.json @@ -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 -} +} \ No newline at end of file diff --git a/configs/vertcoin.json b/configs/vertcoin.json index b1dec1d5..6ee09e63 100644 --- a/configs/vertcoin.json +++ b/configs/vertcoin.json @@ -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 -} +} \ No newline at end of file diff --git a/configs/vertcoin_testnet.json b/configs/vertcoin_testnet.json index 45bead82..7d88ace9 100644 --- a/configs/vertcoin_testnet.json +++ b/configs/vertcoin_testnet.json @@ -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 -} +} \ No newline at end of file diff --git a/configs/zcash.json b/configs/zcash.json index e9f0126f..96850e65 100644 --- a/configs/zcash.json +++ b/configs/zcash.json @@ -1,5 +1,6 @@ { "coin_name": "Zcash", + "coin_shortcut": "ZEC", "rpcURL": "http://127.0.0.1:8032", "rpcUser": "rpc", "rpcPass": "rpc", diff --git a/configs/zcash_testnet.json b/configs/zcash_testnet.json index 6196bb76..436221fd 100644 --- a/configs/zcash_testnet.json +++ b/configs/zcash_testnet.json @@ -1,5 +1,6 @@ { "coin_name": "Zcash Testnet", + "coin_shortcut": "TAZ", "rpcURL": "http://127.0.0.1:18032", "rpcUser": "rpc", "rpcPass": "rpc", diff --git a/server/https.go b/server/internal.go similarity index 79% rename from server/https.go rename to server/internal.go index 0af9c3a1..a4752b86 100644 --- a/server/https.go +++ b/server/internal.go @@ -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)) diff --git a/server/public.go b/server/public.go new file mode 100644 index 00000000..54555325 --- /dev/null +++ b/server/public.go @@ -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) + } +} diff --git a/server/socketio.go b/server/socketio.go index eedda503..46c0a563 100644 --- a/server/socketio.go +++ b/server/socketio.go @@ -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 diff --git a/static/css/main.css b/static/css/main.css new file mode 100644 index 00000000..8265aadd --- /dev/null +++ b/static/css/main.css @@ -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; +} \ No newline at end of file diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 00000000..a1b20e0a Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/templates/address.html b/static/templates/address.html new file mode 100644 index 00000000..3a457703 --- /dev/null +++ b/static/templates/address.html @@ -0,0 +1,52 @@ +{{define "specific"}}{{$cs := .CoinShortcut}}{{$addr := .Address}}{{$data := .}} +

Address + {{formatAmount $addr.Balance}} {{$cs}} +

+
+ {{$addr.AddrStr}} +
+

Confirmed

+
+ + + + + + + + + + + + + + + + + + + +
Total Received{{formatAmount $addr.TotalReceived}} {{$cs}}
Total Sent{{formatAmount $addr.TotalSent}} {{$cs}}
Final Balance{{formatAmount $addr.Balance}} {{$cs}}
No. Transactions{{$addr.TxApperances}}
+
+{{if $addr.UnconfirmedTxApperances}} +

Unconfirmed

+
+ + + + + + + + + + + +
Unconfirmed Balance{{formatAmount $addr.UnconfirmedBalance}} {{$cs}}
No. Transactions{{$addr.UnconfirmedTxApperances}}
+
+{{end}} {{if $addr.Transactions}} +

Transactions

+
+ {{range $tx := $addr.Transactions}}{{$data := setTxToTemplateData $data $tx}}{{template "txdetail" $data }}{{end}} +
+{{end}} {{end}} \ No newline at end of file diff --git a/static/templates/base.html b/static/templates/base.html new file mode 100644 index 00000000..5ab2fb3e --- /dev/null +++ b/static/templates/base.html @@ -0,0 +1,75 @@ + + + + + + + + + + + TREZOR {{.CoinName}} Explorer + + + + +
+
+ {{template "specific" .}} +
+
+ + + + \ No newline at end of file diff --git a/static/templates/tx.html b/static/templates/tx.html new file mode 100644 index 00000000..2eb15224 --- /dev/null +++ b/static/templates/tx.html @@ -0,0 +1,44 @@ +{{define "specific"}}{{$cs := .CoinShortcut}}{{$tx := .Tx}} +

Transaction

+
+ {{$tx.Txid}} +
+

Summary

+
+ + + {{if $tx.Confirmations}} + + + + {{end}} + + + + + {{if $tx.Confirmations}} + + + + {{end}} + + + + + + + + + {{if $tx.Fees}} + + + + {{end}} + +
Mined Time{{formatUnixTime $tx.Blocktime}}
In Block{{if $tx.Confirmations}}{{$tx.Blockhash}}{{else}}Unconfirmed{{end}}
In Block Height{{$tx.Blockheight}}
Total Input{{formatAmount $tx.ValueIn}} {{$cs}}
Total Output{{formatAmount $tx.ValueOut}} {{$cs}}
Fees{{formatAmount $tx.Fees}} {{$cs}}
+
+

Details

+
+ {{template "txdetail" .}} +
+{{end}} \ No newline at end of file diff --git a/static/templates/txdetail.html b/static/templates/txdetail.html new file mode 100644 index 00000000..87ebd673 --- /dev/null +++ b/static/templates/txdetail.html @@ -0,0 +1,78 @@ +{{define "txdetail"}}{{$cs := .CoinShortcut}}{{$addr := .AddrStr}}{{$tx := .Tx}} +
+
+ + {{if $tx.Confirmations}} +
mined {{formatUnixTime $tx.Blocktime}}
+ {{end}} +
+
+
+
+ + + {{range $vin := $tx.Vin}} + + + + {{end}} + +
+ {{if $vin.Txid}} + + {{if $vin.Addr}}{{if eq $vin.Addr $addr}}{{$vin.Addr}}{{else}} + {{$vin.Addr}}{{end}}{{else}}Unparsed address{{end}} + + {{formatAmount $vin.Value}} {{$cs}} + {{else}}No Inputs (Newly Generated Coins){{end}} +
+
+
+
+ + + +
+
+
+ + + {{range $vout := $tx.Vout}} + + + + {{end}} + +
+ {{range $a := $vout.ScriptPubKey.Addresses}} + + {{if eq $a $addr}}{{$a}}{{else}} + {{$a}}{{end}} + + {{else}} + Unparsed address + {{end}} + {{formatAmount $vout.Value}} {{$cs}} +
+
+
+
+
+
+ {{if $tx.Fees}} + Fee: {{formatAmount $tx.Fees}} {{$cs}} + {{end}} +
+
+ {{if $tx.Confirmations}} + {{$tx.Confirmations}} Confirmations + {{else}} + Unconfirmed Transaction! + {{end}} + {{formatAmount $tx.ValueOut}} {{$cs}} +
+
+
+{{end}} \ No newline at end of file diff --git a/static/test.html b/static/test.html index 2d0d1733..e5e0789f 100644 --- a/static/test.html +++ b/static/test.html @@ -4,8 +4,7 @@ - +