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"`
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
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
|
||||
// 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()
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user