From 273436f10990e02e1e9f9442ea3abe29390eacef Mon Sep 17 00:00:00 2001 From: Martin Boehm Date: Fri, 8 Feb 2019 00:42:38 +0100 Subject: [PATCH] Include mempool transactions in GetAddressForXpub --- api/xpub.go | 205 +++++++++++++++++++++++++------------------- static/css/main.css | 2 +- 2 files changed, 116 insertions(+), 91 deletions(-) diff --git a/api/xpub.go b/api/xpub.go index b03b5700..bc15da54 100644 --- a/api/xpub.go +++ b/api/xpub.go @@ -107,7 +107,7 @@ func (w *Worker) xpubGetAddressTxids(addrDesc bchain.AddressDescriptor, mempool return txs, complete, nil } -func (w *Worker) xpubCheckAndLoadTxids(ad *xpubAddress, filter *AddressFilter, maxHeight uint32, pageSize int) error { +func (w *Worker) xpubCheckAndLoadTxids(ad *xpubAddress, filter *AddressFilter, maxHeight uint32, maxResults int) error { // skip if not discovered if ad.balance == nil { return nil @@ -130,9 +130,9 @@ func (w *Worker) xpubCheckAndLoadTxids(ad *xpubAddress, filter *AddressFilter, m } // unless the filter is completely off, load all txids if filter.FromHeight != 0 || filter.ToHeight != 0 || filter.Vout != AddressFilterVoutOff { - pageSize = maxInt + maxResults = maxInt } - newTxids, complete, err := w.xpubGetAddressTxids(ad.addrDesc, false, 0, maxHeight, pageSize) + newTxids, complete, err := w.xpubGetAddressTxids(ad.addrDesc, false, 0, maxHeight, maxResults) if err != nil { return err } @@ -245,9 +245,13 @@ func (w *Worker) GetAddressForXpub(xpub string, page int, txsOnPage int, option cachedXpubsMux.Lock() data, found := cachedXpubs[xpub] cachedXpubsMux.Unlock() + type mempoolMap struct { + tx *Tx + inputOutput byte + } var ( txc xpubTxids - txm []string + txmMap map[string]*Tx txs []*Tx txids []string pg Paging @@ -255,6 +259,7 @@ func (w *Worker) GetAddressForXpub(xpub string, page int, txsOnPage int, option err error bestheight uint32 besthash string + uBalSat big.Int ) // to load all data for xpub may take some time, do it in a loop to process a possible new block for { @@ -302,14 +307,11 @@ func (w *Worker) GetAddressForXpub(xpub string, page int, txsOnPage int, option glog.Info("Scanned ", len(data.addresses)+len(data.changeAddresses), " addresses in ", time.Since(start)) } if option >= TxidHistory { - for i := range data.addresses { - if err = w.xpubCheckAndLoadTxids(&data.addresses[i], filter, bestheight, (page+1)*txsOnPage); err != nil { - return nil, err - } - } - for i := range data.changeAddresses { - if err = w.xpubCheckAndLoadTxids(&data.changeAddresses[i], filter, bestheight, (page+1)*txsOnPage); err != nil { - return nil, err + for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} { + for i := range da { + if err = w.xpubCheckAndLoadTxids(&da[i], filter, bestheight, (page+1)*txsOnPage); err != nil { + return nil, err + } } } } @@ -317,41 +319,85 @@ func (w *Worker) GetAddressForXpub(xpub string, page int, txsOnPage int, option cachedXpubsMux.Lock() cachedXpubs[xpub] = data cachedXpubsMux.Unlock() - // TODO mempool - if option >= TxidHistory { - txc = make(xpubTxids, 0, 32) - var addTxids func(ad *xpubAddress) - if filter.FromHeight == 0 && filter.ToHeight == 0 && filter.Vout == AddressFilterVoutOff { - addTxids = func(ad *xpubAddress) { - txc = append(txc, ad.txids...) + // setup filtering of txids + var useTxids func(txid *xpubTxid, ad *xpubAddress) bool + var addTxids func(ad *xpubAddress) + if filter.FromHeight == 0 && filter.ToHeight == 0 && filter.Vout == AddressFilterVoutOff { + addTxids = func(ad *xpubAddress) { + txc = append(txc, ad.txids...) + } + totalResults = int(data.txs) + } else { + toHeight := maxUint32 + if filter.ToHeight != 0 { + toHeight = filter.ToHeight + } + useTxids = func(txid *xpubTxid, ad *xpubAddress) bool { + if txid.height < filter.FromHeight || txid.height > toHeight { + return false } - totalResults = int(data.txs) - } else { - toHeight := maxUint32 - if filter.ToHeight != 0 { - toHeight = filter.ToHeight + if filter.Vout != AddressFilterVoutOff { + if filter.Vout == AddressFilterVoutInputs && txid.inputOutput&txInput == 0 || + filter.Vout == AddressFilterVoutOutputs && txid.inputOutput&txOutput == 0 { + return false + } } - addTxids = func(ad *xpubAddress) { - for _, txid := range ad.txids { - if txid.height < filter.FromHeight || txid.height > toHeight { - continue - } - if filter.Vout != AddressFilterVoutOff { - if filter.Vout == AddressFilterVoutInputs && txid.inputOutput&txInput == 0 || - filter.Vout == AddressFilterVoutOutputs && txid.inputOutput&txOutput == 0 { - continue - } - } + return true + } + addTxids = func(ad *xpubAddress) { + for _, txid := range ad.txids { + if useTxids(&txid, ad) { txc = append(txc, txid) } } - totalResults = -1 } - for i := range data.addresses { - addTxids(&data.addresses[i]) + totalResults = -1 + } + // process mempool, only if blockheight filter is off + if filter.FromHeight == 0 && filter.ToHeight == 0 { + txmMap = make(map[string]*Tx) + for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} { + for i := range da { + ad := &da[i] + newTxids, _, err := w.xpubGetAddressTxids(ad.addrDesc, true, 0, 0, maxInt) + if err != nil { + return nil, err + } + for _, txid := range newTxids { + // the same tx can have multiple addresses from the same xpub, get it from backend it only once + tx, foundTx := txmMap[txid.txid] + if !foundTx { + tx, err = w.GetTransaction(txid.txid, false, false) + // mempool transaction may fail + if err != nil || tx == nil { + glog.Warning("GetTransaction in mempool: ", err) + continue + } + txmMap[txid.txid] = tx + } + // skip already confirmed txs, mempool may be out of sync + if tx.Confirmations == 0 { + uBalSat.Add(&uBalSat, tx.getAddrVoutValue(ad.addrDesc)) + uBalSat.Sub(&uBalSat, tx.getAddrVinValue(ad.addrDesc)) + if page == 0 && !foundTx && (useTxids == nil || useTxids(&txid, ad)) { + if option == TxidHistory { + txids = append(txids, tx.Txid) + } else if option >= TxHistoryLight { + txs = append(txs, tx) + } + } + } + + } + } } - for i := range data.changeAddresses { - addTxids(&data.changeAddresses[i]) + } + if option >= TxidHistory { + txc = make(xpubTxids, 0, 32) + for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} { + for i := range da { + addTxids(&da[i]) + } } sort.Stable(txc) var from, to int @@ -363,58 +409,37 @@ func (w *Worker) GetAddressForXpub(xpub string, page int, txsOnPage int, option pg, _, _, _ = computePaging(totalResults, page, txsOnPage) } } - if option == TxidHistory { - txids = make([]string, len(txm)+to-from) - } else { - txs = make([]*Tx, len(txm)+to-from) - } - txi := 0 // get confirmed transactions for i := from; i < to; i++ { xpubTxid := &txc[i] if option == TxidHistory { - txids[txi] = xpubTxid.txid + txids = append(txids, xpubTxid.txid) } else { - if txs[txi], err = w.txFromTxid(xpubTxid.txid, bestheight, option); err != nil { + tx, err := w.txFromTxid(xpubTxid.txid, bestheight, option) + if err != nil { return nil, err } + txs = append(txs, tx) } - txi++ - } - if option == TxidHistory { - txids = txids[:txi] - } else if option >= TxHistoryLight { - txs = txs[:txi] } } totalTokens := 0 xpubAddresses := make(map[string]struct{}) tokens := make([]Token, 0, 4) - for i := range data.addresses { - ad := &data.addresses[i] - if ad.balance != nil { - totalTokens++ - if filter.AllTokens || !IsZeroBigInt(&ad.balance.BalanceSat) { - t := w.tokenFromXpubAddress(data, ad, 0, i) - tokens = append(tokens, t) - xpubAddresses[t.Name] = struct{}{} - } else { - a, _, _ := w.chainParser.GetAddressesFromAddrDesc(ad.addrDesc) - if len(a) > 0 { - xpubAddresses[a[0]] = struct{}{} + for ci, da := range [][]xpubAddress{data.addresses, data.changeAddresses} { + for i := range da { + ad := &da[i] + var t *Token + if ad.balance != nil { + totalTokens++ + if filter.AllTokens || !IsZeroBigInt(&ad.balance.BalanceSat) { + token := w.tokenFromXpubAddress(data, ad, ci, i) + tokens = append(tokens, token) + t = &token + xpubAddresses[t.Name] = struct{}{} } } - } - } - for i := range data.changeAddresses { - ad := &data.changeAddresses[i] - if ad.balance != nil { - totalTokens++ - if filter.AllTokens || !IsZeroBigInt(&ad.balance.BalanceSat) { - t := w.tokenFromXpubAddress(data, ad, 1, i) - tokens = append(tokens, t) - xpubAddresses[t.Name] = struct{}{} - } else { + if t == nil { a, _, _ := w.chainParser.GetAddressesFromAddrDesc(ad.addrDesc) if len(a) > 0 { xpubAddresses[a[0]] = struct{}{} @@ -425,19 +450,19 @@ func (w *Worker) GetAddressForXpub(xpub string, page int, txsOnPage int, option var totalReceived big.Int totalReceived.Add(&data.balanceSat, &data.sentSat) addr := Address{ - Paging: pg, - AddrStr: xpub, - BalanceSat: (*Amount)(&data.balanceSat), - TotalReceivedSat: (*Amount)(&totalReceived), - TotalSentSat: (*Amount)(&data.sentSat), - Txs: int(data.txs), - // UnconfirmedBalanceSat: (*Amount)(&uBalSat), - // UnconfirmedTxs: len(txm), - Transactions: txs, - Txids: txids, - TotalTokens: totalTokens, - Tokens: tokens, - XPubAddresses: xpubAddresses, + Paging: pg, + AddrStr: xpub, + BalanceSat: (*Amount)(&data.balanceSat), + TotalReceivedSat: (*Amount)(&totalReceived), + TotalSentSat: (*Amount)(&data.sentSat), + Txs: int(data.txs), + UnconfirmedBalanceSat: (*Amount)(&uBalSat), + UnconfirmedTxs: len(txmMap), + Transactions: txs, + Txids: txids, + TotalTokens: totalTokens, + Tokens: tokens, + XPubAddresses: xpubAddresses, } glog.Info("GetAddressForXpub ", xpub[:16], ", ", len(data.addresses)+len(data.changeAddresses), " derived addresses, ", data.txs, " total txs, loaded ", len(txc), " txids, finished in ", time.Since(start)) return &addr, nil diff --git a/static/css/main.css b/static/css/main.css index e94e97a9..d54f9841 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -183,7 +183,7 @@ h3 { } .tx-own { - background-color: #fff6e6; + background-color: #fbf8f0; } .tx-amt {