Implement address utxo api call #83

This commit is contained in:
Martin Boehm 2018-11-14 14:46:42 +01:00
parent 2dca694f95
commit 8bf5837b26
4 changed files with 125 additions and 5 deletions

View File

@ -111,6 +111,16 @@ type Address struct {
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
type Blocks struct {
Paging

View File

@ -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
// 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 {
err := w.db.GetAddrDescTransactions(vout.ScriptPubKey.AddrDesc, height, ^uint32(0), func(t string, index uint32, isOutput bool) error {
if isOutput == false {
@ -102,12 +102,13 @@ func (w *Worker) GetTransaction(txid string, spendingTxs bool) (*Tx, error) {
if err != nil {
return nil, NewAPIError(fmt.Sprintf("Tx not found, %v", err), true)
}
ta, err := w.db.GetTxAddresses(txid)
if err != nil {
return nil, errors.Annotatef(err, "GetTxAddresses %v", txid)
}
var ta *db.TxAddresses
var blockhash string
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)
if err != nil {
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
}
// 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
func (w *Worker) GetBlocks(page int, blocksOnPage int) (*Blocks, error) {
start := time.Now()

View File

@ -129,6 +129,7 @@ func (s *PublicServer) ConnectFullPublicInterface() {
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))
@ -685,6 +686,16 @@ func (s *PublicServer) apiAddress(r *http.Request) (interface{}, error) {
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) {
var block *api.Block
var err error

View File

@ -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"]}`,
},
},
{
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",
r: newGetRequest(ts.URL + "/api/sendtx/1234567890"),