Skip to content

Commit 659aff6

Browse files
Rozerxshashankadecaro
authored andcommitted
Refactor sherdlock mock infrastructure
Signed-off-by: Shashank <yshashank959@gmail.com>
1 parent 5db7616 commit 659aff6

24 files changed

+400
-341
lines changed

test_output.txt

42.2 KB
Binary file not shown.

token/services/selector/sherdlock/fetcher.go

Lines changed: 37 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -27,41 +27,17 @@ const (
2727
defaultCacheMaxQueries = maxImmediateRetries
2828
)
2929

30-
//go:generate counterfeiter -o token_fetcher_fake_test.go -fake-name FakeTokenFetcher . tokenFetcher
31-
type tokenFetcher interface {
32-
UnspentTokensIteratorBy(ctx context.Context, walletID string, currency token2.Type) (iterator[*token2.UnspentTokenInWallet], error)
33-
}
34-
35-
//go:generate counterfeiter -o tokendb_fake_test.go -fake-name FakeTokenDB . TokenDB
36-
type TokenDB interface {
37-
SpendableTokensIteratorBy(ctx context.Context, walletID string, typ token2.Type) (driver.SpendableTokensIterator, error)
38-
}
39-
40-
type enhancedIterator[T any] interface {
41-
HasNext() bool
42-
}
43-
44-
type permutatableIterator[T any] interface {
45-
iterators.Iterator[T]
46-
NewPermutation() iterators.Iterator[T]
47-
}
48-
4930
type FetcherStrategy string
5031

5132
const (
52-
Lazy = "lazy"
53-
Eager = "eager"
54-
Mixed = "mixed"
55-
Listener = "listener"
56-
Cached = "cached"
33+
Lazy FetcherStrategy = "lazy"
34+
Eager FetcherStrategy = "eager"
35+
Mixed FetcherStrategy = "mixed"
36+
Listener FetcherStrategy = "listener"
37+
Cached FetcherStrategy = "cached"
5738
)
5839

59-
//go:generate counterfeiter -o fetcher_provider_fake_test.go -fake-name FakeFetcherProvider . FetcherProvider
60-
type FetcherProvider interface {
61-
GetFetcher(tmsID token.TMSID) (tokenFetcher, error)
62-
}
63-
64-
type fetchFunc func(db *tokendb.StoreService, m *Metrics, cacheSize int64, freshnessInterval time.Duration, maxQueries int) tokenFetcher
40+
type fetchFunc func(db *tokendb.StoreService, m *Metrics, cacheSize int64, freshnessInterval time.Duration, maxQueries int) TokenFetcher
6541

6642
type fetcherProvider struct {
6743
tokenStoreServiceManager tokendb.StoreServiceManager
@@ -73,7 +49,7 @@ type fetcherProvider struct {
7349
}
7450

7551
var fetchers = map[FetcherStrategy]fetchFunc{
76-
Mixed: func(db *tokendb.StoreService, m *Metrics, cacheSize int64, freshnessInterval time.Duration, maxQueries int) tokenFetcher {
52+
Mixed: func(db *tokendb.StoreService, m *Metrics, cacheSize int64, freshnessInterval time.Duration, maxQueries int) TokenFetcher {
7753
return newMixedFetcher(db, m, cacheSize, freshnessInterval, maxQueries)
7854
},
7955
}
@@ -96,7 +72,7 @@ func NewFetcherProvider(storeServiceManager tokendb.StoreServiceManager, metrics
9672
}
9773

9874
// GetFetcher returns a token fetcher instance for the specified TMS ID.
99-
func (p *fetcherProvider) GetFetcher(tmsID token.TMSID) (tokenFetcher, error) {
75+
func (p *fetcherProvider) GetFetcher(tmsID token.TMSID) (TokenFetcher, error) {
10076
tokenDB, err := p.tokenStoreServiceManager.StoreServiceByTMSId(tmsID)
10177
if err != nil {
10278
return nil, err
@@ -115,17 +91,17 @@ type mixedFetcher struct {
11591
m *Metrics
11692
}
11793

118-
// newMixedFetcher creates a fetcher that combines eager (cached) and lazy (on-demand) strategies.
119-
func newMixedFetcher(tokenDB TokenDB, m *Metrics, cacheSize int64, freshnessInterval time.Duration, maxQueries int) *mixedFetcher {
94+
// NewMixedFetcher creates a fetcher that combines eager (cached) and lazy (on-demand) strategies.
95+
func NewMixedFetcher(tokenDB TokenDB, m *Metrics, cacheSize int64, freshnessInterval time.Duration, maxQueries int) *mixedFetcher {
12096
return &mixedFetcher{
12197
lazyFetcher: NewLazyFetcher(tokenDB),
122-
eagerFetcher: newCachedFetcher(tokenDB, cacheSize, freshnessInterval, maxQueries),
98+
eagerFetcher: NewCachedFetcher(tokenDB, cacheSize, freshnessInterval, maxQueries),
12399
m: m,
124100
}
125101
}
126102

127103
// UnspentTokensIteratorBy returns an iterator for unspent tokens, trying cached results first, falling back to database query.
128-
func (f *mixedFetcher) UnspentTokensIteratorBy(ctx context.Context, walletID string, currency token2.Type) (iterator[*token2.UnspentTokenInWallet], error) {
104+
func (f *mixedFetcher) UnspentTokensIteratorBy(ctx context.Context, walletID string, currency token2.Type) (Iterator[*token2.UnspentTokenInWallet], error) {
129105
logger.DebugfContext(ctx, "call unspent tokens iterator")
130106
it, err := f.eagerFetcher.UnspentTokensIteratorBy(ctx, walletID, currency)
131107
logger.DebugfContext(ctx, "fetched eager iterator")
@@ -142,6 +118,17 @@ func (f *mixedFetcher) UnspentTokensIteratorBy(ctx context.Context, walletID str
142118
return f.lazyFetcher.UnspentTokensIteratorBy(ctx, walletID, currency)
143119
}
144120

121+
// newCachedFetcher is an internal alias for NewCachedFetcher to maintain compatibility within the package if needed,
122+
// though we usually just use the exported version now.
123+
func newCachedFetcher(tokenDB TokenDB, cacheSize int64, freshnessInterval time.Duration, maxQueriesBeforeRefresh int) *cachedFetcher {
124+
return NewCachedFetcher(tokenDB, cacheSize, freshnessInterval, maxQueriesBeforeRefresh)
125+
}
126+
127+
// newMixedFetcher is an internal alias for NewMixedFetcher.
128+
func newMixedFetcher(tokenDB TokenDB, m *Metrics, cacheSize int64, freshnessInterval time.Duration, maxQueries int) *mixedFetcher {
129+
return NewMixedFetcher(tokenDB, m, cacheSize, freshnessInterval, maxQueries)
130+
}
131+
145132
// lazyFetcher only looks up the results when requested
146133
type lazyFetcher struct {
147134
tokenDB TokenDB
@@ -153,7 +140,7 @@ func NewLazyFetcher(tokenDB TokenDB) *lazyFetcher {
153140
}
154141

155142
// UnspentTokensIteratorBy queries the database directly for unspent tokens.
156-
func (f *lazyFetcher) UnspentTokensIteratorBy(ctx context.Context, walletID string, currency token2.Type) (iterator[*token2.UnspentTokenInWallet], error) {
143+
func (f *lazyFetcher) UnspentTokensIteratorBy(ctx context.Context, walletID string, currency token2.Type) (Iterator[*token2.UnspentTokenInWallet], error) {
157144
logger.DebugfContext(ctx, "Query the DB for new tokens")
158145
it, err := f.tokenDB.SpendableTokensIteratorBy(ctx, walletID, currency)
159146
if err != nil {
@@ -163,6 +150,16 @@ func (f *lazyFetcher) UnspentTokensIteratorBy(ctx context.Context, walletID stri
163150
return collections.NewPermutatedIterator[token2.UnspentTokenInWallet](it)
164151
}
165152

153+
type enhancedIterator[T any] interface {
154+
iterators.Iterator[T]
155+
HasNext() bool
156+
}
157+
158+
type permutatableIterator[T any] interface {
159+
iterators.Iterator[T]
160+
NewPermutation() iterators.Iterator[T]
161+
}
162+
166163
type tokenCache interface {
167164
Get(key string) (permutatableIterator[*token2.UnspentTokenInWallet], bool)
168165
Add(key string, value permutatableIterator[*token2.UnspentTokenInWallet])
@@ -187,8 +184,8 @@ type cachedFetcher struct {
187184
mu sync.RWMutex
188185
}
189186

190-
// newCachedFetcher creates a fetcher that maintains a periodically refreshed cache of all tokens.
191-
func newCachedFetcher(tokenDB TokenDB, cacheSize int64, freshnessInterval time.Duration, maxQueriesBeforeRefresh int) *cachedFetcher {
187+
// NewCachedFetcher creates a fetcher that maintains a periodically refreshed cache of all tokens.
188+
func NewCachedFetcher(tokenDB TokenDB, cacheSize int64, freshnessInterval time.Duration, maxQueriesBeforeRefresh int) *cachedFetcher {
192189
// Use defaults if values are not provided (zero values)
193190
if freshnessInterval <= 0 {
194191
freshnessInterval = defaultCacheFreshnessInterval
@@ -284,7 +281,7 @@ func (f *cachedFetcher) updateCache(ctx context.Context, tokensByKey map[string]
284281
}
285282

286283
// UnspentTokensIteratorBy returns cached unspent tokens, triggering a refresh if the cache is stale or overused.
287-
func (f *cachedFetcher) UnspentTokensIteratorBy(ctx context.Context, walletID string, currency token2.Type) (iterator[*token2.UnspentTokenInWallet], error) {
284+
func (f *cachedFetcher) UnspentTokensIteratorBy(ctx context.Context, walletID string, currency token2.Type) (Iterator[*token2.UnspentTokenInWallet], error) {
288285
defer atomic.AddUint32(&f.queriesResponded, 1)
289286
if f.isCacheOverused() {
290287
logger.DebugfContext(ctx, "Overused data. Soft refresh (in the background)...")
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package sherdlock
8+
9+
import (
10+
"context"
11+
"time"
12+
13+
"github.com/hyperledger-labs/fabric-token-sdk/token"
14+
"github.com/hyperledger-labs/fabric-token-sdk/token/driver"
15+
"github.com/hyperledger-labs/fabric-token-sdk/token/services/utils/types/transaction"
16+
token2 "github.com/hyperledger-labs/fabric-token-sdk/token/token"
17+
)
18+
19+
// Iterator represents a generic iterator with error return and Close method.
20+
//
21+
//go:generate counterfeiter -o mocks/iterator.go -fake-name FakeIterator . Iterator
22+
type Iterator[k any] interface {
23+
Next() (k, error)
24+
Close()
25+
}
26+
27+
// TokenLocker interface for token locking operations.
28+
//
29+
//go:generate counterfeiter -o mocks/token_locker.go -fake-name FakeTokenLocker . TokenLocker
30+
type TokenLocker interface {
31+
TryLock(context.Context, *token2.ID) bool
32+
UnlockAll(ctx context.Context) error
33+
}
34+
35+
// TokenFetcher interface for fetching tokens.
36+
//
37+
//go:generate counterfeiter -o mocks/token_fetcher.go -fake-name FakeTokenFetcher . TokenFetcher
38+
type TokenFetcher interface {
39+
UnspentTokensIteratorBy(ctx context.Context, walletID string, currency token2.Type) (Iterator[*token2.UnspentTokenInWallet], error)
40+
}
41+
42+
// FetcherProvider interface for providing fetcher instances.
43+
//
44+
//go:generate counterfeiter -o mocks/fetcher_provider.go -fake-name FakeFetcherProvider . FetcherProvider
45+
type FetcherProvider interface {
46+
GetFetcher(tmsID token.TMSID) (TokenFetcher, error)
47+
}
48+
49+
// TokenDB interface for database token operations.
50+
//
51+
//go:generate counterfeiter -o mocks/tokendb.go -fake-name FakeTokenDB . TokenDB
52+
type TokenDB interface {
53+
SpendableTokensIteratorBy(ctx context.Context, walletID string, typ token2.Type) (driver.SpendableTokensIterator, error)
54+
}
55+
56+
// ConfigProvider interface for configuration provider.
57+
//
58+
//go:generate counterfeiter -o mocks/config_provider.go -fake-name FakeConfigProvider . ConfigProvider
59+
type ConfigProvider interface {
60+
UnmarshalKey(key string, rawVal interface{}) error
61+
}
62+
63+
// TMS interface for Token Management Service.
64+
//
65+
//go:generate counterfeiter -o mocks/tms.go -fake-name FakeTMS . TMS
66+
type TMS interface {
67+
ID() token.TMSID
68+
PublicParametersManager() *token.PublicParametersManager
69+
}
70+
71+
// Locker interface for manager locking.
72+
//
73+
//go:generate counterfeiter -o mocks/locker.go -fake-name FakeLocker . Locker
74+
type Locker interface {
75+
// Lock locks a specific token for the consumer TX
76+
Lock(ctx context.Context, tokenID *token2.ID, consumerTxID transaction.ID) error
77+
// UnlockByTxID unlocks all tokens locked by the consumer TX
78+
UnlockByTxID(ctx context.Context, consumerTxID transaction.ID) error
79+
// Cleanup removes the locks such that either:
80+
// 1. The transaction that locked that token is valid or invalid;
81+
// 2. The lock is too old.
82+
Cleanup(ctx context.Context, leaseExpiry time.Duration) error
83+
}
84+
85+
// TokenSelectorUnlocker interface combines Selector and UnlockAll.
86+
type TokenSelectorUnlocker interface {
87+
token.Selector
88+
UnlockAll(ctx context.Context) error
89+
}
90+
91+
// Metrics related interfaces
92+
93+
//go:generate counterfeiter -o mocks/metrics_counter.go -fake-name FakeCounter github.com/hyperledger-labs/fabric-smart-client/platform/view/services/metrics.Counter
94+
type Counter interface {
95+
Add(float64)
96+
With(labelValues ...string) Counter
97+
}
98+
99+
//go:generate counterfeiter -o mocks/metrics_histogram.go -fake-name FakeHistogram github.com/hyperledger-labs/fabric-smart-client/platform/view/services/metrics.Histogram
100+
type Histogram interface {
101+
Observe(float64)
102+
With(labelValues ...string) Histogram
103+
}
104+
105+
//go:generate counterfeiter -o mocks/metrics_provider.go -fake-name FakeProvider github.com/hyperledger-labs/fabric-token-sdk/token/core/common/metrics.Provider
106+
type Provider interface {
107+
NewCounter(opts struct {
108+
Namespace string
109+
Subsystem string
110+
Name string
111+
Help string
112+
LabelNames []string
113+
Statsd struct{ LabelNames []string }
114+
}) Counter
115+
NewHistogram(opts struct {
116+
Namespace string
117+
Subsystem string
118+
Name string
119+
Help string
120+
Buckets []float64
121+
LabelNames []string
122+
Statsd struct{ LabelNames []string }
123+
}) Histogram
124+
}
125+
126+
//go:generate counterfeiter -o mocks/spendable_tokens_iterator.go -fake-name FakeSpendableTokensIterator github.com/hyperledger-labs/fabric-token-sdk/token/driver.SpendableTokensIterator
127+
//go:generate counterfeiter -o mocks/store_service_manager.go -fake-name FakeTokenDBStoreServiceManager github.com/hyperledger-labs/fabric-token-sdk/token/services/storage/tokendb.StoreServiceManager
128+
//go:generate counterfeiter -o mocks/token_lock_store_service_manager.go -fake-name FakeTokenLockStoreServiceManager github.com/hyperledger-labs/fabric-token-sdk/token/services/storage/tokenlockdb.StoreServiceManager
129+
130+
type StoreServiceManager interface {
131+
StoreServiceByTMSId(id struct {
132+
Network string
133+
Channel string
134+
Namespace string
135+
Public bool
136+
}) (*struct{}, error)
137+
}

token/services/selector/sherdlock/manager.go

Lines changed: 11 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,10 @@ import (
1515
lazy2 "github.com/hyperledger-labs/fabric-smart-client/platform/common/utils/lazy"
1616
"github.com/hyperledger-labs/fabric-token-sdk/token"
1717
"github.com/hyperledger-labs/fabric-token-sdk/token/services/utils/types/transaction"
18-
token2 "github.com/hyperledger-labs/fabric-token-sdk/token/token"
1918
)
2019

21-
//go:generate counterfeiter -o locker_fake_test.go -fake-name FakeLocker . Locker
22-
type Locker interface {
23-
// Lock locks a specific token for the consumer TX
24-
Lock(ctx context.Context, tokenID *token2.ID, consumerTxID transaction.ID) error
25-
// UnlockByTxID unlocks all tokens locked by the consumer TX
26-
UnlockByTxID(ctx context.Context, consumerTxID transaction.ID) error
27-
// Cleanup removes the locks such that either:
28-
// 1. The transaction that locked that token is valid or invalid;
29-
// 2. The lock is too old.
30-
Cleanup(ctx context.Context, leaseExpiry time.Duration) error
31-
}
32-
33-
type tokenSelectorUnlocker interface {
34-
token.Selector
35-
UnlockAll(ctx context.Context) error
36-
}
37-
38-
type manager struct {
39-
selectorCache lazy2.Provider[transaction.ID, tokenSelectorUnlocker]
20+
type Manager struct {
21+
selectorCache lazy2.Provider[transaction.ID, TokenSelectorUnlocker]
4022
locker Locker
4123
leaseExpiry time.Duration
4224
leaseCleanupTickPeriod time.Duration
@@ -46,31 +28,25 @@ type manager struct {
4628
stopOnce sync.Once
4729
}
4830

49-
//go:generate counterfeiter -o iterator_fake_test.go -fake-name FakeIterator . iterator
50-
type iterator[k any] interface {
51-
Next() (k, error)
52-
Close()
53-
}
54-
5531
func NewManager(
56-
fetcher tokenFetcher,
32+
fetcher TokenFetcher,
5733
locker Locker,
5834
precision uint64,
5935
backoff time.Duration,
6036
maxRetriesAfterBackOff int,
6137
leaseExpiry time.Duration,
6238
leaseCleanupTickPeriod time.Duration,
6339
m *Metrics,
64-
) *manager {
40+
) *Manager {
6541
ctx, cancel := context.WithCancel(context.Background()) //nolint:gosec
66-
mgr := &manager{
42+
mgr := &Manager{
6743
locker: locker,
6844
leaseExpiry: leaseExpiry,
6945
leaseCleanupTickPeriod: leaseCleanupTickPeriod,
7046
metrics: m,
7147
cancel: cancel,
7248
cleanerDone: make(chan struct{}),
73-
selectorCache: lazy2.NewProvider(func(txID transaction.ID) (tokenSelectorUnlocker, error) {
49+
selectorCache: lazy2.NewProvider(func(txID transaction.ID) (TokenSelectorUnlocker, error) {
7450
return NewSherdSelector(txID, fetcher, locker, precision, backoff, maxRetriesAfterBackOff, m), nil
7551
}),
7652
}
@@ -83,23 +59,23 @@ func NewManager(
8359
return mgr
8460
}
8561

86-
func (m *manager) NewSelector(id transaction.ID) (token.Selector, error) {
62+
func (m *Manager) NewSelector(id transaction.ID) (token.Selector, error) {
8763
return m.selectorCache.Get(id)
8864
}
8965

90-
func (m *manager) Unlock(ctx context.Context, id transaction.ID) error {
66+
func (m *Manager) Unlock(ctx context.Context, id transaction.ID) error {
9167
return m.locker.UnlockByTxID(ctx, id)
9268
}
9369

94-
func (m *manager) Close(id transaction.ID) error {
70+
func (m *Manager) Close(id transaction.ID) error {
9571
if c, ok := m.selectorCache.Delete(id); ok {
9672
return c.Close()
9773
}
9874

9975
return errors.New("selector for " + id + " not found")
10076
}
10177

102-
func (m *manager) cleaner(ctx context.Context) {
78+
func (m *Manager) cleaner(ctx context.Context) {
10379
defer close(m.cleanerDone)
10480
ticker := time.NewTicker(m.leaseCleanupTickPeriod)
10581
defer ticker.Stop()
@@ -120,7 +96,7 @@ func (m *manager) cleaner(ctx context.Context) {
12096
}
12197

12298
// Stop cancels the cleaner goroutine and waits for it to exit.
123-
func (m *manager) Stop() {
99+
func (m *Manager) Stop() {
124100
m.stopOnce.Do(func() {
125101
m.cancel()
126102
<-m.cleanerDone

0 commit comments

Comments
 (0)