Implement address utxo api call #83
This commit is contained in:
parent
2dca694f95
commit
8bf5837b26
10
api/types.go
10
api/types.go
@ -111,6 +111,16 @@ type Address struct {
|
|||||||
Txids []string `json:"transactions,omitempty"`
|
Txids []string `json:"transactions,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddressUtxo holds information about address and its transactions
|
||||||
|
type AddressUtxo 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"`
|
||||||
|
}
|
||||||
|
|
||||||
// Blocks is list of blocks with paging information
|
// Blocks is list of blocks with paging information
|
||||||
type Blocks struct {
|
type Blocks struct {
|
||||||
Paging
|
Paging
|
||||||
|
|||||||
100
api/worker.go
100
api/worker.go
@ -45,7 +45,7 @@ func (w *Worker) getAddressesFromVout(vout *bchain.Vout) (bchain.AddressDescript
|
|||||||
}
|
}
|
||||||
|
|
||||||
// setSpendingTxToVout is helper function, that finds transaction that spent given output and sets it to the output
|
// setSpendingTxToVout is helper function, that finds transaction that spent given output and sets it to the output
|
||||||
// there is not an index, it must be found using addresses -> txaddresses -> tx
|
// there is no direct index for the operation, it must be found using addresses -> txaddresses -> tx
|
||||||
func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height uint32) error {
|
func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height uint32) error {
|
||||||
err := w.db.GetAddrDescTransactions(vout.ScriptPubKey.AddrDesc, height, ^uint32(0), func(t string, index uint32, isOutput bool) error {
|
err := w.db.GetAddrDescTransactions(vout.ScriptPubKey.AddrDesc, height, ^uint32(0), func(t string, index uint32, isOutput bool) error {
|
||||||
if isOutput == false {
|
if isOutput == false {
|
||||||
@ -102,12 +102,13 @@ func (w *Worker) GetTransaction(txid string, spendingTxs bool) (*Tx, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewAPIError(fmt.Sprintf("Tx not found, %v", err), true)
|
return nil, NewAPIError(fmt.Sprintf("Tx not found, %v", err), true)
|
||||||
}
|
}
|
||||||
ta, err := w.db.GetTxAddresses(txid)
|
var ta *db.TxAddresses
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Annotatef(err, "GetTxAddresses %v", txid)
|
|
||||||
}
|
|
||||||
var blockhash string
|
var blockhash string
|
||||||
if bchainTx.Confirmations > 0 {
|
if bchainTx.Confirmations > 0 {
|
||||||
|
ta, err = w.db.GetTxAddresses(txid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Annotatef(err, "GetTxAddresses %v", txid)
|
||||||
|
}
|
||||||
blockhash, err = w.db.GetBlockHash(height)
|
blockhash, err = w.db.GetBlockHash(height)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Annotatef(err, "GetBlockHash %v", height)
|
return nil, errors.Annotatef(err, "GetBlockHash %v", height)
|
||||||
@ -476,6 +477,95 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids b
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAddressUtxo returns unspent outputs for given address
|
||||||
|
func (w *Worker) GetAddressUtxo(address string) ([]AddressUtxo, error) {
|
||||||
|
start := time.Now()
|
||||||
|
addrDesc, err := w.chainParser.GetAddrDescFromAddress(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewAPIError(fmt.Sprintf("Invalid address, %v", err), true)
|
||||||
|
}
|
||||||
|
var r []AddressUtxo
|
||||||
|
// get utxo from mempool
|
||||||
|
txm, err := w.getAddressTxids(addrDesc, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Annotatef(err, "getAddressTxids %v true", address)
|
||||||
|
}
|
||||||
|
for _, txid := range txm {
|
||||||
|
bchainTx, _, err := w.txCache.GetTransaction(txid)
|
||||||
|
// mempool transaction may fail
|
||||||
|
if err != nil {
|
||||||
|
glog.Error("GetTransaction in mempool ", txid, ": ", err)
|
||||||
|
} else {
|
||||||
|
for i := range bchainTx.Vout {
|
||||||
|
bchainVout := &bchainTx.Vout[i]
|
||||||
|
vad, err := w.chainParser.GetAddrDescFromVout(bchainVout)
|
||||||
|
if err == nil && bytes.Equal(addrDesc, vad) {
|
||||||
|
r = append(r, AddressUtxo{
|
||||||
|
Txid: bchainTx.Txid,
|
||||||
|
Vout: uint32(i),
|
||||||
|
AmountSat: bchainVout.ValueSat,
|
||||||
|
Amount: w.chainParser.AmountToDecimalString(&bchainVout.ValueSat),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// get utxo from index
|
||||||
|
ba, err := w.db.GetAddrDescBalance(addrDesc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true)
|
||||||
|
}
|
||||||
|
// ba can be nil if the address is only in mempool!
|
||||||
|
if ba != nil && ba.BalanceSat.Uint64() > 0 {
|
||||||
|
type outpoint struct {
|
||||||
|
txid string
|
||||||
|
vout uint32
|
||||||
|
}
|
||||||
|
txids := make([]outpoint, 0)
|
||||||
|
err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, vout uint32, isOutput bool) error {
|
||||||
|
if isOutput {
|
||||||
|
txids = append(txids, outpoint{txid, vout})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
var lastTxid string
|
||||||
|
var ta *db.TxAddresses
|
||||||
|
total := ba.BalanceSat
|
||||||
|
b, _, err := w.db.GetBestBlock()
|
||||||
|
bestheight := int(b)
|
||||||
|
for i := len(txids) - 1; i >= 0 && total.Int64() > 0; i-- {
|
||||||
|
o := txids[i]
|
||||||
|
if lastTxid != o.txid {
|
||||||
|
ta, err = w.db.GetTxAddresses(o.txid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lastTxid = o.txid
|
||||||
|
}
|
||||||
|
if ta == nil {
|
||||||
|
glog.Warning("DB inconsistency: tx ", o.txid, ": not found in txAddresses")
|
||||||
|
} else {
|
||||||
|
if len(ta.Outputs) <= int(o.vout) {
|
||||||
|
glog.Warning("DB inconsistency: txAddresses ", o.txid, " does not have enough outputs")
|
||||||
|
} else {
|
||||||
|
v := ta.Outputs[o.vout].ValueSat
|
||||||
|
r = append(r, AddressUtxo{
|
||||||
|
Txid: o.txid,
|
||||||
|
Vout: o.vout,
|
||||||
|
AmountSat: v,
|
||||||
|
Amount: w.chainParser.AmountToDecimalString(&v),
|
||||||
|
Height: int(ta.Height),
|
||||||
|
Confirmations: bestheight - int(ta.Height) + 1,
|
||||||
|
})
|
||||||
|
total.Sub(&total, &v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glog.Info("GetAddressUtxo ", address, ", ", len(r), " utxos, finished in ", time.Since(start))
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetBlocks returns BlockInfo for blocks on given page
|
// GetBlocks returns BlockInfo for blocks on given page
|
||||||
func (w *Worker) GetBlocks(page int, blocksOnPage int) (*Blocks, error) {
|
func (w *Worker) GetBlocks(page int, blocksOnPage int) (*Blocks, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|||||||
@ -129,6 +129,7 @@ func (s *PublicServer) ConnectFullPublicInterface() {
|
|||||||
serveMux.HandleFunc(path+"api/tx/", s.jsonHandler(s.apiTx))
|
serveMux.HandleFunc(path+"api/tx/", s.jsonHandler(s.apiTx))
|
||||||
serveMux.HandleFunc(path+"api/tx-specific/", s.jsonHandler(s.apiTxSpecific))
|
serveMux.HandleFunc(path+"api/tx-specific/", s.jsonHandler(s.apiTxSpecific))
|
||||||
serveMux.HandleFunc(path+"api/address/", s.jsonHandler(s.apiAddress))
|
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/block/", s.jsonHandler(s.apiBlock))
|
||||||
serveMux.HandleFunc(path+"api/sendtx/", s.jsonHandler(s.apiSendTx))
|
serveMux.HandleFunc(path+"api/sendtx/", s.jsonHandler(s.apiSendTx))
|
||||||
serveMux.HandleFunc(path+"api/estimatefee/", s.jsonHandler(s.apiEstimateFee))
|
serveMux.HandleFunc(path+"api/estimatefee/", s.jsonHandler(s.apiEstimateFee))
|
||||||
@ -685,6 +686,16 @@ func (s *PublicServer) apiAddress(r *http.Request) (interface{}, error) {
|
|||||||
return address, err
|
return address, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *PublicServer) apiAddressUtxo(r *http.Request) (interface{}, error) {
|
||||||
|
var utxo []api.AddressUtxo
|
||||||
|
var err error
|
||||||
|
s.metrics.ExplorerViews.With(common.Labels{"action": "api-address"}).Inc()
|
||||||
|
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||||
|
utxo, err = s.api.GetAddressUtxo(r.URL.Path[i+1:])
|
||||||
|
}
|
||||||
|
return utxo, err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *PublicServer) apiBlock(r *http.Request) (interface{}, error) {
|
func (s *PublicServer) apiBlock(r *http.Request) (interface{}, error) {
|
||||||
var block *api.Block
|
var block *api.Block
|
||||||
var err error
|
var err error
|
||||||
|
|||||||
@ -408,6 +408,15 @@ func httpTests(t *testing.T, ts *httptest.Server) {
|
|||||||
`{"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"]}`,
|
`{"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"),
|
||||||
|
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: "apiSendTx",
|
name: "apiSendTx",
|
||||||
r: newGetRequest(ts.URL + "/api/sendtx/1234567890"),
|
r: newGetRequest(ts.URL + "/api/sendtx/1234567890"),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user