@@ -19,7 +19,7 @@ import (
1919 "github.com/hyperledger-labs/fabric-token-sdk/token/driver"
2020 dbdriver "github.com/hyperledger-labs/fabric-token-sdk/token/services/storage/db/driver"
2121 "github.com/hyperledger-labs/fabric-token-sdk/token/services/storage/tokendb"
22- "github.com/hyperledger-labs/fabric-token-sdk/token/services/utils/cache"
22+ utilcache "github.com/hyperledger-labs/fabric-token-sdk/token/services/utils/cache"
2323 token2 "github.com/hyperledger-labs/fabric-token-sdk/token/token"
2424)
2525
@@ -38,7 +38,7 @@ const (
3838 Cached FetcherStrategy = "cached"
3939)
4040
41- type fetchFunc func (db * tokendb.StoreService , m * Metrics , cacheSize int64 , freshnessInterval time.Duration , maxQueries int ) TokenFetcher
41+ type fetchFunc func (db * tokendb.StoreService , m * Metrics , cacheSize int64 , freshnessInterval time.Duration , maxQueries int , notifierDisabled bool ) TokenFetcher
4242
4343type fetcherProvider struct {
4444 tokenStoreServiceManager tokendb.StoreServiceManager
@@ -47,12 +47,13 @@ type fetcherProvider struct {
4747 cacheSize int64
4848 freshnessInterval time.Duration
4949 maxQueries int
50+ notifierDisabled bool
5051}
5152
5253var fetchers = map [FetcherStrategy ]fetchFunc {
53- Mixed : func (db * tokendb.StoreService , m * Metrics , cacheSize int64 , freshnessInterval time.Duration , maxQueries int ) TokenFetcher {
54+ Mixed : func (db * tokendb.StoreService , m * Metrics , cacheSize int64 , freshnessInterval time.Duration , maxQueries int , notifierDisabled bool ) TokenFetcher {
5455 var notifier dbdriver.TokenNotifier
55- if db != nil && db .TokenStore != nil {
56+ if ! notifierDisabled && db != nil && db .TokenStore != nil {
5657 var err error
5758 notifier , err = db .Notifier ()
5859 if err != nil {
@@ -65,7 +66,7 @@ var fetchers = map[FetcherStrategy]fetchFunc{
6566}
6667
6768// NewFetcherProvider creates a new fetcher provider with the specified strategy and configuration.
68- func NewFetcherProvider (storeServiceManager tokendb.StoreServiceManager , metricsProvider metrics.Provider , strategy FetcherStrategy , cacheSize int64 , freshnessInterval time.Duration , maxQueries int ) * fetcherProvider {
69+ func NewFetcherProvider (storeServiceManager tokendb.StoreServiceManager , metricsProvider metrics.Provider , strategy FetcherStrategy , cacheSize int64 , freshnessInterval time.Duration , maxQueries int , notifierDisabled bool ) * fetcherProvider {
6970 fetcher , ok := fetchers [strategy ]
7071 if ! ok {
7172 panic ("undefined fetcher strategy: " + strategy )
@@ -78,6 +79,7 @@ func NewFetcherProvider(storeServiceManager tokendb.StoreServiceManager, metrics
7879 cacheSize : cacheSize ,
7980 freshnessInterval : freshnessInterval ,
8081 maxQueries : maxQueries ,
82+ notifierDisabled : notifierDisabled ,
8183 }
8284}
8385
@@ -88,7 +90,7 @@ func (p *fetcherProvider) GetFetcher(tmsID token.TMSID) (TokenFetcher, error) {
8890 return nil , err
8991 }
9092
91- return p .fetch (tokenDB , p .metrics , p .cacheSize , p .freshnessInterval , p .maxQueries ), nil
93+ return p .fetch (tokenDB , p .metrics , p .cacheSize , p .freshnessInterval , p .maxQueries , p . notifierDisabled ), nil
9294}
9395
9496// mixedFetcher combines both eager and lazy strategies
@@ -149,14 +151,9 @@ func (f *lazyFetcher) UnspentTokensIteratorBy(ctx context.Context, walletID stri
149151 return collections.NewPermutatedIterator [token2.UnspentTokenInWallet ](it )
150152}
151153
152- type permutatableIterator [T any ] interface {
153- iterators.Iterator [T ]
154- NewPermutation () iterators.Iterator [T ]
155- }
156-
157154type tokenCache interface {
158- Get (key string ) (permutatableIterator [ * token2.UnspentTokenInWallet ] , bool )
159- Add (key string , value permutatableIterator [ * token2.UnspentTokenInWallet ] )
155+ Get (key string ) ([] * token2.UnspentTokenInWallet , bool )
156+ Add (key string , value [] * token2.UnspentTokenInWallet )
160157 Delete (key string )
161158 Clear ()
162159}
@@ -198,9 +195,9 @@ func NewCachedFetcher(tokenDB TokenDB, notifier dbdriver.TokenNotifier, cacheSiz
198195 // If cacheSize <= 0, use default size; otherwise use custom size
199196 // Both use the same default NumCounters and BufferItems
200197 if cacheSize <= 0 {
201- ristrettoCache , err = cache .NewDefaultRistrettoCache [permutatableIterator [ * token2.UnspentTokenInWallet ] ]()
198+ ristrettoCache , err = utilcache .NewDefaultRistrettoCache [[] * token2.UnspentTokenInWallet ]()
202199 } else {
203- ristrettoCache , err = cache .NewRistrettoCacheWithSize [permutatableIterator [ * token2.UnspentTokenInWallet ] ](cacheSize )
200+ ristrettoCache , err = utilcache .NewRistrettoCacheWithSize [[] * token2.UnspentTokenInWallet ](cacheSize )
204201 }
205202
206203 if err != nil {
@@ -225,9 +222,54 @@ func NewCachedFetcher(tokenDB TokenDB, notifier dbdriver.TokenNotifier, cacheSiz
225222}
226223
227224// onTokenChange is the callback registered with the token DB notifier.
228- // Any write to the token table marks the cache dirty so the next query forces a refresh.
229- func (f * cachedFetcher ) onTokenChange (_ dbdriver.Operation , _ dbdriver.TokenRecordReference ) {
230- f .dirty .Store (1 )
225+ // When the reference carries wallet/type data it surgically patches the cache
226+ // bucket so the next query sees the change without a full DB scan.
227+ // If data is missing (empty WalletID or Type) we fall back to marking the
228+ // whole cache dirty so the next query triggers a full refresh.
229+ func (f * cachedFetcher ) onTokenChange (op dbdriver.Operation , ref dbdriver.TokenRecordReference ) {
230+ if ref .WalletID == "" || ref .Type == "" {
231+ f .dirty .Store (1 )
232+ return
233+ }
234+ key := tokenKey (ref .WalletID , ref .Type )
235+
236+ f .mu .Lock ()
237+ defer f .mu .Unlock ()
238+
239+ switch op {
240+ case dbdriver .Insert :
241+ toks , _ := f .cache .Get (key )
242+ newTok := & token2.UnspentTokenInWallet {
243+ Id : token2.ID {TxId : ref .TxID , Index : ref .Index },
244+ WalletID : ref .WalletID ,
245+ Type : ref .Type ,
246+ Quantity : ref .Quantity ,
247+ }
248+ f .cache .Add (key , append (toks , newTok ))
249+ f .prevKeys [key ] = struct {}{}
250+
251+ case dbdriver .Delete :
252+ toks , ok := f .cache .Get (key )
253+ if ! ok {
254+ return
255+ }
256+ updated := toks [:0 :0 ]
257+ for _ , t := range toks {
258+ if t .Id .TxId != ref .TxID || t .Id .Index != ref .Index {
259+ updated = append (updated , t )
260+ }
261+ }
262+ if len (updated ) == 0 {
263+ f .cache .Delete (key )
264+ delete (f .prevKeys , key )
265+ } else {
266+ f .cache .Add (key , updated )
267+ }
268+
269+ default :
270+ // For updates (e.g. spendable flag toggles) fall back to a full refresh.
271+ f .dirty .Store (1 )
272+ }
231273}
232274
233275func (f * cachedFetcher ) update (ctx context.Context ) {
@@ -278,7 +320,7 @@ func (f *cachedFetcher) updateCache(ctx context.Context, tokensByKey map[string]
278320 // Step 1: Add/update new entries first
279321 newKeys := make (map [string ]struct {}, len (tokensByKey ))
280322 for key , toks := range tokensByKey {
281- f .cache .Add (key , iterators . Slice ( toks ) )
323+ f .cache .Add (key , toks )
282324 newKeys [key ] = struct {}{}
283325 }
284326
@@ -310,10 +352,10 @@ func (f *cachedFetcher) UnspentTokensIteratorBy(ctx context.Context, walletID st
310352 f .mu .RLock ()
311353 }
312354
313- it , ok := f .cache .Get (tokenKey (walletID , currency ))
355+ toks , ok := f .cache .Get (tokenKey (walletID , currency ))
314356 f .mu .RUnlock ()
315357 if ok {
316- return it .NewPermutation (), nil
358+ return iterators . Slice ( toks ) .NewPermutation (), nil
317359 }
318360 logger .DebugfContext (ctx , "No tokens found in cache for [%s]. Returning empty iterator." , tokenKey (walletID , currency ))
319361
0 commit comments