@@ -22,6 +22,8 @@ const txInput = 1
2222const txOutput = 2
2323
2424const xpubCacheExpirationSeconds = 3600
25+ const xpubCacheMaxEntries = 128
26+ const maxXpubAddressDerivations = (maxAddressesGap + 1 ) * 2
2527
2628var cachedXpubs map [string ]xpubData
2729var cachedXpubsMux sync.Mutex
@@ -85,18 +87,62 @@ func (w *Worker) initXpubCache() {
8587}
8688
8789func (w * Worker ) evictXpubCacheItems () {
90+ now := time .Now ().Unix ()
8891 cachedXpubsMux .Lock ()
89- defer cachedXpubsMux .Unlock ()
90- threshold := time .Now ().Unix () - xpubCacheExpirationSeconds
92+ count := evictXpubCacheItemsLocked (now )
93+ cacheSize := len (cachedXpubs )
94+ cachedXpubsMux .Unlock ()
95+
96+ w .metrics .XPubCacheSize .Set (float64 (cacheSize ))
97+ glog .Info ("Evicted " , count , " items from xpub cache, cache size " , cacheSize )
98+ }
99+
100+ func evictXpubCacheItemsLocked (now int64 ) int {
101+ threshold := now - xpubCacheExpirationSeconds
91102 count := 0
92103 for k , v := range cachedXpubs {
93104 if v .accessed < threshold {
94105 delete (cachedXpubs , k )
95106 count ++
96107 }
97108 }
98- w .metrics .XPubCacheSize .Set (float64 (len (cachedXpubs )))
99- glog .Info ("Evicted " , count , " items from xpub cache, cache size " , len (cachedXpubs ))
109+ return count + trimXpubCacheItemsLocked ()
110+ }
111+
112+ func trimXpubCacheItemsLocked () int {
113+ if len (cachedXpubs ) <= xpubCacheMaxEntries {
114+ return 0
115+ }
116+ type cacheEntry struct {
117+ key string
118+ accessed int64
119+ }
120+ entries := make ([]cacheEntry , 0 , len (cachedXpubs ))
121+ for k , v := range cachedXpubs {
122+ entries = append (entries , cacheEntry {key : k , accessed : v .accessed })
123+ }
124+ sort .Slice (entries , func (i , j int ) bool {
125+ if entries [i ].accessed == entries [j ].accessed {
126+ return entries [i ].key < entries [j ].key
127+ }
128+ return entries [i ].accessed < entries [j ].accessed
129+ })
130+ count := len (cachedXpubs ) - xpubCacheMaxEntries
131+ for i := 0 ; i < count ; i ++ {
132+ delete (cachedXpubs , entries [i ].key )
133+ }
134+ return count
135+ }
136+
137+ func validateXpubScanLimits (xd * bchain.XpubDescriptor , gap int ) error {
138+ if len (xd .ChangeIndexes ) > bchain .MaxXpubChangeIndexes {
139+ return errors .Errorf ("Xpub descriptor change index count %d exceeds limit %d" , len (xd .ChangeIndexes ), bchain .MaxXpubChangeIndexes )
140+ }
141+ derivations := len (xd .ChangeIndexes ) * gap
142+ if derivations > maxXpubAddressDerivations {
143+ return errors .Errorf ("Xpub descriptor scan size %d exceeds limit %d" , derivations , maxXpubAddressDerivations )
144+ }
145+ return nil
100146}
101147
102148func (w * Worker ) xpubGetAddressTxids (addrDesc bchain.AddressDescriptor , mempool bool , fromHeight , toHeight uint32 , maxResults int ) ([]xpubTxid , bool , error ) {
@@ -203,7 +249,10 @@ func (w *Worker) xpubDerivedAddressBalance(data *xpubData, ad *xpubAddress) (boo
203249 return false , nil
204250}
205251
206- func (w * Worker ) xpubScanAddresses (xd * bchain.XpubDescriptor , data * xpubData , addresses []xpubAddress , gap int , change uint32 , minDerivedIndex int , fork bool ) (int , []xpubAddress , error ) {
252+ func (w * Worker ) xpubScanAddresses (xd * bchain.XpubDescriptor , data * xpubData , addresses []xpubAddress , gap int , change uint32 , minDerivedIndex int , fork bool , derivedBefore int ) (int , []xpubAddress , error ) {
253+ if total := derivedBefore + len (addresses ); total > maxXpubAddressDerivations {
254+ return 0 , nil , errors .Errorf ("Xpub descriptor scan size %d exceeds limit %d" , total , maxXpubAddressDerivations )
255+ }
207256 // rescan known addresses
208257 lastUsed := 0
209258 for i := range addresses {
@@ -231,6 +280,9 @@ func (w *Worker) xpubScanAddresses(xd *bchain.XpubDescriptor, data *xpubData, ad
231280 if to < minDerivedIndex {
232281 to = minDerivedIndex
233282 }
283+ if total := derivedBefore + to ; total > maxXpubAddressDerivations {
284+ return 0 , nil , errors .Errorf ("Xpub descriptor scan size %d exceeds limit %d" , total , maxXpubAddressDerivations )
285+ }
234286 descriptors , err := w .chainParser .DeriveAddressDescriptorsFromTo (xd , change , uint32 (from ), uint32 (to ))
235287 if err != nil {
236288 return 0 , nil , err
@@ -325,6 +377,9 @@ func (w *Worker) getXpubData(xd *bchain.XpubDescriptor, page int, txsOnPage int,
325377 }
326378 // gap is increased one as there must be gap of empty addresses before the derivation is stopped
327379 gap ++
380+ if err := validateXpubScanLimits (xd , gap ); err != nil {
381+ return nil , 0 , false , err
382+ }
328383 var processedHash string
329384 cachedXpubsMux .Lock ()
330385 data , inCache := cachedXpubs [xd .XpubDescriptor ]
@@ -366,11 +421,13 @@ func (w *Worker) getXpubData(xd *bchain.XpubDescriptor, page int, txsOnPage int,
366421 data .sentSat = * new (big.Int )
367422 data .txCountEstimate = 0
368423 var minDerivedIndex int
424+ totalDerived := 0
369425 for i , change := range xd .ChangeIndexes {
370- minDerivedIndex , data .addresses [i ], err = w .xpubScanAddresses (xd , & data , data .addresses [i ], gap , change , minDerivedIndex , fork )
426+ minDerivedIndex , data .addresses [i ], err = w .xpubScanAddresses (xd , & data , data .addresses [i ], gap , change , minDerivedIndex , fork , totalDerived )
371427 if err != nil {
372428 return nil , 0 , inCache , err
373429 }
430+ totalDerived += len (data .addresses [i ])
374431 }
375432 }
376433 if option >= AccountDetailsTxidHistory {
@@ -385,8 +442,14 @@ func (w *Worker) getXpubData(xd *bchain.XpubDescriptor, page int, txsOnPage int,
385442 }
386443 data .accessed = time .Now ().Unix ()
387444 cachedXpubsMux .Lock ()
445+ if cachedXpubs == nil {
446+ cachedXpubs = make (map [string ]xpubData )
447+ }
388448 cachedXpubs [xd .XpubDescriptor ] = data
449+ trimXpubCacheItemsLocked ()
450+ cacheSize := len (cachedXpubs )
389451 cachedXpubsMux .Unlock ()
452+ w .metrics .XPubCacheSize .Set (float64 (cacheSize ))
390453 return & data , bestheight , inCache , nil
391454}
392455
0 commit comments