diff --git a/api/worker.go b/api/worker.go index adf4fd4c..8b14639c 100644 --- a/api/worker.go +++ b/api/worker.go @@ -478,35 +478,54 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, onlyTxids b } // GetAddressUtxo returns unspent outputs for given address -func (w *Worker) GetAddressUtxo(address string) ([]AddressUtxo, error) { +func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) ([]AddressUtxo, error) { start := time.Now() addrDesc, err := w.chainParser.GetAddrDescFromAddress(address) if err != nil { return nil, NewAPIError(fmt.Sprintf("Invalid address, %v", err), true) } + spentInMempool := make(map[string]struct{}) r := make([]AddressUtxo, 0, 8) - // get utxo from mempool - txm, err := w.getAddressTxids(addrDesc, true) - if err != nil { - return nil, errors.Annotatef(err, "getAddressTxids %v true", address) - } - txm = UniqueTxidsInReverse(txm) - for _, txid := range txm { - bchainTx, _, err := w.txCache.GetTransaction(txid) - // mempool transaction may fail + if !onlyConfirmed { + // get utxo from mempool + txm, err := w.getAddressTxids(addrDesc, true) 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), - }) + return nil, errors.Annotatef(err, "getAddressTxids %v true", address) + } + txm = UniqueTxidsInReverse(txm) + mc := make([]*bchain.Tx, len(txm)) + for i, txid := range txm { + // get mempool txs and process their inputs to detect spends between mempool txs + bchainTx, _, err := w.txCache.GetTransaction(txid) + // mempool transaction may fail + if err != nil { + glog.Error("GetTransaction in mempool ", txid, ": ", err) + } else { + mc[i] = bchainTx + // get outputs spent by the mempool tx + for i := range bchainTx.Vin { + vin := &bchainTx.Vin[i] + spentInMempool[vin.Txid+strconv.Itoa(int(vin.Vout))] = struct{}{} + } + } + } + for _, bchainTx := range mc { + if bchainTx != nil { + for i := range bchainTx.Vout { + vout := &bchainTx.Vout[i] + vad, err := w.chainParser.GetAddrDescFromVout(vout) + if err == nil && bytes.Equal(addrDesc, vad) { + // report only outpoints that are not spent in mempool + _, e := spentInMempool[bchainTx.Txid+strconv.Itoa(i)] + if !e { + r = append(r, AddressUtxo{ + Txid: bchainTx.Txid, + Vout: uint32(i), + AmountSat: vout.ValueSat, + Amount: w.chainParser.AmountToDecimalString(&vout.ValueSat), + }) + } + } } } } @@ -558,14 +577,18 @@ func (w *Worker) GetAddressUtxo(address string) ([]AddressUtxo, error) { } else { if !ta.Outputs[o.vout].Spent { 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, - }) + // report only outpoints that are not spent in mempool + _, e := spentInMempool[o.txid+strconv.Itoa(int(o.vout))] + if !e { + 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, + }) + } checksum.Sub(&checksum, &v) } } diff --git a/server/public.go b/server/public.go index 85814e75..b2e5c5f2 100644 --- a/server/public.go +++ b/server/public.go @@ -691,7 +691,15 @@ func (s *PublicServer) apiAddressUtxo(r *http.Request) (interface{}, error) { 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:]) + onlyConfirmed := false + c := r.URL.Query().Get("confirmed") + if len(c) > 0 { + onlyConfirmed, err = strconv.ParseBool(c) + if err != nil { + return nil, api.NewAPIError("Parameter 'confirmed' cannot be converted to boolean", true) + } + } + utxo, err = s.api.GetAddressUtxo(r.URL.Path[i+1:], onlyConfirmed) } return utxo, err }