Skip to content

Commit

Permalink
Parse xpub descriptors
Browse files Browse the repository at this point in the history
  • Loading branch information
martinboehm committed Nov 9, 2021
1 parent c4128e5 commit e500d68
Show file tree
Hide file tree
Showing 11 changed files with 500 additions and 126 deletions.
70 changes: 41 additions & 29 deletions api/xpub.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type xpubAddress struct {
}

type xpubData struct {
descriptor *bchain.XpubDescriptor
gap int
accessed int64
basePath string
Expand All @@ -64,8 +65,7 @@ type xpubData struct {
txCountEstimate uint32
sentSat big.Int
balanceSat big.Int
addresses []xpubAddress
changeAddresses []xpubAddress
addresses [][]xpubAddress
}

func (w *Worker) initXpubCache() {
Expand Down Expand Up @@ -201,7 +201,7 @@ func (w *Worker) xpubDerivedAddressBalance(data *xpubData, ad *xpubAddress) (boo
return false, nil
}

func (w *Worker) xpubScanAddresses(xpub string, data *xpubData, addresses []xpubAddress, gap int, change int, minDerivedIndex int, fork bool) (int, []xpubAddress, error) {
func (w *Worker) xpubScanAddresses(xd *bchain.XpubDescriptor, data *xpubData, addresses []xpubAddress, gap int, change uint32, minDerivedIndex int, fork bool) (int, []xpubAddress, error) {
// rescan known addresses
lastUsed := 0
for i := range addresses {
Expand Down Expand Up @@ -229,7 +229,7 @@ func (w *Worker) xpubScanAddresses(xpub string, data *xpubData, addresses []xpub
if to < minDerivedIndex {
to = minDerivedIndex
}
descriptors, err := w.chainParser.DeriveAddressDescriptorsFromTo(xpub, uint32(change), uint32(from), uint32(to))
descriptors, err := w.chainParser.DeriveAddressDescriptorsFromTo(xd, change, uint32(from), uint32(to))
if err != nil {
return 0, nil, err
}
Expand Down Expand Up @@ -277,7 +277,7 @@ func (w *Worker) tokenFromXpubAddress(data *xpubData, ad *xpubAddress, changeInd
}
}

func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option AccountDetails, filter *AddressFilter, gap int) (*xpubData, uint32, bool, error) {
func (w *Worker) getXpubData(xd *bchain.XpubDescriptor, page int, txsOnPage int, option AccountDetails, filter *AddressFilter, gap int) (*xpubData, uint32, bool, error) {
if w.chainType != bchain.ChainBitcoinType {
return nil, 0, false, ErrUnsupportedXpub
}
Expand All @@ -296,7 +296,7 @@ func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option Accoun
gap++
var processedHash string
cachedXpubsMux.Lock()
data, inCache := cachedXpubs[xpub]
data, inCache := cachedXpubs[xd.XpubDescriptor]
cachedXpubsMux.Unlock()
// to load all data for xpub may take some time, do it in a loop to process a possible new block
for {
Expand All @@ -309,8 +309,11 @@ func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option Accoun
}
fork := false
if !inCache || data.gap != gap {
data = xpubData{gap: gap}
data.basePath, err = w.chainParser.DerivationBasePath(xpub)
data = xpubData{
gap: gap,
addresses: make([][]xpubAddress, len(xd.ChangeIndexes)),
}
data.basePath, err = w.chainParser.DerivationBasePath(xd)
if err != nil {
return nil, 0, inCache, err
}
Expand All @@ -331,18 +334,16 @@ func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option Accoun
data.balanceSat = *new(big.Int)
data.sentSat = *new(big.Int)
data.txCountEstimate = 0
var lastUsedIndex int
lastUsedIndex, data.addresses, err = w.xpubScanAddresses(xpub, &data, data.addresses, gap, 0, 0, fork)
if err != nil {
return nil, 0, inCache, err
}
_, data.changeAddresses, err = w.xpubScanAddresses(xpub, &data, data.changeAddresses, gap, 1, lastUsedIndex, fork)
if err != nil {
return nil, 0, inCache, err
var minDerivedIndex int
for i, change := range xd.ChangeIndexes {
minDerivedIndex, data.addresses[i], err = w.xpubScanAddresses(xd, &data, data.addresses[i], gap, change, minDerivedIndex, fork)
if err != nil {
return nil, 0, inCache, err
}
}
}
if option >= AccountDetailsTxidHistory {
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
for _, da := range data.addresses {
for i := range da {
if err = w.xpubCheckAndLoadTxids(&da[i], filter, bestheight, (page+1)*txsOnPage); err != nil {
return nil, 0, inCache, err
Expand All @@ -353,7 +354,7 @@ func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option Accoun
}
data.accessed = time.Now().Unix()
cachedXpubsMux.Lock()
cachedXpubs[xpub] = data
cachedXpubs[xd.XpubDescriptor] = data
cachedXpubsMux.Unlock()
return &data, bestheight, inCache, nil
}
Expand All @@ -377,11 +378,14 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
txids []string
pg Paging
filtered bool
err error
uBalSat big.Int
unconfirmedTxs int
)
data, bestheight, inCache, err := w.getXpubData(xpub, page, txsOnPage, option, filter, gap)
xd, err := w.chainParser.ParseXpub(xpub)
if err != nil {
return nil, err
}
data, bestheight, inCache, err := w.getXpubData(xd, page, txsOnPage, option, filter, gap)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -410,7 +414,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
if filter.ToHeight == 0 && !filter.OnlyConfirmed {
txmMap = make(map[string]*Tx)
mempoolEntries := make(bchain.MempoolTxidEntries, 0)
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
for _, da := range data.addresses {
for i := range da {
ad := &da[i]
newTxids, _, err := w.xpubGetAddressTxids(ad.addrDesc, true, 0, 0, maxInt)
Expand Down Expand Up @@ -457,7 +461,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
if option >= AccountDetailsTxidHistory {
txcMap := make(map[string]bool)
txc = make(xpubTxids, 0, 32)
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
for _, da := range data.addresses {
for i := range da {
ad := &da[i]
for _, txid := range ad.txids {
Expand Down Expand Up @@ -515,7 +519,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
tokens = make([]Token, 0, 4)
xpubAddresses = make(map[string]struct{})
}
for ci, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
for ci, da := range data.addresses {
for i := range da {
ad := &da[i]
if ad.balance != nil {
Expand Down Expand Up @@ -549,22 +553,26 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
Tokens: tokens,
XPubAddresses: xpubAddresses,
}
glog.Info("GetXpubAddress ", xpub[:xpubLogPrefix], ", cache ", inCache, ", ", len(data.addresses)+len(data.changeAddresses), " addresses, ", txCount, " txs, ", time.Since(start))
glog.Info("GetXpubAddress ", xpub[:xpubLogPrefix], ", cache ", inCache, ", ", txCount, " txs, ", 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, _, inCache, err := w.getXpubData(xpub, 0, 1, AccountDetailsBasic, &AddressFilter{
xd, err := w.chainParser.ParseXpub(xpub)
if err != nil {
return nil, err
}
data, _, inCache, err := w.getXpubData(xd, 0, 1, AccountDetailsBasic, &AddressFilter{
Vout: AddressFilterVoutOff,
OnlyConfirmed: onlyConfirmed,
}, gap)
if err != nil {
return nil, err
}
r := make(Utxos, 0, 8)
for ci, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
for ci, da := range data.addresses {
for i := range da {
ad := &da[i]
onlyMempool := false
Expand Down Expand Up @@ -602,7 +610,11 @@ func (w *Worker) GetXpubBalanceHistory(xpub string, fromTimestamp, toTimestamp i
if fromHeight >= toHeight {
return bhs, nil
}
data, _, inCache, err := w.getXpubData(xpub, 0, 1, AccountDetailsTxidHistory, &AddressFilter{
xd, err := w.chainParser.ParseXpub(xpub)
if err != nil {
return nil, err
}
data, _, inCache, err := w.getXpubData(xd, 0, 1, AccountDetailsTxidHistory, &AddressFilter{
Vout: AddressFilterVoutOff,
OnlyConfirmed: true,
FromHeight: fromHeight,
Expand All @@ -612,12 +624,12 @@ func (w *Worker) GetXpubBalanceHistory(xpub string, fromTimestamp, toTimestamp i
return nil, err
}
selfAddrDesc := make(map[string]struct{})
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
for _, da := range data.addresses {
for i := range da {
selfAddrDesc[string(da[i].addrDesc)] = struct{}{}
}
}
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
for _, da := range data.addresses {
for i := range da {
ad := &da[i]
txids := ad.txids
Expand Down
11 changes: 8 additions & 3 deletions bchain/baseparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,18 +280,23 @@ func (p *BaseParser) IsAddrDescIndexable(addrDesc AddressDescriptor) bool {
return true
}

// ParseXpub is unsupported
func (p *BaseParser) ParseXpub(xpub string) (*XpubDescriptor, error) {
return nil, errors.New("Not supported")
}

// DerivationBasePath is unsupported
func (p *BaseParser) DerivationBasePath(xpub string) (string, error) {
func (p *BaseParser) DerivationBasePath(descriptor *XpubDescriptor) (string, error) {
return "", errors.New("Not supported")
}

// DeriveAddressDescriptors is unsupported
func (p *BaseParser) DeriveAddressDescriptors(xpub string, change uint32, indexes []uint32) ([]AddressDescriptor, error) {
func (p *BaseParser) DeriveAddressDescriptors(descriptor *XpubDescriptor, change uint32, indexes []uint32) ([]AddressDescriptor, error) {
return nil, errors.New("Not supported")
}

// DeriveAddressDescriptorsFromTo is unsupported
func (p *BaseParser) DeriveAddressDescriptorsFromTo(xpub string, change uint32, fromIndex uint32, toIndex uint32) ([]AddressDescriptor, error) {
func (p *BaseParser) DeriveAddressDescriptorsFromTo(descriptor *XpubDescriptor, change uint32, fromIndex uint32, toIndex uint32) ([]AddressDescriptor, error) {
return nil, errors.New("Not supported")
}

Expand Down
Loading

0 comments on commit e500d68

Please sign in to comment.