Keep api v1 as compatible with blockbook 0.1.x, add api v2
parent
9c142663ce
commit
e9e6b472b6
24
api/types.go
24
api/types.go
|
@ -52,6 +52,26 @@ func (a *Amount) MarshalJSON() (out []byte, err error) {
|
|||
return []byte(`"` + (*big.Int)(a).String() + `"`), nil
|
||||
}
|
||||
|
||||
func (a *Amount) String() string {
|
||||
if a == nil {
|
||||
return ""
|
||||
}
|
||||
return (*big.Int)(a).String()
|
||||
}
|
||||
|
||||
// DecimalString returns amount with decimal point placed according to parameter d
|
||||
func (a *Amount) DecimalString(d int) string {
|
||||
return bchain.AmountToDecimalString((*big.Int)(a), d)
|
||||
}
|
||||
|
||||
// AsBigInt returns big.Int type for the Amount (empty if Amount is nil)
|
||||
func (a *Amount) AsBigInt() big.Int {
|
||||
if a == nil {
|
||||
return *new(big.Int)
|
||||
}
|
||||
return big.Int(*a)
|
||||
}
|
||||
|
||||
// ScriptSig contains input script
|
||||
type ScriptSig struct {
|
||||
Hex string `json:"hex"`
|
||||
|
@ -135,8 +155,7 @@ type Tx struct {
|
|||
Confirmations uint32 `json:"confirmations"`
|
||||
Time int64 `json:"time,omitempty"`
|
||||
Blocktime int64 `json:"blocktime"`
|
||||
ValueOut string `json:"valueOut"`
|
||||
ValueOutSat *Amount `json:"-"`
|
||||
ValueOutSat *Amount `json:"valueOut,omitempty"`
|
||||
Size int `json:"size,omitempty"`
|
||||
ValueInSat *Amount `json:"valueIn,omitempty"`
|
||||
FeesSat *Amount `json:"fees,omitempty"`
|
||||
|
@ -219,6 +238,7 @@ type BlockbookInfo struct {
|
|||
InSyncMempool bool `json:"inSyncMempool"`
|
||||
LastMempoolTime time.Time `json:"lastMempoolTime"`
|
||||
MempoolSize int `json:"mempoolSize"`
|
||||
Decimals int `json:"decimals"`
|
||||
DbSize int64 `json:"dbSize"`
|
||||
DbSizeFromColumns int64 `json:"dbSizeFromColumns,omitempty"`
|
||||
DbColumns []common.InternalStateColumn `json:"dbColumns,omitempty"`
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// VinV1 is used for legacy api v1
|
||||
type VinV1 struct {
|
||||
Txid string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
Sequence int64 `json:"sequence,omitempty"`
|
||||
N int `json:"n"`
|
||||
ScriptSig ScriptSig `json:"scriptSig"`
|
||||
AddrDesc bchain.AddressDescriptor `json:"-"`
|
||||
Addresses []string `json:"addresses"`
|
||||
Searchable bool `json:"-"`
|
||||
Value string `json:"value"`
|
||||
ValueSat big.Int `json:"-"`
|
||||
}
|
||||
|
||||
// VoutV1 is used for legacy api v1
|
||||
type VoutV1 struct {
|
||||
Value string `json:"value"`
|
||||
ValueSat big.Int `json:"-"`
|
||||
N int `json:"n"`
|
||||
ScriptPubKey ScriptPubKey `json:"scriptPubKey"`
|
||||
Spent bool `json:"spent"`
|
||||
SpentTxID string `json:"spentTxId,omitempty"`
|
||||
SpentIndex int `json:"spentIndex,omitempty"`
|
||||
SpentHeight int `json:"spentHeight,omitempty"`
|
||||
}
|
||||
|
||||
// TxV1 is used for legacy api v1
|
||||
type TxV1 struct {
|
||||
Txid string `json:"txid"`
|
||||
Version int32 `json:"version,omitempty"`
|
||||
Locktime uint32 `json:"locktime,omitempty"`
|
||||
Vin []VinV1 `json:"vin"`
|
||||
Vout []VoutV1 `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 string `json:"valueOut"`
|
||||
ValueOutSat big.Int `json:"-"`
|
||||
Size int `json:"size,omitempty"`
|
||||
ValueIn string `json:"valueIn"`
|
||||
ValueInSat big.Int `json:"-"`
|
||||
Fees string `json:"fees"`
|
||||
FeesSat big.Int `json:"-"`
|
||||
Hex string `json:"hex"`
|
||||
}
|
||||
|
||||
// AddressV1 is used for legacy api v1
|
||||
type AddressV1 struct {
|
||||
Paging
|
||||
AddrStr string `json:"addrStr"`
|
||||
Balance string `json:"balance"`
|
||||
TotalReceived string `json:"totalReceived"`
|
||||
TotalSent string `json:"totalSent"`
|
||||
UnconfirmedBalance string `json:"unconfirmedBalance"`
|
||||
UnconfirmedTxApperances int `json:"unconfirmedTxApperances"`
|
||||
TxApperances int `json:"txApperances"`
|
||||
Transactions []*TxV1 `json:"txs,omitempty"`
|
||||
Txids []string `json:"transactions,omitempty"`
|
||||
}
|
||||
|
||||
// AddressUtxoV1 is used for legacy api v1
|
||||
type AddressUtxoV1 struct {
|
||||
Txid string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
Amount string `json:"amount"`
|
||||
AmountSat big.Int `json:"satoshis"`
|
||||
Height int `json:"height,omitempty"`
|
||||
Confirmations int `json:"confirmations"`
|
||||
}
|
||||
|
||||
// BlockV1 contains information about block
|
||||
type BlockV1 struct {
|
||||
Paging
|
||||
bchain.BlockInfo
|
||||
TxCount int `json:"TxCount"`
|
||||
Transactions []*TxV1 `json:"txs,omitempty"`
|
||||
}
|
||||
|
||||
// TxToV1 converts Tx to TxV1
|
||||
func (w *Worker) TxToV1(tx *Tx) *TxV1 {
|
||||
d := w.chainParser.AmountDecimals()
|
||||
vinV1 := make([]VinV1, len(tx.Vin))
|
||||
for i := range tx.Vin {
|
||||
v := &tx.Vin[i]
|
||||
vinV1[i] = VinV1{
|
||||
AddrDesc: v.AddrDesc,
|
||||
Addresses: v.Addresses,
|
||||
N: v.N,
|
||||
ScriptSig: v.ScriptSig,
|
||||
Searchable: v.Searchable,
|
||||
Sequence: v.Sequence,
|
||||
Txid: v.Txid,
|
||||
Value: v.ValueSat.DecimalString(d),
|
||||
ValueSat: v.ValueSat.AsBigInt(),
|
||||
Vout: v.Vout,
|
||||
}
|
||||
}
|
||||
voutV1 := make([]VoutV1, len(tx.Vout))
|
||||
for i := range tx.Vout {
|
||||
v := &tx.Vout[i]
|
||||
voutV1[i] = VoutV1{
|
||||
N: v.N,
|
||||
ScriptPubKey: v.ScriptPubKey,
|
||||
Spent: v.Spent,
|
||||
SpentHeight: v.SpentHeight,
|
||||
SpentIndex: v.SpentIndex,
|
||||
SpentTxID: v.SpentTxID,
|
||||
Value: v.ValueSat.DecimalString(d),
|
||||
ValueSat: v.ValueSat.AsBigInt(),
|
||||
}
|
||||
}
|
||||
return &TxV1{
|
||||
Blockhash: tx.Blockhash,
|
||||
Blockheight: tx.Blockheight,
|
||||
Blocktime: tx.Blocktime,
|
||||
Confirmations: tx.Confirmations,
|
||||
Fees: tx.FeesSat.DecimalString(d),
|
||||
FeesSat: tx.FeesSat.AsBigInt(),
|
||||
Hex: tx.Hex,
|
||||
Locktime: tx.Locktime,
|
||||
Size: tx.Size,
|
||||
Time: tx.Time,
|
||||
Txid: tx.Txid,
|
||||
ValueIn: tx.ValueInSat.DecimalString(d),
|
||||
ValueInSat: tx.ValueInSat.AsBigInt(),
|
||||
ValueOut: tx.ValueOutSat.DecimalString(d),
|
||||
ValueOutSat: tx.ValueOutSat.AsBigInt(),
|
||||
Version: tx.Version,
|
||||
Vin: vinV1,
|
||||
Vout: voutV1,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Worker) transactionsToV1(txs []*Tx) []*TxV1 {
|
||||
v1 := make([]*TxV1, len(txs))
|
||||
for i := range txs {
|
||||
v1[i] = w.TxToV1(txs[i])
|
||||
}
|
||||
return v1
|
||||
}
|
||||
|
||||
// AddressToV1 converts Address to AddressV1
|
||||
func (w *Worker) AddressToV1(a *Address) *AddressV1 {
|
||||
d := w.chainParser.AmountDecimals()
|
||||
return &AddressV1{
|
||||
AddrStr: a.AddrStr,
|
||||
Balance: a.BalanceSat.DecimalString(d),
|
||||
Paging: a.Paging,
|
||||
TotalReceived: a.TotalReceivedSat.DecimalString(d),
|
||||
TotalSent: a.TotalSentSat.DecimalString(d),
|
||||
Transactions: w.transactionsToV1(a.Transactions),
|
||||
TxApperances: a.TxApperances,
|
||||
Txids: a.Txids,
|
||||
UnconfirmedBalance: a.UnconfirmedBalanceSat.DecimalString(d),
|
||||
UnconfirmedTxApperances: a.UnconfirmedTxApperances,
|
||||
}
|
||||
}
|
||||
|
||||
// AddressUtxoToV1 converts []AddressUtxo to []AddressUtxoV1
|
||||
func (w *Worker) AddressUtxoToV1(au []AddressUtxo) []AddressUtxoV1 {
|
||||
d := w.chainParser.AmountDecimals()
|
||||
v1 := make([]AddressUtxoV1, len(au))
|
||||
for i := range au {
|
||||
utxo := &au[i]
|
||||
v1[i] = AddressUtxoV1{
|
||||
AmountSat: utxo.AmountSat.AsBigInt(),
|
||||
Amount: utxo.AmountSat.DecimalString(d),
|
||||
Confirmations: utxo.Confirmations,
|
||||
Height: utxo.Height,
|
||||
Txid: utxo.Txid,
|
||||
Vout: uint32(utxo.Vout),
|
||||
}
|
||||
}
|
||||
return v1
|
||||
}
|
||||
|
||||
// BlockToV1 converts Address to Address1
|
||||
func (w *Worker) BlockToV1(b *Block) *BlockV1 {
|
||||
return &BlockV1{
|
||||
BlockInfo: b.BlockInfo,
|
||||
Paging: b.Paging,
|
||||
Transactions: w.transactionsToV1(b.Transactions),
|
||||
TxCount: b.TxCount,
|
||||
}
|
||||
}
|
|
@ -918,6 +918,7 @@ func (w *Worker) GetSystemInfo(internal bool) (*SystemInfo, error) {
|
|||
InSyncMempool: ms,
|
||||
LastMempoolTime: mt,
|
||||
MempoolSize: msz,
|
||||
Decimals: w.chainParser.AmountDecimals(),
|
||||
DbSize: w.db.DatabaseSizeOnDisk(),
|
||||
DbSizeFromColumns: dbs,
|
||||
DbColumns: dbc,
|
||||
|
|
|
@ -86,6 +86,11 @@ func (p *BaseParser) AmountToDecimalString(a *big.Int) string {
|
|||
return AmountToDecimalString(a, p.AmountDecimalPoint)
|
||||
}
|
||||
|
||||
// AmountDecimals returns number of decimal places in amounts
|
||||
func (p *BaseParser) AmountDecimals() int {
|
||||
return p.AmountDecimalPoint
|
||||
}
|
||||
|
||||
// ParseTxFromJson parses JSON message containing transaction and returns Tx struct
|
||||
func (p *BaseParser) ParseTxFromJson(msg json.RawMessage) (*Tx, error) {
|
||||
var tx Tx
|
||||
|
|
|
@ -227,6 +227,8 @@ type BlockChainParser interface {
|
|||
// KeepBlockAddresses returns number of blocks which are to be kept in blockTxs column
|
||||
// to be used for rollbacks
|
||||
KeepBlockAddresses() int
|
||||
// AmountDecimals returns number of decimal places in coin amounts
|
||||
AmountDecimals() int
|
||||
// AmountToDecimalString converts amount in big.Int to string with decimal point in the correct place
|
||||
AmountToDecimalString(a *big.Int) string
|
||||
// AmountToBigInt converts amount in json.Number (string) to big.Int
|
||||
|
|
|
@ -26,6 +26,12 @@ const txsOnPage = 25
|
|||
const blocksOnPage = 50
|
||||
const txsInAPI = 1000
|
||||
|
||||
const (
|
||||
_ = iota
|
||||
apiV1
|
||||
apiV2
|
||||
)
|
||||
|
||||
// PublicServer is a handle to public http server
|
||||
type PublicServer struct {
|
||||
binding string
|
||||
|
@ -97,7 +103,7 @@ func NewPublicServer(binding string, certFiles string, db *db.RocksDB, chain bch
|
|||
// default handler
|
||||
serveMux.HandleFunc(path, s.htmlTemplateHandler(s.explorerIndex))
|
||||
// default API handler
|
||||
serveMux.HandleFunc(path+"api/", s.jsonHandler(s.apiIndex))
|
||||
serveMux.HandleFunc(path+"api/", s.jsonHandler(s.apiIndex, apiV2))
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
@ -134,14 +140,42 @@ func (s *PublicServer) ConnectFullPublicInterface() {
|
|||
serveMux.HandleFunc(path+"address/", s.addressRedirect)
|
||||
}
|
||||
// API calls
|
||||
serveMux.HandleFunc(path+"api/block-index/", s.jsonHandler(s.apiBlockIndex))
|
||||
serveMux.HandleFunc(path+"api/tx/", s.jsonHandler(s.apiTx))
|
||||
serveMux.HandleFunc(path+"api/tx-specific/", s.jsonHandler(s.apiTxSpecific))
|
||||
serveMux.HandleFunc(path+"api/address/", s.jsonHandler(s.apiAddress))
|
||||
serveMux.HandleFunc(path+"api/utxo/", s.jsonHandler(s.apiAddressUtxo))
|
||||
serveMux.HandleFunc(path+"api/block/", s.jsonHandler(s.apiBlock))
|
||||
serveMux.HandleFunc(path+"api/sendtx/", s.jsonHandler(s.apiSendTx))
|
||||
serveMux.HandleFunc(path+"api/estimatefee/", s.jsonHandler(s.apiEstimateFee))
|
||||
// default api without version can be changed to different version at any time
|
||||
// use versioned api for stability
|
||||
|
||||
var apiDefault int
|
||||
// ethereum supports only api V2
|
||||
if s.chainParser.GetChainType() == bchain.ChainEthereumType {
|
||||
apiDefault = apiV2
|
||||
} else {
|
||||
apiDefault = apiV1
|
||||
// legacy v1 format
|
||||
serveMux.HandleFunc(path+"api/v1/block-index/", s.jsonHandler(s.apiBlockIndex, apiV1))
|
||||
serveMux.HandleFunc(path+"api/v1/tx-specific/", s.jsonHandler(s.apiTxSpecific, apiV1))
|
||||
serveMux.HandleFunc(path+"api/v1/tx/", s.jsonHandler(s.apiTx, apiV1))
|
||||
serveMux.HandleFunc(path+"api/v1/address/", s.jsonHandler(s.apiAddress, apiV1))
|
||||
serveMux.HandleFunc(path+"api/v1/utxo/", s.jsonHandler(s.apiAddressUtxo, apiV1))
|
||||
serveMux.HandleFunc(path+"api/v1/block/", s.jsonHandler(s.apiBlock, apiV1))
|
||||
serveMux.HandleFunc(path+"api/v1/sendtx/", s.jsonHandler(s.apiSendTx, apiV1))
|
||||
serveMux.HandleFunc(path+"api/v1/estimatefee/", s.jsonHandler(s.apiEstimateFee, apiV1))
|
||||
}
|
||||
serveMux.HandleFunc(path+"api/block-index/", s.jsonHandler(s.apiBlockIndex, apiDefault))
|
||||
serveMux.HandleFunc(path+"api/tx-specific/", s.jsonHandler(s.apiTxSpecific, apiDefault))
|
||||
serveMux.HandleFunc(path+"api/tx/", s.jsonHandler(s.apiTx, apiDefault))
|
||||
serveMux.HandleFunc(path+"api/address/", s.jsonHandler(s.apiAddress, apiDefault))
|
||||
serveMux.HandleFunc(path+"api/utxo/", s.jsonHandler(s.apiAddressUtxo, apiDefault))
|
||||
serveMux.HandleFunc(path+"api/block/", s.jsonHandler(s.apiBlock, apiDefault))
|
||||
serveMux.HandleFunc(path+"api/sendtx/", s.jsonHandler(s.apiSendTx, apiDefault))
|
||||
serveMux.HandleFunc(path+"api/estimatefee/", s.jsonHandler(s.apiEstimateFee, apiDefault))
|
||||
// v2 format
|
||||
serveMux.HandleFunc(path+"api/v2/block-index/", s.jsonHandler(s.apiBlockIndex, apiV2))
|
||||
serveMux.HandleFunc(path+"api/v2/tx-specific/", s.jsonHandler(s.apiTxSpecific, apiV2))
|
||||
serveMux.HandleFunc(path+"api/v2/tx/", s.jsonHandler(s.apiTx, apiV2))
|
||||
serveMux.HandleFunc(path+"api/v2/address/", s.jsonHandler(s.apiAddress, apiV2))
|
||||
serveMux.HandleFunc(path+"api/v2/utxo/", s.jsonHandler(s.apiAddressUtxo, apiV2))
|
||||
serveMux.HandleFunc(path+"api/v2/block/", s.jsonHandler(s.apiBlock, apiV2))
|
||||
serveMux.HandleFunc(path+"api/v2/sendtx/", s.jsonHandler(s.apiSendTx, apiV2))
|
||||
serveMux.HandleFunc(path+"api/v2/estimatefee/", s.jsonHandler(s.apiEstimateFee, apiV2))
|
||||
// socket.io interface
|
||||
serveMux.Handle(path+"socket.io/", s.socketio.GetHandler())
|
||||
// websocket interface
|
||||
|
@ -204,7 +238,7 @@ func getFunctionName(i interface{}) string {
|
|||
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
|
||||
}
|
||||
|
||||
func (s *PublicServer) jsonHandler(handler func(r *http.Request) (interface{}, error)) func(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *PublicServer) jsonHandler(handler func(r *http.Request, apiVersion int) (interface{}, error), apiVersion int) func(w http.ResponseWriter, r *http.Request) {
|
||||
type jsonError struct {
|
||||
Text string `json:"error"`
|
||||
HTTPStatus int `json:"-"`
|
||||
|
@ -228,7 +262,7 @@ func (s *PublicServer) jsonHandler(handler func(r *http.Request) (interface{}, e
|
|||
}
|
||||
json.NewEncoder(w).Encode(data)
|
||||
}()
|
||||
data, err = handler(r)
|
||||
data, err = handler(r, apiVersion)
|
||||
if err != nil || data == nil {
|
||||
if apiErr, ok := err.(*api.APIError); ok {
|
||||
if apiErr.Public {
|
||||
|
@ -415,7 +449,7 @@ func formatAmountWithDecimals(a *api.Amount, d int) string {
|
|||
if a == nil {
|
||||
return "0"
|
||||
}
|
||||
return bchain.AmountToDecimalString((*big.Int)(a), d)
|
||||
return a.DecimalString(d)
|
||||
}
|
||||
|
||||
// called from template to support txdetail.html functionality
|
||||
|
@ -659,12 +693,12 @@ func getPagingRange(page int, total int) ([]int, int, int) {
|
|||
return r, pp, np
|
||||
}
|
||||
|
||||
func (s *PublicServer) apiIndex(r *http.Request) (interface{}, error) {
|
||||
func (s *PublicServer) apiIndex(r *http.Request, apiVersion int) (interface{}, error) {
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-index"}).Inc()
|
||||
return s.api.GetSystemInfo(false)
|
||||
}
|
||||
|
||||
func (s *PublicServer) apiBlockIndex(r *http.Request) (interface{}, error) {
|
||||
func (s *PublicServer) apiBlockIndex(r *http.Request, apiVersion int) (interface{}, error) {
|
||||
type resBlockIndex struct {
|
||||
BlockHash string `json:"blockHash"`
|
||||
}
|
||||
|
@ -690,7 +724,7 @@ func (s *PublicServer) apiBlockIndex(r *http.Request) (interface{}, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (s *PublicServer) apiTx(r *http.Request) (interface{}, error) {
|
||||
func (s *PublicServer) apiTx(r *http.Request, apiVersion int) (interface{}, error) {
|
||||
var tx *api.Tx
|
||||
var err error
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-tx"}).Inc()
|
||||
|
@ -705,11 +739,14 @@ func (s *PublicServer) apiTx(r *http.Request) (interface{}, error) {
|
|||
}
|
||||
}
|
||||
tx, err = s.api.GetTransaction(txid, spendingTxs, false)
|
||||
if err == nil && apiVersion == apiV1 {
|
||||
return s.api.TxToV1(tx), nil
|
||||
}
|
||||
}
|
||||
return tx, err
|
||||
}
|
||||
|
||||
func (s *PublicServer) apiTxSpecific(r *http.Request) (interface{}, error) {
|
||||
func (s *PublicServer) apiTxSpecific(r *http.Request, apiVersion int) (interface{}, error) {
|
||||
var tx json.RawMessage
|
||||
var err error
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-tx-specific"}).Inc()
|
||||
|
@ -720,7 +757,7 @@ func (s *PublicServer) apiTxSpecific(r *http.Request) (interface{}, error) {
|
|||
return tx, err
|
||||
}
|
||||
|
||||
func (s *PublicServer) apiAddress(r *http.Request) (interface{}, error) {
|
||||
func (s *PublicServer) apiAddress(r *http.Request, apiVersion int) (interface{}, error) {
|
||||
var address *api.Address
|
||||
var err error
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-address"}).Inc()
|
||||
|
@ -730,11 +767,14 @@ func (s *PublicServer) apiAddress(r *http.Request) (interface{}, error) {
|
|||
page = 0
|
||||
}
|
||||
address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsInAPI, api.TxidHistory, api.AddressFilterNone)
|
||||
if err == nil && apiVersion == apiV1 {
|
||||
return s.api.AddressToV1(address), nil
|
||||
}
|
||||
}
|
||||
return address, err
|
||||
}
|
||||
|
||||
func (s *PublicServer) apiAddressUtxo(r *http.Request) (interface{}, error) {
|
||||
func (s *PublicServer) apiAddressUtxo(r *http.Request, apiVersion int) (interface{}, error) {
|
||||
var utxo []api.AddressUtxo
|
||||
var err error
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-address"}).Inc()
|
||||
|
@ -748,11 +788,14 @@ func (s *PublicServer) apiAddressUtxo(r *http.Request) (interface{}, error) {
|
|||
}
|
||||
}
|
||||
utxo, err = s.api.GetAddressUtxo(r.URL.Path[i+1:], onlyConfirmed)
|
||||
if err == nil && apiVersion == apiV1 {
|
||||
return s.api.AddressUtxoToV1(utxo), nil
|
||||
}
|
||||
}
|
||||
return utxo, err
|
||||
}
|
||||
|
||||
func (s *PublicServer) apiBlock(r *http.Request) (interface{}, error) {
|
||||
func (s *PublicServer) apiBlock(r *http.Request, apiVersion int) (interface{}, error) {
|
||||
var block *api.Block
|
||||
var err error
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-block"}).Inc()
|
||||
|
@ -762,6 +805,9 @@ func (s *PublicServer) apiBlock(r *http.Request) (interface{}, error) {
|
|||
page = 0
|
||||
}
|
||||
block, err = s.api.GetBlock(r.URL.Path[i+1:], page, txsInAPI)
|
||||
if err == nil && apiVersion == apiV1 {
|
||||
return s.api.BlockToV1(block), nil
|
||||
}
|
||||
}
|
||||
return block, err
|
||||
}
|
||||
|
@ -770,7 +816,7 @@ type resultSendTransaction struct {
|
|||
Result string `json:"result"`
|
||||
}
|
||||
|
||||
func (s *PublicServer) apiSendTx(r *http.Request) (interface{}, error) {
|
||||
func (s *PublicServer) apiSendTx(r *http.Request, apiVersion int) (interface{}, error) {
|
||||
var err error
|
||||
var res resultSendTransaction
|
||||
var hex string
|
||||
|
@ -800,7 +846,7 @@ type resultEstimateFeeAsString struct {
|
|||
Result string `json:"result"`
|
||||
}
|
||||
|
||||
func (s *PublicServer) apiEstimateFee(r *http.Request) (interface{}, error) {
|
||||
func (s *PublicServer) apiEstimateFee(r *http.Request, apiVersion int) (interface{}, error) {
|
||||
var res resultEstimateFeeAsString
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-estimatefee"}).Inc()
|
||||
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||
|
|
|
@ -131,7 +131,7 @@ func newPostRequest(u string, body string) *http.Request {
|
|||
return r
|
||||
}
|
||||
|
||||
func httpTests(t *testing.T, ts *httptest.Server) {
|
||||
func httpTests_BitcoinType(t *testing.T, ts *httptest.Server) {
|
||||
tests := []struct {
|
||||
name string
|
||||
r *http.Request
|
||||
|
@ -359,6 +359,7 @@ func httpTests(t *testing.T, ts *httptest.Server) {
|
|||
body: []string{
|
||||
`{"blockbook":{"coin":"Fakecoin"`,
|
||||
`"bestHeight":225494`,
|
||||
`"decimals":8`,
|
||||
`"backend":{"chain":"fakecoin","blocks":2,"headers":2,"bestblockhash":"00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6"`,
|
||||
`"version":"001001","subversion":"/Fakecoin:0.0.1/"`,
|
||||
},
|
||||
|
@ -373,17 +374,35 @@ func httpTests(t *testing.T, ts *httptest.Server) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "apiTx",
|
||||
r: newGetRequest(ts.URL + "/api/tx/05e2e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07"),
|
||||
name: "apiTx v1",
|
||||
r: newGetRequest(ts.URL + "/api/v1/tx/05e2e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07"),
|
||||
status: http.StatusOK,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
body: []string{
|
||||
`{"txid":"05e2e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07","vin":[{"txid":"effd9ef509383d536b1c8af5bf434c8efbf521a4f2befd4022bbd68694b4ac75","vout":2,"n":0,"scriptSig":{"hex":""},"addresses":["2NEVv9LJmAnY99W1pFoc5UJjVdypBqdnvu1"],"value":"0.00009876"}],"vout":[{"value":"0.00009","n":0,"scriptPubKey":{"hex":"a914e921fc4912a315078f370d959f2c4f7b6d2a683c87","addresses":["2NEVv9LJmAnY99W1pFoc5UJjVdypBqdnvu1"]},"spent":false}],"blockhash":"00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6","blockheight":225494,"confirmations":1,"time":22549400002,"blocktime":22549400002,"valueOut":"0.00009","valueIn":"0.00009876","fees":"0.00000876"}`,
|
||||
`{"txid":"05e2e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07","vin":[{"txid":"effd9ef509383d536b1c8af5bf434c8efbf521a4f2befd4022bbd68694b4ac75","vout":2,"n":0,"scriptSig":{"hex":""},"addresses":["2NEVv9LJmAnY99W1pFoc5UJjVdypBqdnvu1"],"value":"0.00009876"}],"vout":[{"value":"0.00009","n":0,"scriptPubKey":{"hex":"a914e921fc4912a315078f370d959f2c4f7b6d2a683c87","addresses":["2NEVv9LJmAnY99W1pFoc5UJjVdypBqdnvu1"]},"spent":false}],"blockhash":"00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6","blockheight":225494,"confirmations":1,"time":22549400002,"blocktime":22549400002,"valueOut":"0.00009","valueIn":"0.00009876","fees":"0.00000876","hex":""}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "apiTx - not found",
|
||||
r: newGetRequest(ts.URL + "/api/tx/1232e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07"),
|
||||
name: "apiTx - not found v1",
|
||||
r: newGetRequest(ts.URL + "/api/v1/tx/1232e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07"),
|
||||
status: http.StatusBadRequest,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
body: []string{
|
||||
`{"error":"Tx not found, Not found"}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "apiTx v2",
|
||||
r: newGetRequest(ts.URL + "/api/v2/tx/05e2e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07"),
|
||||
status: http.StatusOK,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
body: []string{
|
||||
`{"txid":"05e2e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07","vin":[{"txid":"effd9ef509383d536b1c8af5bf434c8efbf521a4f2befd4022bbd68694b4ac75","vout":2,"n":0,"scriptSig":{"hex":""},"addresses":["2NEVv9LJmAnY99W1pFoc5UJjVdypBqdnvu1"],"value":"9876"}],"vout":[{"value":"9000","n":0,"scriptPubKey":{"hex":"a914e921fc4912a315078f370d959f2c4f7b6d2a683c87","addresses":["2NEVv9LJmAnY99W1pFoc5UJjVdypBqdnvu1"]},"spent":false}],"blockhash":"00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6","blockheight":225494,"confirmations":1,"time":22549400002,"blocktime":22549400002,"valueOut":"9000","valueIn":"9876","fees":"876"}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "apiTx - not found v2",
|
||||
r: newGetRequest(ts.URL + "/api/v2/tx/1232e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07"),
|
||||
status: http.StatusBadRequest,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
body: []string{
|
||||
|
@ -400,23 +419,41 @@ func httpTests(t *testing.T, ts *httptest.Server) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "apiAddress",
|
||||
r: newGetRequest(ts.URL + "/api/address/mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw"),
|
||||
name: "apiAddress v1",
|
||||
r: newGetRequest(ts.URL + "/api/v1/address/mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw"),
|
||||
status: http.StatusOK,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
body: []string{
|
||||
`{"page":1,"totalPages":1,"itemsOnPage":1000,"addrStr":"mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw","balance":"0","balanceSat":"0","totalReceived":"12345.67890123","totalSent":"12345.67890123","unconfirmedBalance":"0","unconfirmedBalanceSat":"0","unconfirmedTxApperances":0,"txApperances":2,"transactions":["7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25","effd9ef509383d536b1c8af5bf434c8efbf521a4f2befd4022bbd68694b4ac75"]}`,
|
||||
`{"page":1,"totalPages":1,"itemsOnPage":1000,"addrStr":"mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw","balance":"0","totalReceived":"12345.67890123","totalSent":"12345.67890123","unconfirmedBalance":"0","unconfirmedTxApperances":0,"txApperances":2,"transactions":["7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25","effd9ef509383d536b1c8af5bf434c8efbf521a4f2befd4022bbd68694b4ac75"]}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "apiAddressUtxo",
|
||||
r: newGetRequest(ts.URL + "/api/utxo/mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL"),
|
||||
name: "apiAddress v2",
|
||||
r: newGetRequest(ts.URL + "/api/v2/address/mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw"),
|
||||
status: http.StatusOK,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
body: []string{
|
||||
`{"page":1,"totalPages":1,"itemsOnPage":1000,"addrStr":"mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw","balance":"0","totalReceived":"1234567890123","totalSent":"1234567890123","unconfirmedBalance":"0","unconfirmedTxApperances":0,"txApperances":2,"txids":["7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25","effd9ef509383d536b1c8af5bf434c8efbf521a4f2befd4022bbd68694b4ac75"]}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "apiAddressUtxo v1",
|
||||
r: newGetRequest(ts.URL + "/api/v1/utxo/mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL"),
|
||||
status: http.StatusOK,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
body: []string{
|
||||
`[{"txid":"7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25","vout":1,"amount":"9172.83951061","satoshis":917283951061,"height":225494,"confirmations":1}]`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "apiAddressUtxo v2",
|
||||
r: newGetRequest(ts.URL + "/api/v2/utxo/mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL"),
|
||||
status: http.StatusOK,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
body: []string{
|
||||
`[{"txid":"7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25","vout":1,"value":"917283951061","height":225494,"confirmations":1}]`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "apiSendTx",
|
||||
r: newGetRequest(ts.URL + "/api/sendtx/1234567890"),
|
||||
|
@ -475,7 +512,7 @@ func httpTests(t *testing.T, ts *httptest.Server) {
|
|||
b := string(bb)
|
||||
for _, c := range tt.body {
|
||||
if !strings.Contains(b, c) {
|
||||
t.Errorf("Page body does not contain %v, body %v", c, b)
|
||||
t.Errorf("got %v, want to contain %v", b, c)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -483,7 +520,7 @@ func httpTests(t *testing.T, ts *httptest.Server) {
|
|||
}
|
||||
}
|
||||
|
||||
func socketioTests(t *testing.T, ts *httptest.Server) {
|
||||
func socketioTests_BitcoinType(t *testing.T, ts *httptest.Server) {
|
||||
type socketioReq struct {
|
||||
Method string `json:"method"`
|
||||
Params []interface{} `json:"params"`
|
||||
|
@ -578,7 +615,7 @@ func socketioTests(t *testing.T, ts *httptest.Server) {
|
|||
t.Errorf("Socketio error %v", err)
|
||||
}
|
||||
if resp != tt.want {
|
||||
t.Errorf("Socketio resp %v, want %v", resp, tt.want)
|
||||
t.Errorf("got %v, want %v", resp, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -592,6 +629,6 @@ func Test_PublicServer_BitcoinType(t *testing.T) {
|
|||
ts := httptest.NewServer(s.https.Handler)
|
||||
defer ts.Close()
|
||||
|
||||
httpTests(t, ts)
|
||||
socketioTests(t, ts)
|
||||
httpTests_BitcoinType(t, ts)
|
||||
socketioTests_BitcoinType(t, ts)
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<tr>
|
||||
<th>Contract</th>
|
||||
<th>Tokens</th>
|
||||
<th style="width: 15%;">No. Txs</th>
|
||||
<th style="width: 15%;">Transfers</th>
|
||||
</tr>
|
||||
{{- range $et := $addr.Erc20Tokens -}}
|
||||
<tr>
|
||||
|
|
Loading…
Reference in New Issue