Get utxo for xpub
parent
273436f109
commit
6b0a4960fd
29
api/types.go
29
api/types.go
|
@ -213,7 +213,10 @@ type AddressFilter struct {
|
|||
Contract string
|
||||
FromHeight uint32
|
||||
ToHeight uint32
|
||||
AllTokens bool
|
||||
// AllTokens set to true will include xpub addresses with zero balance
|
||||
AllTokens bool
|
||||
// OnlyConfirmed set to true will ignore mempool transactions; mempool is also ignored if FromHeight/ToHeight filter is specified
|
||||
OnlyConfirmed bool
|
||||
}
|
||||
|
||||
// Address holds information about address and its transactions
|
||||
|
@ -238,13 +241,33 @@ type Address struct {
|
|||
XPubAddresses map[string]struct{} `json:"-"`
|
||||
}
|
||||
|
||||
// AddressUtxo holds information about address and its transactions
|
||||
type AddressUtxo struct {
|
||||
// Utxo is one unspent transaction output
|
||||
type Utxo struct {
|
||||
Txid string `json:"txid"`
|
||||
Vout int32 `json:"vout"`
|
||||
AmountSat *Amount `json:"value"`
|
||||
Height int `json:"height,omitempty"`
|
||||
Confirmations int `json:"confirmations"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
}
|
||||
|
||||
// Utxos is array of Utxo
|
||||
type Utxos []Utxo
|
||||
|
||||
func (a Utxos) Len() int { return len(a) }
|
||||
func (a Utxos) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a Utxos) Less(i, j int) bool {
|
||||
// sort in reverse order, unconfirmed (height==0) utxos on top
|
||||
hi := a[i].Height
|
||||
hj := a[j].Height
|
||||
if hi == 0 {
|
||||
hi = maxInt
|
||||
}
|
||||
if hj == 0 {
|
||||
hj = maxInt
|
||||
}
|
||||
return hi >= hj
|
||||
}
|
||||
|
||||
// Blocks is list of blocks with paging information
|
||||
|
|
|
@ -192,7 +192,7 @@ func (w *Worker) AddressToV1(a *Address) *AddressV1 {
|
|||
}
|
||||
|
||||
// AddressUtxoToV1 converts []AddressUtxo to []AddressUtxoV1
|
||||
func (w *Worker) AddressUtxoToV1(au []AddressUtxo) []AddressUtxoV1 {
|
||||
func (w *Worker) AddressUtxoToV1(au Utxos) []AddressUtxoV1 {
|
||||
d := w.chainParser.AmountDecimals()
|
||||
v1 := make([]AddressUtxoV1, len(au))
|
||||
for i := range au {
|
||||
|
|
238
api/worker.go
238
api/worker.go
|
@ -684,7 +684,7 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
|
|||
page = 0
|
||||
}
|
||||
// process mempool, only if blockheight filter is off
|
||||
if filter.FromHeight == 0 && filter.ToHeight == 0 {
|
||||
if filter.FromHeight == 0 && filter.ToHeight == 0 && !filter.OnlyConfirmed {
|
||||
txm, err = w.getAddressTxids(addrDesc, true, filter, maxInt)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "getAddressTxids %v true", addrDesc)
|
||||
|
@ -766,126 +766,138 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
|
|||
return r, nil
|
||||
}
|
||||
|
||||
func (w *Worker) getAddrDescUtxo(addrDesc bchain.AddressDescriptor, ba *db.AddrBalance, onlyConfirmed bool, onlyMempool bool) (Utxos, error) {
|
||||
var err error
|
||||
r := make(Utxos, 0, 8)
|
||||
spentInMempool := make(map[string]struct{})
|
||||
if !onlyConfirmed {
|
||||
// get utxo from mempool
|
||||
txm, err := w.getAddressTxids(addrDesc, true, &AddressFilter{Vout: AddressFilterVoutOff}, maxInt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(txm) > 0 {
|
||||
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, Utxo{
|
||||
Txid: bchainTx.Txid,
|
||||
Vout: int32(i),
|
||||
AmountSat: (*Amount)(&vout.ValueSat),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !onlyMempool {
|
||||
// get utxo from index
|
||||
if ba == nil {
|
||||
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 && !IsZeroBigInt(&ba.BalanceSat) {
|
||||
outpoints := make([]bchain.Outpoint, 0, 8)
|
||||
err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, height uint32, indexes []int32) error {
|
||||
for _, index := range indexes {
|
||||
// take only outputs
|
||||
if index >= 0 {
|
||||
outpoints = append(outpoints, bchain.Outpoint{Txid: txid, Vout: index})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var lastTxid string
|
||||
var ta *db.TxAddresses
|
||||
var checksum big.Int
|
||||
checksum.Set(&ba.BalanceSat)
|
||||
b, _, err := w.db.GetBestBlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bestheight := int(b)
|
||||
for i := len(outpoints) - 1; i >= 0 && checksum.Int64() > 0; i-- {
|
||||
o := outpoints[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 {
|
||||
if !ta.Outputs[o.Vout].Spent {
|
||||
v := ta.Outputs[o.Vout].ValueSat
|
||||
// report only outpoints that are not spent in mempool
|
||||
_, e := spentInMempool[o.Txid+strconv.Itoa(int(o.Vout))]
|
||||
if !e {
|
||||
r = append(r, Utxo{
|
||||
Txid: o.Txid,
|
||||
Vout: o.Vout,
|
||||
AmountSat: (*Amount)(&v),
|
||||
Height: int(ta.Height),
|
||||
Confirmations: bestheight - int(ta.Height) + 1,
|
||||
})
|
||||
}
|
||||
checksum.Sub(&checksum, &v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if checksum.Uint64() != 0 {
|
||||
glog.Warning("DB inconsistency: ", addrDesc, ": checksum is not zero")
|
||||
}
|
||||
}
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// GetAddressUtxo returns unspent outputs for given address
|
||||
func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) ([]AddressUtxo, error) {
|
||||
func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) (Utxos, error) {
|
||||
if w.chainType != bchain.ChainBitcoinType {
|
||||
return nil, NewAPIError("Not supported", true)
|
||||
}
|
||||
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)
|
||||
if !onlyConfirmed {
|
||||
// get utxo from mempool
|
||||
txm, err := w.getAddressTxids(addrDesc, true, &AddressFilter{Vout: AddressFilterVoutOff}, maxInt)
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "getAddressTxids %v true", address)
|
||||
}
|
||||
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: int32(i),
|
||||
AmountSat: (*Amount)(&vout.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)
|
||||
}
|
||||
var checksum big.Int
|
||||
// ba can be nil if the address is only in mempool!
|
||||
if ba != nil && !IsZeroBigInt(&ba.BalanceSat) {
|
||||
outpoints := make([]bchain.Outpoint, 0, 8)
|
||||
err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, height uint32, indexes []int32) error {
|
||||
for _, index := range indexes {
|
||||
// take only outputs
|
||||
if index >= 0 {
|
||||
outpoints = append(outpoints, bchain.Outpoint{Txid: txid, Vout: index})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var lastTxid string
|
||||
var ta *db.TxAddresses
|
||||
checksum = ba.BalanceSat
|
||||
b, _, err := w.db.GetBestBlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bestheight := int(b)
|
||||
for i := len(outpoints) - 1; i >= 0 && checksum.Int64() > 0; i-- {
|
||||
o := outpoints[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 {
|
||||
if !ta.Outputs[o.Vout].Spent {
|
||||
v := ta.Outputs[o.Vout].ValueSat
|
||||
// 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: (*Amount)(&v),
|
||||
Height: int(ta.Height),
|
||||
Confirmations: bestheight - int(ta.Height) + 1,
|
||||
})
|
||||
}
|
||||
checksum.Sub(&checksum, &v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if checksum.Uint64() != 0 {
|
||||
glog.Warning("DB inconsistency: ", address, ": checksum is not zero")
|
||||
return nil, NewAPIError(fmt.Sprintf("Invalid address '%v', %v", address, err), true)
|
||||
}
|
||||
r, err := w.getAddrDescUtxo(addrDesc, nil, onlyConfirmed, false)
|
||||
glog.Info("GetAddressUtxo ", address, ", ", len(r), " utxos, finished in ", time.Since(start))
|
||||
return r, nil
|
||||
}
|
||||
|
|
146
api/xpub.go
146
api/xpub.go
|
@ -19,7 +19,7 @@ const defaultAddressesGap = 20
|
|||
const txInput = 1
|
||||
const txOutput = 2
|
||||
|
||||
var cachedXpubs = make(map[string]*xpubData)
|
||||
var cachedXpubs = make(map[string]xpubData)
|
||||
var cachedXpubsMux sync.Mutex
|
||||
|
||||
type xpubTxid struct {
|
||||
|
@ -216,63 +216,52 @@ func (w *Worker) tokenFromXpubAddress(data *xpubData, ad *xpubAddress, changeInd
|
|||
if len(a) > 0 {
|
||||
address = a[0]
|
||||
}
|
||||
var balance *big.Int
|
||||
var transfers int
|
||||
if ad.balance != nil {
|
||||
balance = &ad.balance.BalanceSat
|
||||
transfers = int(ad.balance.Txs)
|
||||
}
|
||||
return Token{
|
||||
Type: XPUBAddressTokenType,
|
||||
Name: address,
|
||||
Decimals: w.chainParser.AmountDecimals(),
|
||||
BalanceSat: (*Amount)(&ad.balance.BalanceSat),
|
||||
Transfers: int(ad.balance.Txs),
|
||||
BalanceSat: (*Amount)(balance),
|
||||
Transfers: transfers,
|
||||
Contract: fmt.Sprintf("%s/%d/%d", data.basePath, changeIndex, index),
|
||||
}
|
||||
}
|
||||
|
||||
// GetAddressForXpub computes address value and gets transactions for given address
|
||||
func (w *Worker) GetAddressForXpub(xpub string, page int, txsOnPage int, option GetAddressOption, filter *AddressFilter, gap int) (*Address, error) {
|
||||
func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option GetAddressOption, filter *AddressFilter, gap int) (*xpubData, uint32, error) {
|
||||
if w.chainType != bchain.ChainBitcoinType || len(xpub) != xpubLen {
|
||||
return nil, ErrUnsupportedXpub
|
||||
return nil, 0, ErrUnsupportedXpub
|
||||
}
|
||||
start := time.Now()
|
||||
var (
|
||||
err error
|
||||
bestheight uint32
|
||||
besthash string
|
||||
)
|
||||
if gap <= 0 {
|
||||
gap = defaultAddressesGap
|
||||
}
|
||||
// gap is increased one as there must be gap of empty addresses before the derivation is stopped
|
||||
gap++
|
||||
page--
|
||||
if page < 0 {
|
||||
page = 0
|
||||
}
|
||||
var processedHash string
|
||||
cachedXpubsMux.Lock()
|
||||
data, found := cachedXpubs[xpub]
|
||||
cachedXpubsMux.Unlock()
|
||||
type mempoolMap struct {
|
||||
tx *Tx
|
||||
inputOutput byte
|
||||
}
|
||||
var (
|
||||
txc xpubTxids
|
||||
txmMap map[string]*Tx
|
||||
txs []*Tx
|
||||
txids []string
|
||||
pg Paging
|
||||
totalResults int
|
||||
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 {
|
||||
bestheight, besthash, err = w.db.GetBestBlock()
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "GetBestBlock")
|
||||
return nil, 0, errors.Annotatef(err, "GetBestBlock")
|
||||
}
|
||||
if besthash == processedHash {
|
||||
break
|
||||
}
|
||||
fork := false
|
||||
if !found || data.gap != gap {
|
||||
data = &xpubData{gap: gap}
|
||||
data = xpubData{gap: gap}
|
||||
data.basePath, err = w.chainParser.DerivationBasePath(xpub)
|
||||
if err != nil {
|
||||
glog.Warning("DerivationBasePath error", err)
|
||||
|
@ -281,7 +270,7 @@ func (w *Worker) GetAddressForXpub(xpub string, page int, txsOnPage int, option
|
|||
} else {
|
||||
hash, err := w.db.GetBlockHash(data.dataHeight)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, 0, err
|
||||
}
|
||||
if hash != data.dataHash {
|
||||
// in case of for reset all cached data
|
||||
|
@ -296,21 +285,20 @@ func (w *Worker) GetAddressForXpub(xpub string, page int, txsOnPage int, option
|
|||
data.sentSat = *new(big.Int)
|
||||
data.txs = 0
|
||||
var lastUsedIndex int
|
||||
lastUsedIndex, data.addresses, err = w.xpubScanAddresses(xpub, data, data.addresses, gap, 0, 0, fork)
|
||||
lastUsedIndex, data.addresses, err = w.xpubScanAddresses(xpub, &data, data.addresses, gap, 0, 0, fork)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, 0, err
|
||||
}
|
||||
_, data.changeAddresses, err = w.xpubScanAddresses(xpub, data, data.changeAddresses, gap, 1, lastUsedIndex, fork)
|
||||
_, data.changeAddresses, err = w.xpubScanAddresses(xpub, &data, data.changeAddresses, gap, 1, lastUsedIndex, fork)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, 0, err
|
||||
}
|
||||
glog.Info("Scanned ", len(data.addresses)+len(data.changeAddresses), " addresses in ", time.Since(start))
|
||||
}
|
||||
if option >= TxidHistory {
|
||||
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
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -319,6 +307,34 @@ func (w *Worker) GetAddressForXpub(xpub string, page int, txsOnPage int, option
|
|||
cachedXpubsMux.Lock()
|
||||
cachedXpubs[xpub] = data
|
||||
cachedXpubsMux.Unlock()
|
||||
return &data, bestheight, nil
|
||||
}
|
||||
|
||||
// GetXpubAddress computes address value and gets transactions for given address
|
||||
func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option GetAddressOption, filter *AddressFilter, gap int) (*Address, error) {
|
||||
start := time.Now()
|
||||
page--
|
||||
if page < 0 {
|
||||
page = 0
|
||||
}
|
||||
type mempoolMap struct {
|
||||
tx *Tx
|
||||
inputOutput byte
|
||||
}
|
||||
var (
|
||||
txc xpubTxids
|
||||
txmMap map[string]*Tx
|
||||
txs []*Tx
|
||||
txids []string
|
||||
pg Paging
|
||||
totalResults int
|
||||
err error
|
||||
uBalSat big.Int
|
||||
)
|
||||
data, bestheight, err := w.getXpubData(xpub, page, txsOnPage, option, filter, gap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// setup filtering of txids
|
||||
var useTxids func(txid *xpubTxid, ad *xpubAddress) bool
|
||||
var addTxids func(ad *xpubAddress)
|
||||
|
@ -354,7 +370,7 @@ func (w *Worker) GetAddressForXpub(xpub string, page int, txsOnPage int, option
|
|||
totalResults = -1
|
||||
}
|
||||
// process mempool, only if blockheight filter is off
|
||||
if filter.FromHeight == 0 && filter.ToHeight == 0 {
|
||||
if filter.FromHeight == 0 && filter.ToHeight == 0 && !filter.OnlyConfirmed {
|
||||
txmMap = make(map[string]*Tx)
|
||||
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
||||
for i := range da {
|
||||
|
@ -429,22 +445,14 @@ func (w *Worker) GetAddressForXpub(xpub string, page int, txsOnPage int, option
|
|||
for ci, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
||||
for i := range da {
|
||||
ad := &da[i]
|
||||
var t *Token
|
||||
token := w.tokenFromXpubAddress(data, ad, ci, i)
|
||||
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{}{}
|
||||
}
|
||||
}
|
||||
if t == nil {
|
||||
a, _, _ := w.chainParser.GetAddressesFromAddrDesc(ad.addrDesc)
|
||||
if len(a) > 0 {
|
||||
xpubAddresses[a[0]] = struct{}{}
|
||||
}
|
||||
}
|
||||
xpubAddresses[token.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
var totalReceived big.Int
|
||||
|
@ -464,6 +472,48 @@ func (w *Worker) GetAddressForXpub(xpub string, page int, txsOnPage int, option
|
|||
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))
|
||||
glog.Info("GetXpubAddress ", 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
|
||||
}
|
||||
|
||||
// GetXpubUtxo returns unspent outputs for given xpub
|
||||
func (w *Worker) GetXpubUtxo(xpub string, onlyConfirmed bool, gap int) (Utxos, error) {
|
||||
start := time.Now()
|
||||
data, _, err := w.getXpubData(xpub, 0, 1, Basic, &AddressFilter{
|
||||
Vout: AddressFilterVoutOff,
|
||||
AllTokens: false,
|
||||
OnlyConfirmed: onlyConfirmed,
|
||||
}, gap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := make(Utxos, 0, 8)
|
||||
for ci, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
||||
for i := range da {
|
||||
ad := &da[i]
|
||||
onlyMempool := false
|
||||
if ad.balance == nil {
|
||||
if onlyConfirmed {
|
||||
continue
|
||||
}
|
||||
onlyMempool = true
|
||||
}
|
||||
utxos, err := w.getAddrDescUtxo(ad.addrDesc, ad.balance, onlyConfirmed, onlyMempool)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(utxos) > 0 {
|
||||
t := w.tokenFromXpubAddress(data, ad, ci, i)
|
||||
for j := range utxos {
|
||||
a := &utxos[j]
|
||||
a.Address = t.Name
|
||||
a.Path = t.Contract
|
||||
}
|
||||
r = append(r, utxos...)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Stable(r)
|
||||
glog.Info("GetXpubUtxo ", xpub[:16], ", ", len(r), " utxos, finished in ", time.Since(start))
|
||||
return r, nil
|
||||
}
|
||||
|
|
|
@ -157,7 +157,7 @@ func (s *PublicServer) ConnectFullPublicInterface() {
|
|||
serveMux.HandleFunc(path+"api/v1/tx-specific/", s.jsonHandler(s.apiTxSpecific, apiV1))
|
||||
serveMux.HandleFunc(path+"api/v1/tx/", s.jsonHandler(s.apiTx, apiV1))
|
||||
serveMux.HandleFunc(path+"api/v1/address/", s.jsonHandler(s.apiAddress, apiV1))
|
||||
serveMux.HandleFunc(path+"api/v1/utxo/", s.jsonHandler(s.apiAddressUtxo, apiV1))
|
||||
serveMux.HandleFunc(path+"api/v1/utxo/", s.jsonHandler(s.apiUtxo, apiV1))
|
||||
serveMux.HandleFunc(path+"api/v1/block/", s.jsonHandler(s.apiBlock, apiV1))
|
||||
serveMux.HandleFunc(path+"api/v1/sendtx/", s.jsonHandler(s.apiSendTx, apiV1))
|
||||
serveMux.HandleFunc(path+"api/v1/estimatefee/", s.jsonHandler(s.apiEstimateFee, apiV1))
|
||||
|
@ -167,7 +167,7 @@ func (s *PublicServer) ConnectFullPublicInterface() {
|
|||
serveMux.HandleFunc(path+"api/tx/", s.jsonHandler(s.apiTx, apiDefault))
|
||||
serveMux.HandleFunc(path+"api/address/", s.jsonHandler(s.apiAddress, apiDefault))
|
||||
serveMux.HandleFunc(path+"api/xpub/", s.jsonHandler(s.apiXpub, apiDefault))
|
||||
serveMux.HandleFunc(path+"api/utxo/", s.jsonHandler(s.apiAddressUtxo, apiDefault))
|
||||
serveMux.HandleFunc(path+"api/utxo/", s.jsonHandler(s.apiUtxo, apiDefault))
|
||||
serveMux.HandleFunc(path+"api/block/", s.jsonHandler(s.apiBlock, apiDefault))
|
||||
serveMux.HandleFunc(path+"api/sendtx/", s.jsonHandler(s.apiSendTx, apiDefault))
|
||||
serveMux.HandleFunc(path+"api/estimatefee/", s.jsonHandler(s.apiEstimateFee, apiDefault))
|
||||
|
@ -177,7 +177,7 @@ func (s *PublicServer) ConnectFullPublicInterface() {
|
|||
serveMux.HandleFunc(path+"api/v2/tx/", s.jsonHandler(s.apiTx, apiV2))
|
||||
serveMux.HandleFunc(path+"api/v2/address/", s.jsonHandler(s.apiAddress, apiV2))
|
||||
serveMux.HandleFunc(path+"api/v2/xpub/", s.jsonHandler(s.apiXpub, apiV2))
|
||||
serveMux.HandleFunc(path+"api/v2/utxo/", s.jsonHandler(s.apiAddressUtxo, apiV2))
|
||||
serveMux.HandleFunc(path+"api/v2/utxo/", s.jsonHandler(s.apiUtxo, apiV2))
|
||||
serveMux.HandleFunc(path+"api/v2/block/", s.jsonHandler(s.apiBlock, apiV2))
|
||||
serveMux.HandleFunc(path+"api/v2/sendtx/", s.jsonHandler(s.apiSendTx, apiV2))
|
||||
serveMux.HandleFunc(path+"api/v2/estimatefee/", s.jsonHandler(s.apiEstimateFee, apiV2))
|
||||
|
@ -625,7 +625,7 @@ func (s *PublicServer) getAddressForXpub(r *http.Request, xpub string, pageSize
|
|||
gap = 0
|
||||
}
|
||||
allAddresses, _ := strconv.ParseBool(r.URL.Query().Get("alladdresses"))
|
||||
return s.api.GetAddressForXpub(xpub, page, pageSize, option, &api.AddressFilter{Vout: fn, AllTokens: allAddresses}, gap)
|
||||
return s.api.GetXpubAddress(xpub, page, pageSize, option, &api.AddressFilter{Vout: fn, AllTokens: allAddresses}, gap)
|
||||
}
|
||||
|
||||
func (s *PublicServer) explorerXpub(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
|
||||
|
@ -714,7 +714,7 @@ func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (t
|
|||
var err error
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "search"}).Inc()
|
||||
if len(q) > 0 {
|
||||
address, err = s.api.GetAddressForXpub(q, 0, 1, api.Basic, &api.AddressFilter{Vout: api.AddressFilterVoutOff}, 0)
|
||||
address, err = s.api.GetXpubAddress(q, 0, 1, api.Basic, &api.AddressFilter{Vout: api.AddressFilterVoutOff}, 0)
|
||||
if err == nil {
|
||||
http.Redirect(w, r, joinURL("/xpub/", address.AddrStr), 302)
|
||||
return noTpl, nil, nil
|
||||
|
@ -912,10 +912,9 @@ func (s *PublicServer) apiXpub(r *http.Request, apiVersion int) (interface{}, er
|
|||
return address, err
|
||||
}
|
||||
|
||||
func (s *PublicServer) apiAddressUtxo(r *http.Request, apiVersion int) (interface{}, error) {
|
||||
var utxo []api.AddressUtxo
|
||||
func (s *PublicServer) apiUtxo(r *http.Request, apiVersion int) (interface{}, error) {
|
||||
var utxo []api.Utxo
|
||||
var err error
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-address"}).Inc()
|
||||
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||
onlyConfirmed := false
|
||||
c := r.URL.Query().Get("confirmed")
|
||||
|
@ -925,7 +924,17 @@ func (s *PublicServer) apiAddressUtxo(r *http.Request, apiVersion int) (interfac
|
|||
return nil, api.NewAPIError("Parameter 'confirmed' cannot be converted to boolean", true)
|
||||
}
|
||||
}
|
||||
utxo, err = s.api.GetAddressUtxo(r.URL.Path[i+1:], onlyConfirmed)
|
||||
gap, ec := strconv.Atoi(r.URL.Query().Get("gap"))
|
||||
if ec != nil {
|
||||
gap = 0
|
||||
}
|
||||
utxo, err = s.api.GetXpubUtxo(r.URL.Path[i+1:], onlyConfirmed, gap)
|
||||
if err == nil {
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-xpub-utxo"}).Inc()
|
||||
} else {
|
||||
utxo, err = s.api.GetAddressUtxo(r.URL.Path[i+1:], onlyConfirmed)
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-address-utxo"}).Inc()
|
||||
}
|
||||
if err == nil && apiVersion == apiV1 {
|
||||
return s.api.AddressUtxoToV1(utxo), nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue