From 14c7fd81f6f6279ea6eb0b95c541fa1529d52621 Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Sat, 2 May 2026 14:45:35 +0530 Subject: [PATCH 01/27] Add sorting options to ListTokensOptions Signed-off-by: Soumya Mohapatra --- token/driver/wallet.go | 29 +++++++++++++++++++++++++++++ token/wallet.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/token/driver/wallet.go b/token/driver/wallet.go index f92c46efd7..356303b801 100644 --- a/token/driver/wallet.go +++ b/token/driver/wallet.go @@ -69,10 +69,39 @@ type RecipientData struct { TokenMetadataAuditInfo []byte } +// SortField specifies which field to use for ordering token results. +type SortField int + +const ( + // SortByDefault leaves ordering to the database default. + SortByDefault SortField = iota + // SortByQuantity orders results by token quantity (amount). + SortByQuantity +) + +// SortDirection specifies ascending or descending order. +type SortDirection int + +const ( + // SortAscending orders results from smallest to largest. + SortAscending SortDirection = iota + // SortDescending orders results from largest to smallest. + SortDescending +) + // ListTokensOptions contains options that can be used to list tokens from a wallet type ListTokensOptions struct { // TokenType is the type of token to list TokenType token.Type +<<<<<<< HEAD +======= + // Context is used to track the operation + Context context.Context + // SortBy specifies which field to use for ordering the results. + SortBy SortField + // SortDirection specifies ascending or descending order. + SortDirection SortDirection +>>>>>>> f26e9668 (Add sorting options to ListTokensOptions) } // Wallet models a generic wallet diff --git a/token/wallet.go b/token/wallet.go index a9797826e5..52526396a3 100644 --- a/token/wallet.go +++ b/token/wallet.go @@ -37,6 +37,28 @@ func WithType(tokenType token.Type) ListTokensOption { } } +<<<<<<< HEAD +======= +// WithContext return a list tokens option that contains the passed context +func WithContext(ctx context.Context) ListTokensOption { + return func(o *ListTokensOptions) error { + o.Context = ctx + + return nil + } +} + +// WithSortBy returns a list token option that specifies the sort field and direction. +func WithSortBy(field driver.SortField, dir driver.SortDirection) ListTokensOption { + return func(o *ListTokensOptions) error { + o.SortBy = field + o.SortDirection = dir + + return nil + } +} + +>>>>>>> f26e9668 (Add sorting options to ListTokensOptions) type IdentityConfiguration = driver.IdentityConfiguration // WalletManager defines the interface for managing wallets. @@ -358,6 +380,13 @@ func CompileListTokensOption(opts ...ListTokensOption) (*driver.ListTokensOption } return &driver.ListTokensOptions{ +<<<<<<< HEAD TokenType: txOptions.TokenType, +======= + TokenType: txOptions.TokenType, + Context: txOptions.Context, + SortBy: txOptions.SortBy, + SortDirection: txOptions.SortDirection, +>>>>>>> f26e9668 (Add sorting options to ListTokensOptions) }, nil } From aefa35cf24f5f9a8a400e28a81813b8145763083 Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Sat, 2 May 2026 14:48:24 +0530 Subject: [PATCH 02/27] Add IsSpent and SpentBy fields to IssuedToken Signed-off-by: Soumya Mohapatra --- token/token/token.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/token/token/token.go b/token/token/token.go index 6044b670db..579370ff68 100644 --- a/token/token/token.go +++ b/token/token/token.go @@ -61,6 +61,10 @@ type IssuedToken struct { Quantity string `json:"quantity,omitempty" protobuf:"bytes,3,opt,name=quantity,proto3"` // Issuer is the issuer of this token Issuer []byte + // IsSpent is true if the token has been spent (transferred or redeemed) + IsSpent bool `json:"is_spent,omitempty"` + // SpentBy is the transaction ID that consumed this token, if any + SpentBy string `json:"spent_by,omitempty"` } type IssuedTokens struct { From 7b0f45eee4945e115af2b73c34e4b2ce99462967 Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Sat, 2 May 2026 14:49:09 +0530 Subject: [PATCH 03/27] Add IssuedBalance, RedeemedTokens, RedeemedBalance to IssuerWallet interface Signed-off-by: Soumya Mohapatra --- token/driver/wallet.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/token/driver/wallet.go b/token/driver/wallet.go index 356303b801..d1308e97de 100644 --- a/token/driver/wallet.go +++ b/token/driver/wallet.go @@ -180,6 +180,15 @@ type IssuerWallet interface { // HistoryTokens returns the list of tokens issued by this wallet filtered using the passed options. HistoryTokens(ctx context.Context, opts *ListTokensOptions) (*token.IssuedTokens, error) + + // IssuedBalance returns the total amount of tokens issued by this wallet (not yet redeemed). + IssuedBalance(ctx context.Context, opts *ListTokensOptions) (uint64, error) + + // RedeemedTokens returns the list of redeemed tokens that were originally issued by this wallet. + RedeemedTokens(ctx context.Context, opts *ListTokensOptions) (*token.IssuedTokens, error) + + // RedeemedBalance returns the total amount of redeemed tokens that were originally issued by this wallet. + RedeemedBalance(ctx context.Context, opts *ListTokensOptions) (uint64, error) } // AuditorWallet models the wallet of an auditor From c7565cd7d75eb76f3b987799c61485a5209aa751 Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Sat, 2 May 2026 14:49:46 +0530 Subject: [PATCH 04/27] Add IssuedBalance, ListRedeemedTokens, RedeemedBalance to IssuerTokenVault Signed-off-by: Soumya Mohapatra --- token/services/identity/role/wallets.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/token/services/identity/role/wallets.go b/token/services/identity/role/wallets.go index 8fc48c9c84..c6fd41a365 100644 --- a/token/services/identity/role/wallets.go +++ b/token/services/identity/role/wallets.go @@ -117,6 +117,12 @@ func (w *AuditorWallet) GetSigner(ctx context.Context, identity Identity) (Signe //go:generate counterfeiter -o mock/itv.go -fake-name IssuerTokenVault . IssuerTokenVault type IssuerTokenVault interface { ListHistoryIssuedTokens(context.Context) (*token.IssuedTokens, error) + // IssuedBalance returns the sum of amounts of non-deleted tokens issued by this node. + IssuedBalance(ctx context.Context) (uint64, error) + // ListRedeemedTokens returns issued tokens that were redeemed (spent via a Redeem action). + ListRedeemedTokens(ctx context.Context) (*token.IssuedTokens, error) + // RedeemedBalance returns the sum of amounts of redeemed tokens issued by this node. + RedeemedBalance(ctx context.Context) (uint64, error) } // IssuerWallet represents a wallet that manages a single issuer identity. From 24fb917d8e1103bb251d69005c6c62beabae742f Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Sat, 2 May 2026 14:52:58 +0530 Subject: [PATCH 05/27] Implement IssuedBalance, ListRedeemedTokens, RedeemedBalance SQL queries Signed-off-by: Soumya Mohapatra --- token/driver/vault.go | 6 ++ token/services/storage/db/driver/token.go | 6 ++ .../services/storage/db/sql/common/tokens.go | 80 +++++++++++++++++++ token/vault.go | 12 +++ 4 files changed, 104 insertions(+) diff --git a/token/driver/vault.go b/token/driver/vault.go index 6806cf5ca5..f6613645b8 100644 --- a/token/driver/vault.go +++ b/token/driver/vault.go @@ -87,6 +87,12 @@ type QueryEngine interface { ListAuditTokens(ctx context.Context, ids ...*token.ID) ([]*token.Token, error) // ListHistoryIssuedTokens returns a list of all tokens issued by the service. ListHistoryIssuedTokens(ctx context.Context) (*token.IssuedTokens, error) + // IssuedBalance returns the sum of amounts of non-deleted tokens flagged as issued. + IssuedBalance(ctx context.Context) (uint64, error) + // ListRedeemedTokens returns issued tokens that were spent by a Redeem action. + ListRedeemedTokens(ctx context.Context) (*token.IssuedTokens, error) + // RedeemedBalance returns the sum of amounts of issued tokens spent by a Redeem action. + RedeemedBalance(ctx context.Context) (uint64, error) // PublicParams returns the serialized public parameters used by the driver. PublicParams(ctx context.Context) ([]byte, error) // GetTokenMetadata retrieves the private information (metadata) for the given token IDs. diff --git a/token/services/storage/db/driver/token.go b/token/services/storage/db/driver/token.go index 462478626f..f362b919b5 100644 --- a/token/services/storage/db/driver/token.go +++ b/token/services/storage/db/driver/token.go @@ -202,6 +202,12 @@ type TokenStore interface { ListAuditTokens(ctx context.Context, ids ...*token.ID) ([]*token.Token, error) // ListHistoryIssuedTokens returns the list of all issued tokens ListHistoryIssuedTokens(ctx context.Context) (*token.IssuedTokens, error) + // IssuedBalance returns the sum of amounts of non-deleted tokens flagged as issued + IssuedBalance(ctx context.Context) (uint64, error) + // ListRedeemedTokens returns issued tokens that were spent by a Redeem action + ListRedeemedTokens(ctx context.Context) (*token.IssuedTokens, error) + // RedeemedBalance returns the sum of amounts of issued tokens spent by a Redeem action + RedeemedBalance(ctx context.Context) (uint64, error) // GetTokenOutputs returns the value of the tokens as they appear on the ledger for the passed ids. // For each token, the call-back function is invoked. The call-back function is invoked respecting the order of the passed ids. GetTokenOutputs(ctx context.Context, ids []*token.ID, callback driver.QueryCallbackFunc) error diff --git a/token/services/storage/db/sql/common/tokens.go b/token/services/storage/db/sql/common/tokens.go index cb5ffa0486..1302326861 100644 --- a/token/services/storage/db/sql/common/tokens.go +++ b/token/services/storage/db/sql/common/tokens.go @@ -38,6 +38,7 @@ type tokenTables struct { PublicParams string Certifications string Requests string + Transactions string } type TokenStore struct { @@ -68,6 +69,7 @@ func NewTokenStoreWithNotifier(readDB, writeDB *sql.DB, tables TableNames, ci co PublicParams: tables.PublicParams, Certifications: tables.Certifications, Requests: tables.Requests, + Transactions: tables.Transactions, }, ci, notifier), nil } @@ -610,6 +612,84 @@ func (db *TokenStore) ListHistoryIssuedTokens(ctx context.Context) (*token.Issue return &token.IssuedTokens{Tokens: tokens}, rows.Err() } +// IssuedBalance returns the sum of amounts of non-deleted tokens flagged as issued. +func (db *TokenStore) IssuedBalance(ctx context.Context) (uint64, error) { + query, args := q.Select(). + FieldsByName("SUM(amount)"). + From(q.Table(db.table.Tokens)). + Where(cond.And(cond.Eq("issuer", true), cond.Eq("is_deleted", false))). + Format(db.ci) + + logging.Debug(logger, query, args) + sum, err := common.QueryUnique[*uint64](db.readDB, query, args...) + if err != nil || sum == nil { + return 0, err + } + + return *sum, nil +} + +// ListRedeemedTokens returns issued tokens that were spent by a Redeem action. +// It JOINs with the transactions table to identify tokens whose spent_by txID +// has action_type = Redeem (2). +func (db *TokenStore) ListRedeemedTokens(ctx context.Context) (*token.IssuedTokens, error) { + tokTable := q.Table(db.table.Tokens) + txTable := q.Table(db.table.Transactions) + query, args := q.Select(). + Fields( + tokTable.Field("tx_id"), tokTable.Field("idx"), + tokTable.Field("owner_raw"), tokTable.Field("token_type"), + tokTable.Field("quantity"), tokTable.Field("issuer_raw"), + tokTable.Field("is_deleted"), tokTable.Field("spent_by"), + ). + From(tokTable.Join(txTable, cond.Cmp(tokTable.Field("spent_by"), "=", txTable.Field("tx_id")))). + Where(cond.And( + cond.Eq("issuer", true), + cond.Eq("is_deleted", true), + cond.Eq("action_type", int(driver.Redeem)), + )). + Format(db.ci) + + logging.Debug(logger, query, args) + rows, err := db.readDB.QueryContext(ctx, query, args...) + if err != nil { + return nil, err + } + + it := common.NewIterator(rows, func(tok *token.IssuedToken) error { + return rows.Scan(&tok.Id.TxId, &tok.Id.Index, &tok.Owner, &tok.Type, &tok.Quantity, &tok.Issuer, &tok.IsSpent, &tok.SpentBy) + }) + tokens, err := iterators.ReadAllPointers(it) + if err != nil { + return nil, err + } + + return &token.IssuedTokens{Tokens: tokens}, rows.Err() +} + +// RedeemedBalance returns the sum of amounts of issued tokens spent by a Redeem action. +func (db *TokenStore) RedeemedBalance(ctx context.Context) (uint64, error) { + tokTable := q.Table(db.table.Tokens) + txTable := q.Table(db.table.Transactions) + query, args := q.Select(). + FieldsByName("SUM(amount)"). + From(tokTable.Join(txTable, cond.Cmp(tokTable.Field("spent_by"), "=", txTable.Field("tx_id")))). + Where(cond.And( + cond.Eq("issuer", true), + cond.Eq("is_deleted", true), + cond.Eq("action_type", int(driver.Redeem)), + )). + Format(db.ci) + + logging.Debug(logger, query, args) + sum, err := common.QueryUnique[*uint64](db.readDB, query, args...) + if err != nil || sum == nil { + return 0, err + } + + return *sum, nil +} + func (db *TokenStore) GetTokenOutputs(ctx context.Context, ids []*token.ID, callback tdriver.QueryCallbackFunc) error { tokens, err := db.getLedgerToken(ctx, ids) if err != nil { diff --git a/token/vault.go b/token/vault.go index 9d33cf2f30..553fdac533 100644 --- a/token/vault.go +++ b/token/vault.go @@ -123,6 +123,18 @@ func (q *QueryEngine) ListHistoryIssuedTokens(ctx context.Context) (*token.Issue return q.qe.ListHistoryIssuedTokens(ctx) } +func (q *QueryEngine) IssuedBalance(ctx context.Context) (uint64, error) { + return q.qe.IssuedBalance(ctx) +} + +func (q *QueryEngine) ListRedeemedTokens(ctx context.Context) (*token.IssuedTokens, error) { + return q.qe.ListRedeemedTokens(ctx) +} + +func (q *QueryEngine) RedeemedBalance(ctx context.Context) (uint64, error) { + return q.qe.RedeemedBalance(ctx) +} + // PublicParams returns the public parameters stored in the vault func (q *QueryEngine) PublicParams(ctx context.Context) ([]byte, error) { return q.qe.PublicParams(ctx) From 25d67cb4f2cb91968d2b4baf66281e5790646a7d Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Sat, 2 May 2026 14:54:11 +0530 Subject: [PATCH 06/27] Implement IssuerWallet concrete methods for balance and redeemed tokens Signed-off-by: Soumya Mohapatra --- token/services/identity/role/wallets.go | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/token/services/identity/role/wallets.go b/token/services/identity/role/wallets.go index c6fd41a365..f786b25e32 100644 --- a/token/services/identity/role/wallets.go +++ b/token/services/identity/role/wallets.go @@ -220,6 +220,46 @@ func (w *IssuerWallet) HistoryTokens(ctx context.Context, opts *driver.ListToken return unspentTokens, nil } +// IssuedBalance returns the total amount of non-redeemed tokens issued by +// this wallet. +func (w *IssuerWallet) IssuedBalance(ctx context.Context, opts *driver.ListTokensOptions) (uint64, error) { + w.Logger.DebugfContext(ctx, "issuer wallet [%s]: issued balance, type [%s]", w.ID(), opts.TokenType) + return w.TokenVault.IssuedBalance(ctx) +} + +// RedeemedTokens returns the list of redeemed tokens originally issued by this wallet. +func (w *IssuerWallet) RedeemedTokens(ctx context.Context, opts *driver.ListTokensOptions) (*token.IssuedTokens, error) { + w.Logger.DebugfContext(ctx, "issuer wallet [%s]: redeemed tokens, type [%s]", w.ID(), opts.TokenType) + source, err := w.TokenVault.ListRedeemedTokens(ctx) + if err != nil { + return nil, errors.Wrap(err, "redeemed tokens query failed") + } + + return w.filterTokens(ctx, source, opts), nil +} + +// RedeemedBalance returns the total amount of redeemed tokens originally +// issued by this wallet. +func (w *IssuerWallet) RedeemedBalance(ctx context.Context, opts *driver.ListTokensOptions) (uint64, error) { + w.Logger.DebugfContext(ctx, "issuer wallet [%s]: redeemed balance, type [%s]", w.ID(), opts.TokenType) + return w.TokenVault.RedeemedBalance(ctx) +} + +// filterTokens filters the source tokens by token type and wallet identity. +func (w *IssuerWallet) filterTokens(ctx context.Context, source *token.IssuedTokens, opts *driver.ListTokensOptions) *token.IssuedTokens { + result := &token.IssuedTokens{} + for _, t := range source.Tokens { + if len(opts.TokenType) != 0 && t.Type != opts.TokenType { + continue + } + if !w.Contains(ctx, t.Issuer) { + continue + } + result.Tokens = append(result.Tokens, t) + } + return result +} + // CertifierWallet represents a wallet bounded to a single certifier // identity. It provides access to a signer and exposes whether a given // identity is the certifier for this wallet. From 698d9d11317e460b55bbb36d57c153bf3be2dee8 Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Sat, 2 May 2026 14:54:41 +0530 Subject: [PATCH 07/27] Expose IssuedBalance, ListRedeemedTokens, RedeemedBalance in public API Signed-off-by: Soumya Mohapatra --- token/wallet.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/token/wallet.go b/token/wallet.go index 52526396a3..cf77ba1146 100644 --- a/token/wallet.go +++ b/token/wallet.go @@ -371,6 +371,39 @@ func (i *IssuerWallet) ListIssuedTokens(ctx context.Context, opts ...ListTokensO return i.w.HistoryTokens(ctx, compiledOpts) } +// IssuedBalance returns the total amount of non-redeemed tokens issued by this wallet. +// Options: WithType +func (i *IssuerWallet) IssuedBalance(ctx context.Context, opts ...ListTokensOption) (uint64, error) { + compiledOpts, err := CompileListTokensOption(opts...) + if err != nil { + return 0, err + } + + return i.w.IssuedBalance(ctx, compiledOpts) +} + +// ListRedeemedTokens returns the list of redeemed tokens originally issued by this wallet. +// Options: WithType +func (i *IssuerWallet) ListRedeemedTokens(ctx context.Context, opts ...ListTokensOption) (*token.IssuedTokens, error) { + compiledOpts, err := CompileListTokensOption(opts...) + if err != nil { + return nil, err + } + + return i.w.RedeemedTokens(ctx, compiledOpts) +} + +// RedeemedBalance returns the total amount of redeemed tokens originally issued by this wallet. +// Options: WithType +func (i *IssuerWallet) RedeemedBalance(ctx context.Context, opts ...ListTokensOption) (uint64, error) { + compiledOpts, err := CompileListTokensOption(opts...) + if err != nil { + return 0, err + } + + return i.w.RedeemedBalance(ctx, compiledOpts) +} + func CompileListTokensOption(opts ...ListTokensOption) (*driver.ListTokensOptions, error) { txOptions := &ListTokensOptions{} for _, opt := range opts { From 9530f4484189787b4aac3e41a71886455271487c Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Sat, 2 May 2026 15:14:14 +0530 Subject: [PATCH 08/27] Regenerate mocks and add new methods to TokenVault interface Signed-off-by: Soumya Mohapatra --- token/driver/mock/iw.go | 237 ++++++++++++++++++++++ token/driver/mock/qe.go | 231 +++++++++++++++++++++ token/services/identity/role/mock/itv.go | 231 +++++++++++++++++++++ token/services/identity/role/mock/tv.go | 231 +++++++++++++++++++++ token/services/identity/role/wfactory.go | 7 + token/services/tokens/mock/token_store.go | 231 +++++++++++++++++++++ 6 files changed, 1168 insertions(+) diff --git a/token/driver/mock/iw.go b/token/driver/mock/iw.go index 18b3ce2134..dbf9dc2faa 100644 --- a/token/driver/mock/iw.go +++ b/token/driver/mock/iw.go @@ -85,6 +85,48 @@ type IssuerWallet struct { iDReturnsOnCall map[int]struct { result1 string } + IssuedBalanceStub func(context.Context, *driver.ListTokensOptions) (uint64, error) + issuedBalanceMutex sync.RWMutex + issuedBalanceArgsForCall []struct { + arg1 context.Context + arg2 *driver.ListTokensOptions + } + issuedBalanceReturns struct { + result1 uint64 + result2 error + } + issuedBalanceReturnsOnCall map[int]struct { + result1 uint64 + result2 error + } + RedeemedBalanceStub func(context.Context, *driver.ListTokensOptions) (uint64, error) + redeemedBalanceMutex sync.RWMutex + redeemedBalanceArgsForCall []struct { + arg1 context.Context + arg2 *driver.ListTokensOptions + } + redeemedBalanceReturns struct { + result1 uint64 + result2 error + } + redeemedBalanceReturnsOnCall map[int]struct { + result1 uint64 + result2 error + } + RedeemedTokensStub func(context.Context, *driver.ListTokensOptions) (*token.IssuedTokens, error) + redeemedTokensMutex sync.RWMutex + redeemedTokensArgsForCall []struct { + arg1 context.Context + arg2 *driver.ListTokensOptions + } + redeemedTokensReturns struct { + result1 *token.IssuedTokens + result2 error + } + redeemedTokensReturnsOnCall map[int]struct { + result1 *token.IssuedTokens + result2 error + } invocations map[string][][]interface{} invocationsMutex sync.RWMutex } @@ -460,6 +502,201 @@ func (fake *IssuerWallet) IDReturnsOnCall(i int, result1 string) { }{result1} } +func (fake *IssuerWallet) IssuedBalance(arg1 context.Context, arg2 *driver.ListTokensOptions) (uint64, error) { + fake.issuedBalanceMutex.Lock() + ret, specificReturn := fake.issuedBalanceReturnsOnCall[len(fake.issuedBalanceArgsForCall)] + fake.issuedBalanceArgsForCall = append(fake.issuedBalanceArgsForCall, struct { + arg1 context.Context + arg2 *driver.ListTokensOptions + }{arg1, arg2}) + stub := fake.IssuedBalanceStub + fakeReturns := fake.issuedBalanceReturns + fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2}) + fake.issuedBalanceMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *IssuerWallet) IssuedBalanceCallCount() int { + fake.issuedBalanceMutex.RLock() + defer fake.issuedBalanceMutex.RUnlock() + return len(fake.issuedBalanceArgsForCall) +} + +func (fake *IssuerWallet) IssuedBalanceCalls(stub func(context.Context, *driver.ListTokensOptions) (uint64, error)) { + fake.issuedBalanceMutex.Lock() + defer fake.issuedBalanceMutex.Unlock() + fake.IssuedBalanceStub = stub +} + +func (fake *IssuerWallet) IssuedBalanceArgsForCall(i int) (context.Context, *driver.ListTokensOptions) { + fake.issuedBalanceMutex.RLock() + defer fake.issuedBalanceMutex.RUnlock() + argsForCall := fake.issuedBalanceArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *IssuerWallet) IssuedBalanceReturns(result1 uint64, result2 error) { + fake.issuedBalanceMutex.Lock() + defer fake.issuedBalanceMutex.Unlock() + fake.IssuedBalanceStub = nil + fake.issuedBalanceReturns = struct { + result1 uint64 + result2 error + }{result1, result2} +} + +func (fake *IssuerWallet) IssuedBalanceReturnsOnCall(i int, result1 uint64, result2 error) { + fake.issuedBalanceMutex.Lock() + defer fake.issuedBalanceMutex.Unlock() + fake.IssuedBalanceStub = nil + if fake.issuedBalanceReturnsOnCall == nil { + fake.issuedBalanceReturnsOnCall = make(map[int]struct { + result1 uint64 + result2 error + }) + } + fake.issuedBalanceReturnsOnCall[i] = struct { + result1 uint64 + result2 error + }{result1, result2} +} + +func (fake *IssuerWallet) RedeemedBalance(arg1 context.Context, arg2 *driver.ListTokensOptions) (uint64, error) { + fake.redeemedBalanceMutex.Lock() + ret, specificReturn := fake.redeemedBalanceReturnsOnCall[len(fake.redeemedBalanceArgsForCall)] + fake.redeemedBalanceArgsForCall = append(fake.redeemedBalanceArgsForCall, struct { + arg1 context.Context + arg2 *driver.ListTokensOptions + }{arg1, arg2}) + stub := fake.RedeemedBalanceStub + fakeReturns := fake.redeemedBalanceReturns + fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2}) + fake.redeemedBalanceMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *IssuerWallet) RedeemedBalanceCallCount() int { + fake.redeemedBalanceMutex.RLock() + defer fake.redeemedBalanceMutex.RUnlock() + return len(fake.redeemedBalanceArgsForCall) +} + +func (fake *IssuerWallet) RedeemedBalanceCalls(stub func(context.Context, *driver.ListTokensOptions) (uint64, error)) { + fake.redeemedBalanceMutex.Lock() + defer fake.redeemedBalanceMutex.Unlock() + fake.RedeemedBalanceStub = stub +} + +func (fake *IssuerWallet) RedeemedBalanceArgsForCall(i int) (context.Context, *driver.ListTokensOptions) { + fake.redeemedBalanceMutex.RLock() + defer fake.redeemedBalanceMutex.RUnlock() + argsForCall := fake.redeemedBalanceArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *IssuerWallet) RedeemedBalanceReturns(result1 uint64, result2 error) { + fake.redeemedBalanceMutex.Lock() + defer fake.redeemedBalanceMutex.Unlock() + fake.RedeemedBalanceStub = nil + fake.redeemedBalanceReturns = struct { + result1 uint64 + result2 error + }{result1, result2} +} + +func (fake *IssuerWallet) RedeemedBalanceReturnsOnCall(i int, result1 uint64, result2 error) { + fake.redeemedBalanceMutex.Lock() + defer fake.redeemedBalanceMutex.Unlock() + fake.RedeemedBalanceStub = nil + if fake.redeemedBalanceReturnsOnCall == nil { + fake.redeemedBalanceReturnsOnCall = make(map[int]struct { + result1 uint64 + result2 error + }) + } + fake.redeemedBalanceReturnsOnCall[i] = struct { + result1 uint64 + result2 error + }{result1, result2} +} + +func (fake *IssuerWallet) RedeemedTokens(arg1 context.Context, arg2 *driver.ListTokensOptions) (*token.IssuedTokens, error) { + fake.redeemedTokensMutex.Lock() + ret, specificReturn := fake.redeemedTokensReturnsOnCall[len(fake.redeemedTokensArgsForCall)] + fake.redeemedTokensArgsForCall = append(fake.redeemedTokensArgsForCall, struct { + arg1 context.Context + arg2 *driver.ListTokensOptions + }{arg1, arg2}) + stub := fake.RedeemedTokensStub + fakeReturns := fake.redeemedTokensReturns + fake.recordInvocation("RedeemedTokens", []interface{}{arg1, arg2}) + fake.redeemedTokensMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *IssuerWallet) RedeemedTokensCallCount() int { + fake.redeemedTokensMutex.RLock() + defer fake.redeemedTokensMutex.RUnlock() + return len(fake.redeemedTokensArgsForCall) +} + +func (fake *IssuerWallet) RedeemedTokensCalls(stub func(context.Context, *driver.ListTokensOptions) (*token.IssuedTokens, error)) { + fake.redeemedTokensMutex.Lock() + defer fake.redeemedTokensMutex.Unlock() + fake.RedeemedTokensStub = stub +} + +func (fake *IssuerWallet) RedeemedTokensArgsForCall(i int) (context.Context, *driver.ListTokensOptions) { + fake.redeemedTokensMutex.RLock() + defer fake.redeemedTokensMutex.RUnlock() + argsForCall := fake.redeemedTokensArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *IssuerWallet) RedeemedTokensReturns(result1 *token.IssuedTokens, result2 error) { + fake.redeemedTokensMutex.Lock() + defer fake.redeemedTokensMutex.Unlock() + fake.RedeemedTokensStub = nil + fake.redeemedTokensReturns = struct { + result1 *token.IssuedTokens + result2 error + }{result1, result2} +} + +func (fake *IssuerWallet) RedeemedTokensReturnsOnCall(i int, result1 *token.IssuedTokens, result2 error) { + fake.redeemedTokensMutex.Lock() + defer fake.redeemedTokensMutex.Unlock() + fake.RedeemedTokensStub = nil + if fake.redeemedTokensReturnsOnCall == nil { + fake.redeemedTokensReturnsOnCall = make(map[int]struct { + result1 *token.IssuedTokens + result2 error + }) + } + fake.redeemedTokensReturnsOnCall[i] = struct { + result1 *token.IssuedTokens + result2 error + }{result1, result2} +} + func (fake *IssuerWallet) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() diff --git a/token/driver/mock/qe.go b/token/driver/mock/qe.go index 6279ccaf9d..22ad498e5d 100644 --- a/token/driver/mock/qe.go +++ b/token/driver/mock/qe.go @@ -129,6 +129,19 @@ type QueryEngine struct { result1 bool result2 error } + IssuedBalanceStub func(context.Context) (uint64, error) + issuedBalanceMutex sync.RWMutex + issuedBalanceArgsForCall []struct { + arg1 context.Context + } + issuedBalanceReturns struct { + result1 uint64 + result2 error + } + issuedBalanceReturnsOnCall map[int]struct { + result1 uint64 + result2 error + } ListAuditTokensStub func(context.Context, ...*token.ID) ([]*token.Token, error) listAuditTokensMutex sync.RWMutex listAuditTokensArgsForCall []struct { @@ -156,6 +169,19 @@ type QueryEngine struct { result1 *token.IssuedTokens result2 error } + ListRedeemedTokensStub func(context.Context) (*token.IssuedTokens, error) + listRedeemedTokensMutex sync.RWMutex + listRedeemedTokensArgsForCall []struct { + arg1 context.Context + } + listRedeemedTokensReturns struct { + result1 *token.IssuedTokens + result2 error + } + listRedeemedTokensReturnsOnCall map[int]struct { + result1 *token.IssuedTokens + result2 error + } ListUnspentTokensStub func(context.Context) (*token.UnspentTokens, error) listUnspentTokensMutex sync.RWMutex listUnspentTokensArgsForCall []struct { @@ -182,6 +208,19 @@ type QueryEngine struct { result1 []byte result2 error } + RedeemedBalanceStub func(context.Context) (uint64, error) + redeemedBalanceMutex sync.RWMutex + redeemedBalanceArgsForCall []struct { + arg1 context.Context + } + redeemedBalanceReturns struct { + result1 uint64 + result2 error + } + redeemedBalanceReturnsOnCall map[int]struct { + result1 uint64 + result2 error + } UnspentLedgerTokensIteratorByStub func(context.Context) (driver.LedgerTokensIterator, error) unspentLedgerTokensIteratorByMutex sync.RWMutex unspentLedgerTokensIteratorByArgsForCall []struct { @@ -786,6 +825,70 @@ func (fake *QueryEngine) IsPendingReturnsOnCall(i int, result1 bool, result2 err }{result1, result2} } +func (fake *QueryEngine) IssuedBalance(arg1 context.Context) (uint64, error) { + fake.issuedBalanceMutex.Lock() + ret, specificReturn := fake.issuedBalanceReturnsOnCall[len(fake.issuedBalanceArgsForCall)] + fake.issuedBalanceArgsForCall = append(fake.issuedBalanceArgsForCall, struct { + arg1 context.Context + }{arg1}) + stub := fake.IssuedBalanceStub + fakeReturns := fake.issuedBalanceReturns + fake.recordInvocation("IssuedBalance", []interface{}{arg1}) + fake.issuedBalanceMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *QueryEngine) IssuedBalanceCallCount() int { + fake.issuedBalanceMutex.RLock() + defer fake.issuedBalanceMutex.RUnlock() + return len(fake.issuedBalanceArgsForCall) +} + +func (fake *QueryEngine) IssuedBalanceCalls(stub func(context.Context) (uint64, error)) { + fake.issuedBalanceMutex.Lock() + defer fake.issuedBalanceMutex.Unlock() + fake.IssuedBalanceStub = stub +} + +func (fake *QueryEngine) IssuedBalanceArgsForCall(i int) context.Context { + fake.issuedBalanceMutex.RLock() + defer fake.issuedBalanceMutex.RUnlock() + argsForCall := fake.issuedBalanceArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *QueryEngine) IssuedBalanceReturns(result1 uint64, result2 error) { + fake.issuedBalanceMutex.Lock() + defer fake.issuedBalanceMutex.Unlock() + fake.IssuedBalanceStub = nil + fake.issuedBalanceReturns = struct { + result1 uint64 + result2 error + }{result1, result2} +} + +func (fake *QueryEngine) IssuedBalanceReturnsOnCall(i int, result1 uint64, result2 error) { + fake.issuedBalanceMutex.Lock() + defer fake.issuedBalanceMutex.Unlock() + fake.IssuedBalanceStub = nil + if fake.issuedBalanceReturnsOnCall == nil { + fake.issuedBalanceReturnsOnCall = make(map[int]struct { + result1 uint64 + result2 error + }) + } + fake.issuedBalanceReturnsOnCall[i] = struct { + result1 uint64 + result2 error + }{result1, result2} +} + func (fake *QueryEngine) ListAuditTokens(arg1 context.Context, arg2 ...*token.ID) ([]*token.Token, error) { fake.listAuditTokensMutex.Lock() ret, specificReturn := fake.listAuditTokensReturnsOnCall[len(fake.listAuditTokensArgsForCall)] @@ -915,6 +1018,70 @@ func (fake *QueryEngine) ListHistoryIssuedTokensReturnsOnCall(i int, result1 *to }{result1, result2} } +func (fake *QueryEngine) ListRedeemedTokens(arg1 context.Context) (*token.IssuedTokens, error) { + fake.listRedeemedTokensMutex.Lock() + ret, specificReturn := fake.listRedeemedTokensReturnsOnCall[len(fake.listRedeemedTokensArgsForCall)] + fake.listRedeemedTokensArgsForCall = append(fake.listRedeemedTokensArgsForCall, struct { + arg1 context.Context + }{arg1}) + stub := fake.ListRedeemedTokensStub + fakeReturns := fake.listRedeemedTokensReturns + fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1}) + fake.listRedeemedTokensMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *QueryEngine) ListRedeemedTokensCallCount() int { + fake.listRedeemedTokensMutex.RLock() + defer fake.listRedeemedTokensMutex.RUnlock() + return len(fake.listRedeemedTokensArgsForCall) +} + +func (fake *QueryEngine) ListRedeemedTokensCalls(stub func(context.Context) (*token.IssuedTokens, error)) { + fake.listRedeemedTokensMutex.Lock() + defer fake.listRedeemedTokensMutex.Unlock() + fake.ListRedeemedTokensStub = stub +} + +func (fake *QueryEngine) ListRedeemedTokensArgsForCall(i int) context.Context { + fake.listRedeemedTokensMutex.RLock() + defer fake.listRedeemedTokensMutex.RUnlock() + argsForCall := fake.listRedeemedTokensArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *QueryEngine) ListRedeemedTokensReturns(result1 *token.IssuedTokens, result2 error) { + fake.listRedeemedTokensMutex.Lock() + defer fake.listRedeemedTokensMutex.Unlock() + fake.ListRedeemedTokensStub = nil + fake.listRedeemedTokensReturns = struct { + result1 *token.IssuedTokens + result2 error + }{result1, result2} +} + +func (fake *QueryEngine) ListRedeemedTokensReturnsOnCall(i int, result1 *token.IssuedTokens, result2 error) { + fake.listRedeemedTokensMutex.Lock() + defer fake.listRedeemedTokensMutex.Unlock() + fake.ListRedeemedTokensStub = nil + if fake.listRedeemedTokensReturnsOnCall == nil { + fake.listRedeemedTokensReturnsOnCall = make(map[int]struct { + result1 *token.IssuedTokens + result2 error + }) + } + fake.listRedeemedTokensReturnsOnCall[i] = struct { + result1 *token.IssuedTokens + result2 error + }{result1, result2} +} + func (fake *QueryEngine) ListUnspentTokens(arg1 context.Context) (*token.UnspentTokens, error) { fake.listUnspentTokensMutex.Lock() ret, specificReturn := fake.listUnspentTokensReturnsOnCall[len(fake.listUnspentTokensArgsForCall)] @@ -1043,6 +1210,70 @@ func (fake *QueryEngine) PublicParamsReturnsOnCall(i int, result1 []byte, result }{result1, result2} } +func (fake *QueryEngine) RedeemedBalance(arg1 context.Context) (uint64, error) { + fake.redeemedBalanceMutex.Lock() + ret, specificReturn := fake.redeemedBalanceReturnsOnCall[len(fake.redeemedBalanceArgsForCall)] + fake.redeemedBalanceArgsForCall = append(fake.redeemedBalanceArgsForCall, struct { + arg1 context.Context + }{arg1}) + stub := fake.RedeemedBalanceStub + fakeReturns := fake.redeemedBalanceReturns + fake.recordInvocation("RedeemedBalance", []interface{}{arg1}) + fake.redeemedBalanceMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *QueryEngine) RedeemedBalanceCallCount() int { + fake.redeemedBalanceMutex.RLock() + defer fake.redeemedBalanceMutex.RUnlock() + return len(fake.redeemedBalanceArgsForCall) +} + +func (fake *QueryEngine) RedeemedBalanceCalls(stub func(context.Context) (uint64, error)) { + fake.redeemedBalanceMutex.Lock() + defer fake.redeemedBalanceMutex.Unlock() + fake.RedeemedBalanceStub = stub +} + +func (fake *QueryEngine) RedeemedBalanceArgsForCall(i int) context.Context { + fake.redeemedBalanceMutex.RLock() + defer fake.redeemedBalanceMutex.RUnlock() + argsForCall := fake.redeemedBalanceArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *QueryEngine) RedeemedBalanceReturns(result1 uint64, result2 error) { + fake.redeemedBalanceMutex.Lock() + defer fake.redeemedBalanceMutex.Unlock() + fake.RedeemedBalanceStub = nil + fake.redeemedBalanceReturns = struct { + result1 uint64 + result2 error + }{result1, result2} +} + +func (fake *QueryEngine) RedeemedBalanceReturnsOnCall(i int, result1 uint64, result2 error) { + fake.redeemedBalanceMutex.Lock() + defer fake.redeemedBalanceMutex.Unlock() + fake.RedeemedBalanceStub = nil + if fake.redeemedBalanceReturnsOnCall == nil { + fake.redeemedBalanceReturnsOnCall = make(map[int]struct { + result1 uint64 + result2 error + }) + } + fake.redeemedBalanceReturnsOnCall[i] = struct { + result1 uint64 + result2 error + }{result1, result2} +} + func (fake *QueryEngine) UnspentLedgerTokensIteratorBy(arg1 context.Context) (driver.LedgerTokensIterator, error) { fake.unspentLedgerTokensIteratorByMutex.Lock() ret, specificReturn := fake.unspentLedgerTokensIteratorByReturnsOnCall[len(fake.unspentLedgerTokensIteratorByArgsForCall)] diff --git a/token/services/identity/role/mock/itv.go b/token/services/identity/role/mock/itv.go index 5b5bc9ab54..4cb52e9a74 100644 --- a/token/services/identity/role/mock/itv.go +++ b/token/services/identity/role/mock/itv.go @@ -10,6 +10,19 @@ import ( ) type IssuerTokenVault struct { + IssuedBalanceStub func(context.Context) (uint64, error) + issuedBalanceMutex sync.RWMutex + issuedBalanceArgsForCall []struct { + arg1 context.Context + } + issuedBalanceReturns struct { + result1 uint64 + result2 error + } + issuedBalanceReturnsOnCall map[int]struct { + result1 uint64 + result2 error + } ListHistoryIssuedTokensStub func(context.Context) (*token.IssuedTokens, error) listHistoryIssuedTokensMutex sync.RWMutex listHistoryIssuedTokensArgsForCall []struct { @@ -23,10 +36,100 @@ type IssuerTokenVault struct { result1 *token.IssuedTokens result2 error } + ListRedeemedTokensStub func(context.Context) (*token.IssuedTokens, error) + listRedeemedTokensMutex sync.RWMutex + listRedeemedTokensArgsForCall []struct { + arg1 context.Context + } + listRedeemedTokensReturns struct { + result1 *token.IssuedTokens + result2 error + } + listRedeemedTokensReturnsOnCall map[int]struct { + result1 *token.IssuedTokens + result2 error + } + RedeemedBalanceStub func(context.Context) (uint64, error) + redeemedBalanceMutex sync.RWMutex + redeemedBalanceArgsForCall []struct { + arg1 context.Context + } + redeemedBalanceReturns struct { + result1 uint64 + result2 error + } + redeemedBalanceReturnsOnCall map[int]struct { + result1 uint64 + result2 error + } invocations map[string][][]interface{} invocationsMutex sync.RWMutex } +func (fake *IssuerTokenVault) IssuedBalance(arg1 context.Context) (uint64, error) { + fake.issuedBalanceMutex.Lock() + ret, specificReturn := fake.issuedBalanceReturnsOnCall[len(fake.issuedBalanceArgsForCall)] + fake.issuedBalanceArgsForCall = append(fake.issuedBalanceArgsForCall, struct { + arg1 context.Context + }{arg1}) + stub := fake.IssuedBalanceStub + fakeReturns := fake.issuedBalanceReturns + fake.recordInvocation("IssuedBalance", []interface{}{arg1}) + fake.issuedBalanceMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *IssuerTokenVault) IssuedBalanceCallCount() int { + fake.issuedBalanceMutex.RLock() + defer fake.issuedBalanceMutex.RUnlock() + return len(fake.issuedBalanceArgsForCall) +} + +func (fake *IssuerTokenVault) IssuedBalanceCalls(stub func(context.Context) (uint64, error)) { + fake.issuedBalanceMutex.Lock() + defer fake.issuedBalanceMutex.Unlock() + fake.IssuedBalanceStub = stub +} + +func (fake *IssuerTokenVault) IssuedBalanceArgsForCall(i int) context.Context { + fake.issuedBalanceMutex.RLock() + defer fake.issuedBalanceMutex.RUnlock() + argsForCall := fake.issuedBalanceArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *IssuerTokenVault) IssuedBalanceReturns(result1 uint64, result2 error) { + fake.issuedBalanceMutex.Lock() + defer fake.issuedBalanceMutex.Unlock() + fake.IssuedBalanceStub = nil + fake.issuedBalanceReturns = struct { + result1 uint64 + result2 error + }{result1, result2} +} + +func (fake *IssuerTokenVault) IssuedBalanceReturnsOnCall(i int, result1 uint64, result2 error) { + fake.issuedBalanceMutex.Lock() + defer fake.issuedBalanceMutex.Unlock() + fake.IssuedBalanceStub = nil + if fake.issuedBalanceReturnsOnCall == nil { + fake.issuedBalanceReturnsOnCall = make(map[int]struct { + result1 uint64 + result2 error + }) + } + fake.issuedBalanceReturnsOnCall[i] = struct { + result1 uint64 + result2 error + }{result1, result2} +} + func (fake *IssuerTokenVault) ListHistoryIssuedTokens(arg1 context.Context) (*token.IssuedTokens, error) { fake.listHistoryIssuedTokensMutex.Lock() ret, specificReturn := fake.listHistoryIssuedTokensReturnsOnCall[len(fake.listHistoryIssuedTokensArgsForCall)] @@ -91,6 +194,134 @@ func (fake *IssuerTokenVault) ListHistoryIssuedTokensReturnsOnCall(i int, result }{result1, result2} } +func (fake *IssuerTokenVault) ListRedeemedTokens(arg1 context.Context) (*token.IssuedTokens, error) { + fake.listRedeemedTokensMutex.Lock() + ret, specificReturn := fake.listRedeemedTokensReturnsOnCall[len(fake.listRedeemedTokensArgsForCall)] + fake.listRedeemedTokensArgsForCall = append(fake.listRedeemedTokensArgsForCall, struct { + arg1 context.Context + }{arg1}) + stub := fake.ListRedeemedTokensStub + fakeReturns := fake.listRedeemedTokensReturns + fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1}) + fake.listRedeemedTokensMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *IssuerTokenVault) ListRedeemedTokensCallCount() int { + fake.listRedeemedTokensMutex.RLock() + defer fake.listRedeemedTokensMutex.RUnlock() + return len(fake.listRedeemedTokensArgsForCall) +} + +func (fake *IssuerTokenVault) ListRedeemedTokensCalls(stub func(context.Context) (*token.IssuedTokens, error)) { + fake.listRedeemedTokensMutex.Lock() + defer fake.listRedeemedTokensMutex.Unlock() + fake.ListRedeemedTokensStub = stub +} + +func (fake *IssuerTokenVault) ListRedeemedTokensArgsForCall(i int) context.Context { + fake.listRedeemedTokensMutex.RLock() + defer fake.listRedeemedTokensMutex.RUnlock() + argsForCall := fake.listRedeemedTokensArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *IssuerTokenVault) ListRedeemedTokensReturns(result1 *token.IssuedTokens, result2 error) { + fake.listRedeemedTokensMutex.Lock() + defer fake.listRedeemedTokensMutex.Unlock() + fake.ListRedeemedTokensStub = nil + fake.listRedeemedTokensReturns = struct { + result1 *token.IssuedTokens + result2 error + }{result1, result2} +} + +func (fake *IssuerTokenVault) ListRedeemedTokensReturnsOnCall(i int, result1 *token.IssuedTokens, result2 error) { + fake.listRedeemedTokensMutex.Lock() + defer fake.listRedeemedTokensMutex.Unlock() + fake.ListRedeemedTokensStub = nil + if fake.listRedeemedTokensReturnsOnCall == nil { + fake.listRedeemedTokensReturnsOnCall = make(map[int]struct { + result1 *token.IssuedTokens + result2 error + }) + } + fake.listRedeemedTokensReturnsOnCall[i] = struct { + result1 *token.IssuedTokens + result2 error + }{result1, result2} +} + +func (fake *IssuerTokenVault) RedeemedBalance(arg1 context.Context) (uint64, error) { + fake.redeemedBalanceMutex.Lock() + ret, specificReturn := fake.redeemedBalanceReturnsOnCall[len(fake.redeemedBalanceArgsForCall)] + fake.redeemedBalanceArgsForCall = append(fake.redeemedBalanceArgsForCall, struct { + arg1 context.Context + }{arg1}) + stub := fake.RedeemedBalanceStub + fakeReturns := fake.redeemedBalanceReturns + fake.recordInvocation("RedeemedBalance", []interface{}{arg1}) + fake.redeemedBalanceMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *IssuerTokenVault) RedeemedBalanceCallCount() int { + fake.redeemedBalanceMutex.RLock() + defer fake.redeemedBalanceMutex.RUnlock() + return len(fake.redeemedBalanceArgsForCall) +} + +func (fake *IssuerTokenVault) RedeemedBalanceCalls(stub func(context.Context) (uint64, error)) { + fake.redeemedBalanceMutex.Lock() + defer fake.redeemedBalanceMutex.Unlock() + fake.RedeemedBalanceStub = stub +} + +func (fake *IssuerTokenVault) RedeemedBalanceArgsForCall(i int) context.Context { + fake.redeemedBalanceMutex.RLock() + defer fake.redeemedBalanceMutex.RUnlock() + argsForCall := fake.redeemedBalanceArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *IssuerTokenVault) RedeemedBalanceReturns(result1 uint64, result2 error) { + fake.redeemedBalanceMutex.Lock() + defer fake.redeemedBalanceMutex.Unlock() + fake.RedeemedBalanceStub = nil + fake.redeemedBalanceReturns = struct { + result1 uint64 + result2 error + }{result1, result2} +} + +func (fake *IssuerTokenVault) RedeemedBalanceReturnsOnCall(i int, result1 uint64, result2 error) { + fake.redeemedBalanceMutex.Lock() + defer fake.redeemedBalanceMutex.Unlock() + fake.RedeemedBalanceStub = nil + if fake.redeemedBalanceReturnsOnCall == nil { + fake.redeemedBalanceReturnsOnCall = make(map[int]struct { + result1 uint64 + result2 error + }) + } + fake.redeemedBalanceReturnsOnCall[i] = struct { + result1 uint64 + result2 error + }{result1, result2} +} + func (fake *IssuerTokenVault) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() diff --git a/token/services/identity/role/mock/tv.go b/token/services/identity/role/mock/tv.go index b463ac19ce..6646006264 100644 --- a/token/services/identity/role/mock/tv.go +++ b/token/services/identity/role/mock/tv.go @@ -27,6 +27,19 @@ type TokenVault struct { result1 *big.Int result2 error } + IssuedBalanceStub func(context.Context) (uint64, error) + issuedBalanceMutex sync.RWMutex + issuedBalanceArgsForCall []struct { + arg1 context.Context + } + issuedBalanceReturns struct { + result1 uint64 + result2 error + } + issuedBalanceReturnsOnCall map[int]struct { + result1 uint64 + result2 error + } ListHistoryIssuedTokensStub func(context.Context) (*token.IssuedTokens, error) listHistoryIssuedTokensMutex sync.RWMutex listHistoryIssuedTokensArgsForCall []struct { @@ -40,6 +53,32 @@ type TokenVault struct { result1 *token.IssuedTokens result2 error } + ListRedeemedTokensStub func(context.Context) (*token.IssuedTokens, error) + listRedeemedTokensMutex sync.RWMutex + listRedeemedTokensArgsForCall []struct { + arg1 context.Context + } + listRedeemedTokensReturns struct { + result1 *token.IssuedTokens + result2 error + } + listRedeemedTokensReturnsOnCall map[int]struct { + result1 *token.IssuedTokens + result2 error + } + RedeemedBalanceStub func(context.Context) (uint64, error) + redeemedBalanceMutex sync.RWMutex + redeemedBalanceArgsForCall []struct { + arg1 context.Context + } + redeemedBalanceReturns struct { + result1 uint64 + result2 error + } + redeemedBalanceReturnsOnCall map[int]struct { + result1 uint64 + result2 error + } UnspentTokensIteratorByStub func(context.Context, string, token.Type) (driver.UnspentTokensIterator, error) unspentTokensIteratorByMutex sync.RWMutex unspentTokensIteratorByArgsForCall []struct { @@ -125,6 +164,70 @@ func (fake *TokenVault) BalanceReturnsOnCall(i int, result1 *big.Int, result2 er }{result1, result2} } +func (fake *TokenVault) IssuedBalance(arg1 context.Context) (uint64, error) { + fake.issuedBalanceMutex.Lock() + ret, specificReturn := fake.issuedBalanceReturnsOnCall[len(fake.issuedBalanceArgsForCall)] + fake.issuedBalanceArgsForCall = append(fake.issuedBalanceArgsForCall, struct { + arg1 context.Context + }{arg1}) + stub := fake.IssuedBalanceStub + fakeReturns := fake.issuedBalanceReturns + fake.recordInvocation("IssuedBalance", []interface{}{arg1}) + fake.issuedBalanceMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *TokenVault) IssuedBalanceCallCount() int { + fake.issuedBalanceMutex.RLock() + defer fake.issuedBalanceMutex.RUnlock() + return len(fake.issuedBalanceArgsForCall) +} + +func (fake *TokenVault) IssuedBalanceCalls(stub func(context.Context) (uint64, error)) { + fake.issuedBalanceMutex.Lock() + defer fake.issuedBalanceMutex.Unlock() + fake.IssuedBalanceStub = stub +} + +func (fake *TokenVault) IssuedBalanceArgsForCall(i int) context.Context { + fake.issuedBalanceMutex.RLock() + defer fake.issuedBalanceMutex.RUnlock() + argsForCall := fake.issuedBalanceArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *TokenVault) IssuedBalanceReturns(result1 uint64, result2 error) { + fake.issuedBalanceMutex.Lock() + defer fake.issuedBalanceMutex.Unlock() + fake.IssuedBalanceStub = nil + fake.issuedBalanceReturns = struct { + result1 uint64 + result2 error + }{result1, result2} +} + +func (fake *TokenVault) IssuedBalanceReturnsOnCall(i int, result1 uint64, result2 error) { + fake.issuedBalanceMutex.Lock() + defer fake.issuedBalanceMutex.Unlock() + fake.IssuedBalanceStub = nil + if fake.issuedBalanceReturnsOnCall == nil { + fake.issuedBalanceReturnsOnCall = make(map[int]struct { + result1 uint64 + result2 error + }) + } + fake.issuedBalanceReturnsOnCall[i] = struct { + result1 uint64 + result2 error + }{result1, result2} +} + func (fake *TokenVault) ListHistoryIssuedTokens(arg1 context.Context) (*token.IssuedTokens, error) { fake.listHistoryIssuedTokensMutex.Lock() ret, specificReturn := fake.listHistoryIssuedTokensReturnsOnCall[len(fake.listHistoryIssuedTokensArgsForCall)] @@ -189,6 +292,134 @@ func (fake *TokenVault) ListHistoryIssuedTokensReturnsOnCall(i int, result1 *tok }{result1, result2} } +func (fake *TokenVault) ListRedeemedTokens(arg1 context.Context) (*token.IssuedTokens, error) { + fake.listRedeemedTokensMutex.Lock() + ret, specificReturn := fake.listRedeemedTokensReturnsOnCall[len(fake.listRedeemedTokensArgsForCall)] + fake.listRedeemedTokensArgsForCall = append(fake.listRedeemedTokensArgsForCall, struct { + arg1 context.Context + }{arg1}) + stub := fake.ListRedeemedTokensStub + fakeReturns := fake.listRedeemedTokensReturns + fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1}) + fake.listRedeemedTokensMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *TokenVault) ListRedeemedTokensCallCount() int { + fake.listRedeemedTokensMutex.RLock() + defer fake.listRedeemedTokensMutex.RUnlock() + return len(fake.listRedeemedTokensArgsForCall) +} + +func (fake *TokenVault) ListRedeemedTokensCalls(stub func(context.Context) (*token.IssuedTokens, error)) { + fake.listRedeemedTokensMutex.Lock() + defer fake.listRedeemedTokensMutex.Unlock() + fake.ListRedeemedTokensStub = stub +} + +func (fake *TokenVault) ListRedeemedTokensArgsForCall(i int) context.Context { + fake.listRedeemedTokensMutex.RLock() + defer fake.listRedeemedTokensMutex.RUnlock() + argsForCall := fake.listRedeemedTokensArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *TokenVault) ListRedeemedTokensReturns(result1 *token.IssuedTokens, result2 error) { + fake.listRedeemedTokensMutex.Lock() + defer fake.listRedeemedTokensMutex.Unlock() + fake.ListRedeemedTokensStub = nil + fake.listRedeemedTokensReturns = struct { + result1 *token.IssuedTokens + result2 error + }{result1, result2} +} + +func (fake *TokenVault) ListRedeemedTokensReturnsOnCall(i int, result1 *token.IssuedTokens, result2 error) { + fake.listRedeemedTokensMutex.Lock() + defer fake.listRedeemedTokensMutex.Unlock() + fake.ListRedeemedTokensStub = nil + if fake.listRedeemedTokensReturnsOnCall == nil { + fake.listRedeemedTokensReturnsOnCall = make(map[int]struct { + result1 *token.IssuedTokens + result2 error + }) + } + fake.listRedeemedTokensReturnsOnCall[i] = struct { + result1 *token.IssuedTokens + result2 error + }{result1, result2} +} + +func (fake *TokenVault) RedeemedBalance(arg1 context.Context) (uint64, error) { + fake.redeemedBalanceMutex.Lock() + ret, specificReturn := fake.redeemedBalanceReturnsOnCall[len(fake.redeemedBalanceArgsForCall)] + fake.redeemedBalanceArgsForCall = append(fake.redeemedBalanceArgsForCall, struct { + arg1 context.Context + }{arg1}) + stub := fake.RedeemedBalanceStub + fakeReturns := fake.redeemedBalanceReturns + fake.recordInvocation("RedeemedBalance", []interface{}{arg1}) + fake.redeemedBalanceMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *TokenVault) RedeemedBalanceCallCount() int { + fake.redeemedBalanceMutex.RLock() + defer fake.redeemedBalanceMutex.RUnlock() + return len(fake.redeemedBalanceArgsForCall) +} + +func (fake *TokenVault) RedeemedBalanceCalls(stub func(context.Context) (uint64, error)) { + fake.redeemedBalanceMutex.Lock() + defer fake.redeemedBalanceMutex.Unlock() + fake.RedeemedBalanceStub = stub +} + +func (fake *TokenVault) RedeemedBalanceArgsForCall(i int) context.Context { + fake.redeemedBalanceMutex.RLock() + defer fake.redeemedBalanceMutex.RUnlock() + argsForCall := fake.redeemedBalanceArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *TokenVault) RedeemedBalanceReturns(result1 uint64, result2 error) { + fake.redeemedBalanceMutex.Lock() + defer fake.redeemedBalanceMutex.Unlock() + fake.RedeemedBalanceStub = nil + fake.redeemedBalanceReturns = struct { + result1 uint64 + result2 error + }{result1, result2} +} + +func (fake *TokenVault) RedeemedBalanceReturnsOnCall(i int, result1 uint64, result2 error) { + fake.redeemedBalanceMutex.Lock() + defer fake.redeemedBalanceMutex.Unlock() + fake.RedeemedBalanceStub = nil + if fake.redeemedBalanceReturnsOnCall == nil { + fake.redeemedBalanceReturnsOnCall = make(map[int]struct { + result1 uint64 + result2 error + }) + } + fake.redeemedBalanceReturnsOnCall[i] = struct { + result1 uint64 + result2 error + }{result1, result2} +} + func (fake *TokenVault) UnspentTokensIteratorBy(arg1 context.Context, arg2 string, arg3 token.Type) (driver.UnspentTokensIterator, error) { fake.unspentTokensIteratorByMutex.Lock() ret, specificReturn := fake.unspentTokensIteratorByReturnsOnCall[len(fake.unspentTokensIteratorByArgsForCall)] diff --git a/token/services/identity/role/wfactory.go b/token/services/identity/role/wfactory.go index 5e357b1649..d28cc35387 100644 --- a/token/services/identity/role/wfactory.go +++ b/token/services/identity/role/wfactory.go @@ -22,7 +22,14 @@ import ( type TokenVault interface { UnspentTokensIteratorBy(ctx context.Context, id string, tokenType token.Type) (driver.UnspentTokensIterator, error) ListHistoryIssuedTokens(ctx context.Context) (*token.IssuedTokens, error) +<<<<<<< HEAD Balance(ctx context.Context, id string, tokenType token.Type) (*big.Int, error) +======= + Balance(ctx context.Context, id string, tokenType token.Type) (uint64, error) + IssuedBalance(ctx context.Context) (uint64, error) + ListRedeemedTokens(ctx context.Context) (*token.IssuedTokens, error) + RedeemedBalance(ctx context.Context) (uint64, error) +>>>>>>> effff1e9 (Regenerate mocks and add new methods to TokenVault interface) } //go:generate counterfeiter -o mock/wc.go -fake-name WalletsConfiguration . WalletsConfiguration diff --git a/token/services/tokens/mock/token_store.go b/token/services/tokens/mock/token_store.go index ddd0aac0e5..5e76e5ca69 100644 --- a/token/services/tokens/mock/token_store.go +++ b/token/services/tokens/mock/token_store.go @@ -163,6 +163,19 @@ type FakeTokenStore struct { result1 bool result2 error } + IssuedBalanceStub func(context.Context) (uint64, error) + issuedBalanceMutex sync.RWMutex + issuedBalanceArgsForCall []struct { + arg1 context.Context + } + issuedBalanceReturns struct { + result1 uint64 + result2 error + } + issuedBalanceReturnsOnCall map[int]struct { + result1 uint64 + result2 error + } ListAuditTokensStub func(context.Context, ...*token.ID) ([]*token.Token, error) listAuditTokensMutex sync.RWMutex listAuditTokensArgsForCall []struct { @@ -190,6 +203,19 @@ type FakeTokenStore struct { result1 *token.IssuedTokens result2 error } + ListRedeemedTokensStub func(context.Context) (*token.IssuedTokens, error) + listRedeemedTokensMutex sync.RWMutex + listRedeemedTokensArgsForCall []struct { + arg1 context.Context + } + listRedeemedTokensReturns struct { + result1 *token.IssuedTokens + result2 error + } + listRedeemedTokensReturnsOnCall map[int]struct { + result1 *token.IssuedTokens + result2 error + } ListUnspentTokensStub func(context.Context) (*token.UnspentTokens, error) listUnspentTokensMutex sync.RWMutex listUnspentTokensArgsForCall []struct { @@ -298,6 +324,19 @@ type FakeTokenStore struct { result1 []driver.TokenDetails result2 error } + RedeemedBalanceStub func(context.Context) (uint64, error) + redeemedBalanceMutex sync.RWMutex + redeemedBalanceArgsForCall []struct { + arg1 context.Context + } + redeemedBalanceReturns struct { + result1 uint64 + result2 error + } + redeemedBalanceReturnsOnCall map[int]struct { + result1 uint64 + result2 error + } SetSupportedTokenFormatsStub func([]token.Format) error setSupportedTokenFormatsMutex sync.RWMutex setSupportedTokenFormatsArgsForCall []struct { @@ -1161,6 +1200,70 @@ func (fake *FakeTokenStore) IsMineReturnsOnCall(i int, result1 bool, result2 err }{result1, result2} } +func (fake *FakeTokenStore) IssuedBalance(arg1 context.Context) (uint64, error) { + fake.issuedBalanceMutex.Lock() + ret, specificReturn := fake.issuedBalanceReturnsOnCall[len(fake.issuedBalanceArgsForCall)] + fake.issuedBalanceArgsForCall = append(fake.issuedBalanceArgsForCall, struct { + arg1 context.Context + }{arg1}) + stub := fake.IssuedBalanceStub + fakeReturns := fake.issuedBalanceReturns + fake.recordInvocation("IssuedBalance", []interface{}{arg1}) + fake.issuedBalanceMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeTokenStore) IssuedBalanceCallCount() int { + fake.issuedBalanceMutex.RLock() + defer fake.issuedBalanceMutex.RUnlock() + return len(fake.issuedBalanceArgsForCall) +} + +func (fake *FakeTokenStore) IssuedBalanceCalls(stub func(context.Context) (uint64, error)) { + fake.issuedBalanceMutex.Lock() + defer fake.issuedBalanceMutex.Unlock() + fake.IssuedBalanceStub = stub +} + +func (fake *FakeTokenStore) IssuedBalanceArgsForCall(i int) context.Context { + fake.issuedBalanceMutex.RLock() + defer fake.issuedBalanceMutex.RUnlock() + argsForCall := fake.issuedBalanceArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeTokenStore) IssuedBalanceReturns(result1 uint64, result2 error) { + fake.issuedBalanceMutex.Lock() + defer fake.issuedBalanceMutex.Unlock() + fake.IssuedBalanceStub = nil + fake.issuedBalanceReturns = struct { + result1 uint64 + result2 error + }{result1, result2} +} + +func (fake *FakeTokenStore) IssuedBalanceReturnsOnCall(i int, result1 uint64, result2 error) { + fake.issuedBalanceMutex.Lock() + defer fake.issuedBalanceMutex.Unlock() + fake.IssuedBalanceStub = nil + if fake.issuedBalanceReturnsOnCall == nil { + fake.issuedBalanceReturnsOnCall = make(map[int]struct { + result1 uint64 + result2 error + }) + } + fake.issuedBalanceReturnsOnCall[i] = struct { + result1 uint64 + result2 error + }{result1, result2} +} + func (fake *FakeTokenStore) ListAuditTokens(arg1 context.Context, arg2 ...*token.ID) ([]*token.Token, error) { fake.listAuditTokensMutex.Lock() ret, specificReturn := fake.listAuditTokensReturnsOnCall[len(fake.listAuditTokensArgsForCall)] @@ -1290,6 +1393,70 @@ func (fake *FakeTokenStore) ListHistoryIssuedTokensReturnsOnCall(i int, result1 }{result1, result2} } +func (fake *FakeTokenStore) ListRedeemedTokens(arg1 context.Context) (*token.IssuedTokens, error) { + fake.listRedeemedTokensMutex.Lock() + ret, specificReturn := fake.listRedeemedTokensReturnsOnCall[len(fake.listRedeemedTokensArgsForCall)] + fake.listRedeemedTokensArgsForCall = append(fake.listRedeemedTokensArgsForCall, struct { + arg1 context.Context + }{arg1}) + stub := fake.ListRedeemedTokensStub + fakeReturns := fake.listRedeemedTokensReturns + fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1}) + fake.listRedeemedTokensMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeTokenStore) ListRedeemedTokensCallCount() int { + fake.listRedeemedTokensMutex.RLock() + defer fake.listRedeemedTokensMutex.RUnlock() + return len(fake.listRedeemedTokensArgsForCall) +} + +func (fake *FakeTokenStore) ListRedeemedTokensCalls(stub func(context.Context) (*token.IssuedTokens, error)) { + fake.listRedeemedTokensMutex.Lock() + defer fake.listRedeemedTokensMutex.Unlock() + fake.ListRedeemedTokensStub = stub +} + +func (fake *FakeTokenStore) ListRedeemedTokensArgsForCall(i int) context.Context { + fake.listRedeemedTokensMutex.RLock() + defer fake.listRedeemedTokensMutex.RUnlock() + argsForCall := fake.listRedeemedTokensArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeTokenStore) ListRedeemedTokensReturns(result1 *token.IssuedTokens, result2 error) { + fake.listRedeemedTokensMutex.Lock() + defer fake.listRedeemedTokensMutex.Unlock() + fake.ListRedeemedTokensStub = nil + fake.listRedeemedTokensReturns = struct { + result1 *token.IssuedTokens + result2 error + }{result1, result2} +} + +func (fake *FakeTokenStore) ListRedeemedTokensReturnsOnCall(i int, result1 *token.IssuedTokens, result2 error) { + fake.listRedeemedTokensMutex.Lock() + defer fake.listRedeemedTokensMutex.Unlock() + fake.ListRedeemedTokensStub = nil + if fake.listRedeemedTokensReturnsOnCall == nil { + fake.listRedeemedTokensReturnsOnCall = make(map[int]struct { + result1 *token.IssuedTokens + result2 error + }) + } + fake.listRedeemedTokensReturnsOnCall[i] = struct { + result1 *token.IssuedTokens + result2 error + }{result1, result2} +} + func (fake *FakeTokenStore) ListUnspentTokens(arg1 context.Context) (*token.UnspentTokens, error) { fake.listUnspentTokensMutex.Lock() ret, specificReturn := fake.listUnspentTokensReturnsOnCall[len(fake.listUnspentTokensArgsForCall)] @@ -1797,6 +1964,70 @@ func (fake *FakeTokenStore) QueryTokenDetailsReturnsOnCall(i int, result1 []driv }{result1, result2} } +func (fake *FakeTokenStore) RedeemedBalance(arg1 context.Context) (uint64, error) { + fake.redeemedBalanceMutex.Lock() + ret, specificReturn := fake.redeemedBalanceReturnsOnCall[len(fake.redeemedBalanceArgsForCall)] + fake.redeemedBalanceArgsForCall = append(fake.redeemedBalanceArgsForCall, struct { + arg1 context.Context + }{arg1}) + stub := fake.RedeemedBalanceStub + fakeReturns := fake.redeemedBalanceReturns + fake.recordInvocation("RedeemedBalance", []interface{}{arg1}) + fake.redeemedBalanceMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeTokenStore) RedeemedBalanceCallCount() int { + fake.redeemedBalanceMutex.RLock() + defer fake.redeemedBalanceMutex.RUnlock() + return len(fake.redeemedBalanceArgsForCall) +} + +func (fake *FakeTokenStore) RedeemedBalanceCalls(stub func(context.Context) (uint64, error)) { + fake.redeemedBalanceMutex.Lock() + defer fake.redeemedBalanceMutex.Unlock() + fake.RedeemedBalanceStub = stub +} + +func (fake *FakeTokenStore) RedeemedBalanceArgsForCall(i int) context.Context { + fake.redeemedBalanceMutex.RLock() + defer fake.redeemedBalanceMutex.RUnlock() + argsForCall := fake.redeemedBalanceArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakeTokenStore) RedeemedBalanceReturns(result1 uint64, result2 error) { + fake.redeemedBalanceMutex.Lock() + defer fake.redeemedBalanceMutex.Unlock() + fake.RedeemedBalanceStub = nil + fake.redeemedBalanceReturns = struct { + result1 uint64 + result2 error + }{result1, result2} +} + +func (fake *FakeTokenStore) RedeemedBalanceReturnsOnCall(i int, result1 uint64, result2 error) { + fake.redeemedBalanceMutex.Lock() + defer fake.redeemedBalanceMutex.Unlock() + fake.RedeemedBalanceStub = nil + if fake.redeemedBalanceReturnsOnCall == nil { + fake.redeemedBalanceReturnsOnCall = make(map[int]struct { + result1 uint64 + result2 error + }) + } + fake.redeemedBalanceReturnsOnCall[i] = struct { + result1 uint64 + result2 error + }{result1, result2} +} + func (fake *FakeTokenStore) SetSupportedTokenFormats(arg1 []token.Format) error { var arg1Copy []token.Format if arg1 != nil { From 8ad2ef515dfe225224db5693377ee5dfcc0658fc Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Sat, 2 May 2026 15:44:09 +0530 Subject: [PATCH 09/27] Fix nlreturn lint errors in IssuerWallet methods Signed-off-by: Soumya Mohapatra --- token/services/identity/role/wallets.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/token/services/identity/role/wallets.go b/token/services/identity/role/wallets.go index f786b25e32..79cedd3270 100644 --- a/token/services/identity/role/wallets.go +++ b/token/services/identity/role/wallets.go @@ -224,6 +224,7 @@ func (w *IssuerWallet) HistoryTokens(ctx context.Context, opts *driver.ListToken // this wallet. func (w *IssuerWallet) IssuedBalance(ctx context.Context, opts *driver.ListTokensOptions) (uint64, error) { w.Logger.DebugfContext(ctx, "issuer wallet [%s]: issued balance, type [%s]", w.ID(), opts.TokenType) + return w.TokenVault.IssuedBalance(ctx) } @@ -242,6 +243,7 @@ func (w *IssuerWallet) RedeemedTokens(ctx context.Context, opts *driver.ListToke // issued by this wallet. func (w *IssuerWallet) RedeemedBalance(ctx context.Context, opts *driver.ListTokensOptions) (uint64, error) { w.Logger.DebugfContext(ctx, "issuer wallet [%s]: redeemed balance, type [%s]", w.ID(), opts.TokenType) + return w.TokenVault.RedeemedBalance(ctx) } @@ -257,6 +259,7 @@ func (w *IssuerWallet) filterTokens(ctx context.Context, source *token.IssuedTok } result.Tokens = append(result.Tokens, t) } + return result } From 4dd0de33b0c96f014108a99ec007f898f6ddf353 Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Sat, 2 May 2026 18:32:05 +0530 Subject: [PATCH 10/27] Wire TokenType filter into IssuedBalance, ListRedeemedTokens, RedeemedBalance SQL queries Signed-off-by: Soumya Mohapatra --- token/driver/mock/qe.go | 54 ++++++++++--------- token/driver/vault.go | 9 ++-- token/services/identity/role/mock/itv.go | 54 ++++++++++--------- token/services/identity/role/mock/tv.go | 54 ++++++++++--------- token/services/identity/role/wallets.go | 15 +++--- token/services/identity/role/wfactory.go | 6 +++ token/services/storage/db/driver/token.go | 15 +++--- .../services/storage/db/sql/common/tokens.go | 44 +++++++++------ token/services/tokens/mock/token_store.go | 54 ++++++++++--------- token/vault.go | 12 ++--- 10 files changed, 185 insertions(+), 132 deletions(-) diff --git a/token/driver/mock/qe.go b/token/driver/mock/qe.go index 22ad498e5d..1f16214555 100644 --- a/token/driver/mock/qe.go +++ b/token/driver/mock/qe.go @@ -129,10 +129,11 @@ type QueryEngine struct { result1 bool result2 error } - IssuedBalanceStub func(context.Context) (uint64, error) + IssuedBalanceStub func(context.Context, token.Type) (uint64, error) issuedBalanceMutex sync.RWMutex issuedBalanceArgsForCall []struct { arg1 context.Context + arg2 token.Type } issuedBalanceReturns struct { result1 uint64 @@ -169,10 +170,11 @@ type QueryEngine struct { result1 *token.IssuedTokens result2 error } - ListRedeemedTokensStub func(context.Context) (*token.IssuedTokens, error) + ListRedeemedTokensStub func(context.Context, token.Type) (*token.IssuedTokens, error) listRedeemedTokensMutex sync.RWMutex listRedeemedTokensArgsForCall []struct { arg1 context.Context + arg2 token.Type } listRedeemedTokensReturns struct { result1 *token.IssuedTokens @@ -208,10 +210,11 @@ type QueryEngine struct { result1 []byte result2 error } - RedeemedBalanceStub func(context.Context) (uint64, error) + RedeemedBalanceStub func(context.Context, token.Type) (uint64, error) redeemedBalanceMutex sync.RWMutex redeemedBalanceArgsForCall []struct { arg1 context.Context + arg2 token.Type } redeemedBalanceReturns struct { result1 uint64 @@ -825,18 +828,19 @@ func (fake *QueryEngine) IsPendingReturnsOnCall(i int, result1 bool, result2 err }{result1, result2} } -func (fake *QueryEngine) IssuedBalance(arg1 context.Context) (uint64, error) { +func (fake *QueryEngine) IssuedBalance(arg1 context.Context, arg2 token.Type) (uint64, error) { fake.issuedBalanceMutex.Lock() ret, specificReturn := fake.issuedBalanceReturnsOnCall[len(fake.issuedBalanceArgsForCall)] fake.issuedBalanceArgsForCall = append(fake.issuedBalanceArgsForCall, struct { arg1 context.Context - }{arg1}) + arg2 token.Type + }{arg1, arg2}) stub := fake.IssuedBalanceStub fakeReturns := fake.issuedBalanceReturns - fake.recordInvocation("IssuedBalance", []interface{}{arg1}) + fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2}) fake.issuedBalanceMutex.Unlock() if stub != nil { - return stub(arg1) + return stub(arg1, arg2) } if specificReturn { return ret.result1, ret.result2 @@ -850,17 +854,17 @@ func (fake *QueryEngine) IssuedBalanceCallCount() int { return len(fake.issuedBalanceArgsForCall) } -func (fake *QueryEngine) IssuedBalanceCalls(stub func(context.Context) (uint64, error)) { +func (fake *QueryEngine) IssuedBalanceCalls(stub func(context.Context, token.Type) (uint64, error)) { fake.issuedBalanceMutex.Lock() defer fake.issuedBalanceMutex.Unlock() fake.IssuedBalanceStub = stub } -func (fake *QueryEngine) IssuedBalanceArgsForCall(i int) context.Context { +func (fake *QueryEngine) IssuedBalanceArgsForCall(i int) (context.Context, token.Type) { fake.issuedBalanceMutex.RLock() defer fake.issuedBalanceMutex.RUnlock() argsForCall := fake.issuedBalanceArgsForCall[i] - return argsForCall.arg1 + return argsForCall.arg1, argsForCall.arg2 } func (fake *QueryEngine) IssuedBalanceReturns(result1 uint64, result2 error) { @@ -1018,18 +1022,19 @@ func (fake *QueryEngine) ListHistoryIssuedTokensReturnsOnCall(i int, result1 *to }{result1, result2} } -func (fake *QueryEngine) ListRedeemedTokens(arg1 context.Context) (*token.IssuedTokens, error) { +func (fake *QueryEngine) ListRedeemedTokens(arg1 context.Context, arg2 token.Type) (*token.IssuedTokens, error) { fake.listRedeemedTokensMutex.Lock() ret, specificReturn := fake.listRedeemedTokensReturnsOnCall[len(fake.listRedeemedTokensArgsForCall)] fake.listRedeemedTokensArgsForCall = append(fake.listRedeemedTokensArgsForCall, struct { arg1 context.Context - }{arg1}) + arg2 token.Type + }{arg1, arg2}) stub := fake.ListRedeemedTokensStub fakeReturns := fake.listRedeemedTokensReturns - fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1}) + fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2}) fake.listRedeemedTokensMutex.Unlock() if stub != nil { - return stub(arg1) + return stub(arg1, arg2) } if specificReturn { return ret.result1, ret.result2 @@ -1043,17 +1048,17 @@ func (fake *QueryEngine) ListRedeemedTokensCallCount() int { return len(fake.listRedeemedTokensArgsForCall) } -func (fake *QueryEngine) ListRedeemedTokensCalls(stub func(context.Context) (*token.IssuedTokens, error)) { +func (fake *QueryEngine) ListRedeemedTokensCalls(stub func(context.Context, token.Type) (*token.IssuedTokens, error)) { fake.listRedeemedTokensMutex.Lock() defer fake.listRedeemedTokensMutex.Unlock() fake.ListRedeemedTokensStub = stub } -func (fake *QueryEngine) ListRedeemedTokensArgsForCall(i int) context.Context { +func (fake *QueryEngine) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type) { fake.listRedeemedTokensMutex.RLock() defer fake.listRedeemedTokensMutex.RUnlock() argsForCall := fake.listRedeemedTokensArgsForCall[i] - return argsForCall.arg1 + return argsForCall.arg1, argsForCall.arg2 } func (fake *QueryEngine) ListRedeemedTokensReturns(result1 *token.IssuedTokens, result2 error) { @@ -1210,18 +1215,19 @@ func (fake *QueryEngine) PublicParamsReturnsOnCall(i int, result1 []byte, result }{result1, result2} } -func (fake *QueryEngine) RedeemedBalance(arg1 context.Context) (uint64, error) { +func (fake *QueryEngine) RedeemedBalance(arg1 context.Context, arg2 token.Type) (uint64, error) { fake.redeemedBalanceMutex.Lock() ret, specificReturn := fake.redeemedBalanceReturnsOnCall[len(fake.redeemedBalanceArgsForCall)] fake.redeemedBalanceArgsForCall = append(fake.redeemedBalanceArgsForCall, struct { arg1 context.Context - }{arg1}) + arg2 token.Type + }{arg1, arg2}) stub := fake.RedeemedBalanceStub fakeReturns := fake.redeemedBalanceReturns - fake.recordInvocation("RedeemedBalance", []interface{}{arg1}) + fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2}) fake.redeemedBalanceMutex.Unlock() if stub != nil { - return stub(arg1) + return stub(arg1, arg2) } if specificReturn { return ret.result1, ret.result2 @@ -1235,17 +1241,17 @@ func (fake *QueryEngine) RedeemedBalanceCallCount() int { return len(fake.redeemedBalanceArgsForCall) } -func (fake *QueryEngine) RedeemedBalanceCalls(stub func(context.Context) (uint64, error)) { +func (fake *QueryEngine) RedeemedBalanceCalls(stub func(context.Context, token.Type) (uint64, error)) { fake.redeemedBalanceMutex.Lock() defer fake.redeemedBalanceMutex.Unlock() fake.RedeemedBalanceStub = stub } -func (fake *QueryEngine) RedeemedBalanceArgsForCall(i int) context.Context { +func (fake *QueryEngine) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type) { fake.redeemedBalanceMutex.RLock() defer fake.redeemedBalanceMutex.RUnlock() argsForCall := fake.redeemedBalanceArgsForCall[i] - return argsForCall.arg1 + return argsForCall.arg1, argsForCall.arg2 } func (fake *QueryEngine) RedeemedBalanceReturns(result1 uint64, result2 error) { diff --git a/token/driver/vault.go b/token/driver/vault.go index f6613645b8..aa286c66cd 100644 --- a/token/driver/vault.go +++ b/token/driver/vault.go @@ -88,11 +88,14 @@ type QueryEngine interface { // ListHistoryIssuedTokens returns a list of all tokens issued by the service. ListHistoryIssuedTokens(ctx context.Context) (*token.IssuedTokens, error) // IssuedBalance returns the sum of amounts of non-deleted tokens flagged as issued. - IssuedBalance(ctx context.Context) (uint64, error) + // If tokenType is non-empty, only tokens of that type are included. + IssuedBalance(ctx context.Context, tokenType token.Type) (uint64, error) // ListRedeemedTokens returns issued tokens that were spent by a Redeem action. - ListRedeemedTokens(ctx context.Context) (*token.IssuedTokens, error) + // If tokenType is non-empty, only tokens of that type are included. + ListRedeemedTokens(ctx context.Context, tokenType token.Type) (*token.IssuedTokens, error) // RedeemedBalance returns the sum of amounts of issued tokens spent by a Redeem action. - RedeemedBalance(ctx context.Context) (uint64, error) + // If tokenType is non-empty, only tokens of that type are included. + RedeemedBalance(ctx context.Context, tokenType token.Type) (uint64, error) // PublicParams returns the serialized public parameters used by the driver. PublicParams(ctx context.Context) ([]byte, error) // GetTokenMetadata retrieves the private information (metadata) for the given token IDs. diff --git a/token/services/identity/role/mock/itv.go b/token/services/identity/role/mock/itv.go index 4cb52e9a74..900c2da273 100644 --- a/token/services/identity/role/mock/itv.go +++ b/token/services/identity/role/mock/itv.go @@ -10,10 +10,11 @@ import ( ) type IssuerTokenVault struct { - IssuedBalanceStub func(context.Context) (uint64, error) + IssuedBalanceStub func(context.Context, token.Type) (uint64, error) issuedBalanceMutex sync.RWMutex issuedBalanceArgsForCall []struct { arg1 context.Context + arg2 token.Type } issuedBalanceReturns struct { result1 uint64 @@ -36,10 +37,11 @@ type IssuerTokenVault struct { result1 *token.IssuedTokens result2 error } - ListRedeemedTokensStub func(context.Context) (*token.IssuedTokens, error) + ListRedeemedTokensStub func(context.Context, token.Type) (*token.IssuedTokens, error) listRedeemedTokensMutex sync.RWMutex listRedeemedTokensArgsForCall []struct { arg1 context.Context + arg2 token.Type } listRedeemedTokensReturns struct { result1 *token.IssuedTokens @@ -49,10 +51,11 @@ type IssuerTokenVault struct { result1 *token.IssuedTokens result2 error } - RedeemedBalanceStub func(context.Context) (uint64, error) + RedeemedBalanceStub func(context.Context, token.Type) (uint64, error) redeemedBalanceMutex sync.RWMutex redeemedBalanceArgsForCall []struct { arg1 context.Context + arg2 token.Type } redeemedBalanceReturns struct { result1 uint64 @@ -66,18 +69,19 @@ type IssuerTokenVault struct { invocationsMutex sync.RWMutex } -func (fake *IssuerTokenVault) IssuedBalance(arg1 context.Context) (uint64, error) { +func (fake *IssuerTokenVault) IssuedBalance(arg1 context.Context, arg2 token.Type) (uint64, error) { fake.issuedBalanceMutex.Lock() ret, specificReturn := fake.issuedBalanceReturnsOnCall[len(fake.issuedBalanceArgsForCall)] fake.issuedBalanceArgsForCall = append(fake.issuedBalanceArgsForCall, struct { arg1 context.Context - }{arg1}) + arg2 token.Type + }{arg1, arg2}) stub := fake.IssuedBalanceStub fakeReturns := fake.issuedBalanceReturns - fake.recordInvocation("IssuedBalance", []interface{}{arg1}) + fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2}) fake.issuedBalanceMutex.Unlock() if stub != nil { - return stub(arg1) + return stub(arg1, arg2) } if specificReturn { return ret.result1, ret.result2 @@ -91,17 +95,17 @@ func (fake *IssuerTokenVault) IssuedBalanceCallCount() int { return len(fake.issuedBalanceArgsForCall) } -func (fake *IssuerTokenVault) IssuedBalanceCalls(stub func(context.Context) (uint64, error)) { +func (fake *IssuerTokenVault) IssuedBalanceCalls(stub func(context.Context, token.Type) (uint64, error)) { fake.issuedBalanceMutex.Lock() defer fake.issuedBalanceMutex.Unlock() fake.IssuedBalanceStub = stub } -func (fake *IssuerTokenVault) IssuedBalanceArgsForCall(i int) context.Context { +func (fake *IssuerTokenVault) IssuedBalanceArgsForCall(i int) (context.Context, token.Type) { fake.issuedBalanceMutex.RLock() defer fake.issuedBalanceMutex.RUnlock() argsForCall := fake.issuedBalanceArgsForCall[i] - return argsForCall.arg1 + return argsForCall.arg1, argsForCall.arg2 } func (fake *IssuerTokenVault) IssuedBalanceReturns(result1 uint64, result2 error) { @@ -194,18 +198,19 @@ func (fake *IssuerTokenVault) ListHistoryIssuedTokensReturnsOnCall(i int, result }{result1, result2} } -func (fake *IssuerTokenVault) ListRedeemedTokens(arg1 context.Context) (*token.IssuedTokens, error) { +func (fake *IssuerTokenVault) ListRedeemedTokens(arg1 context.Context, arg2 token.Type) (*token.IssuedTokens, error) { fake.listRedeemedTokensMutex.Lock() ret, specificReturn := fake.listRedeemedTokensReturnsOnCall[len(fake.listRedeemedTokensArgsForCall)] fake.listRedeemedTokensArgsForCall = append(fake.listRedeemedTokensArgsForCall, struct { arg1 context.Context - }{arg1}) + arg2 token.Type + }{arg1, arg2}) stub := fake.ListRedeemedTokensStub fakeReturns := fake.listRedeemedTokensReturns - fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1}) + fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2}) fake.listRedeemedTokensMutex.Unlock() if stub != nil { - return stub(arg1) + return stub(arg1, arg2) } if specificReturn { return ret.result1, ret.result2 @@ -219,17 +224,17 @@ func (fake *IssuerTokenVault) ListRedeemedTokensCallCount() int { return len(fake.listRedeemedTokensArgsForCall) } -func (fake *IssuerTokenVault) ListRedeemedTokensCalls(stub func(context.Context) (*token.IssuedTokens, error)) { +func (fake *IssuerTokenVault) ListRedeemedTokensCalls(stub func(context.Context, token.Type) (*token.IssuedTokens, error)) { fake.listRedeemedTokensMutex.Lock() defer fake.listRedeemedTokensMutex.Unlock() fake.ListRedeemedTokensStub = stub } -func (fake *IssuerTokenVault) ListRedeemedTokensArgsForCall(i int) context.Context { +func (fake *IssuerTokenVault) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type) { fake.listRedeemedTokensMutex.RLock() defer fake.listRedeemedTokensMutex.RUnlock() argsForCall := fake.listRedeemedTokensArgsForCall[i] - return argsForCall.arg1 + return argsForCall.arg1, argsForCall.arg2 } func (fake *IssuerTokenVault) ListRedeemedTokensReturns(result1 *token.IssuedTokens, result2 error) { @@ -258,18 +263,19 @@ func (fake *IssuerTokenVault) ListRedeemedTokensReturnsOnCall(i int, result1 *to }{result1, result2} } -func (fake *IssuerTokenVault) RedeemedBalance(arg1 context.Context) (uint64, error) { +func (fake *IssuerTokenVault) RedeemedBalance(arg1 context.Context, arg2 token.Type) (uint64, error) { fake.redeemedBalanceMutex.Lock() ret, specificReturn := fake.redeemedBalanceReturnsOnCall[len(fake.redeemedBalanceArgsForCall)] fake.redeemedBalanceArgsForCall = append(fake.redeemedBalanceArgsForCall, struct { arg1 context.Context - }{arg1}) + arg2 token.Type + }{arg1, arg2}) stub := fake.RedeemedBalanceStub fakeReturns := fake.redeemedBalanceReturns - fake.recordInvocation("RedeemedBalance", []interface{}{arg1}) + fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2}) fake.redeemedBalanceMutex.Unlock() if stub != nil { - return stub(arg1) + return stub(arg1, arg2) } if specificReturn { return ret.result1, ret.result2 @@ -283,17 +289,17 @@ func (fake *IssuerTokenVault) RedeemedBalanceCallCount() int { return len(fake.redeemedBalanceArgsForCall) } -func (fake *IssuerTokenVault) RedeemedBalanceCalls(stub func(context.Context) (uint64, error)) { +func (fake *IssuerTokenVault) RedeemedBalanceCalls(stub func(context.Context, token.Type) (uint64, error)) { fake.redeemedBalanceMutex.Lock() defer fake.redeemedBalanceMutex.Unlock() fake.RedeemedBalanceStub = stub } -func (fake *IssuerTokenVault) RedeemedBalanceArgsForCall(i int) context.Context { +func (fake *IssuerTokenVault) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type) { fake.redeemedBalanceMutex.RLock() defer fake.redeemedBalanceMutex.RUnlock() argsForCall := fake.redeemedBalanceArgsForCall[i] - return argsForCall.arg1 + return argsForCall.arg1, argsForCall.arg2 } func (fake *IssuerTokenVault) RedeemedBalanceReturns(result1 uint64, result2 error) { diff --git a/token/services/identity/role/mock/tv.go b/token/services/identity/role/mock/tv.go index 6646006264..231fcfda22 100644 --- a/token/services/identity/role/mock/tv.go +++ b/token/services/identity/role/mock/tv.go @@ -27,10 +27,11 @@ type TokenVault struct { result1 *big.Int result2 error } - IssuedBalanceStub func(context.Context) (uint64, error) + IssuedBalanceStub func(context.Context, token.Type) (uint64, error) issuedBalanceMutex sync.RWMutex issuedBalanceArgsForCall []struct { arg1 context.Context + arg2 token.Type } issuedBalanceReturns struct { result1 uint64 @@ -53,10 +54,11 @@ type TokenVault struct { result1 *token.IssuedTokens result2 error } - ListRedeemedTokensStub func(context.Context) (*token.IssuedTokens, error) + ListRedeemedTokensStub func(context.Context, token.Type) (*token.IssuedTokens, error) listRedeemedTokensMutex sync.RWMutex listRedeemedTokensArgsForCall []struct { arg1 context.Context + arg2 token.Type } listRedeemedTokensReturns struct { result1 *token.IssuedTokens @@ -66,10 +68,11 @@ type TokenVault struct { result1 *token.IssuedTokens result2 error } - RedeemedBalanceStub func(context.Context) (uint64, error) + RedeemedBalanceStub func(context.Context, token.Type) (uint64, error) redeemedBalanceMutex sync.RWMutex redeemedBalanceArgsForCall []struct { arg1 context.Context + arg2 token.Type } redeemedBalanceReturns struct { result1 uint64 @@ -164,18 +167,19 @@ func (fake *TokenVault) BalanceReturnsOnCall(i int, result1 *big.Int, result2 er }{result1, result2} } -func (fake *TokenVault) IssuedBalance(arg1 context.Context) (uint64, error) { +func (fake *TokenVault) IssuedBalance(arg1 context.Context, arg2 token.Type) (uint64, error) { fake.issuedBalanceMutex.Lock() ret, specificReturn := fake.issuedBalanceReturnsOnCall[len(fake.issuedBalanceArgsForCall)] fake.issuedBalanceArgsForCall = append(fake.issuedBalanceArgsForCall, struct { arg1 context.Context - }{arg1}) + arg2 token.Type + }{arg1, arg2}) stub := fake.IssuedBalanceStub fakeReturns := fake.issuedBalanceReturns - fake.recordInvocation("IssuedBalance", []interface{}{arg1}) + fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2}) fake.issuedBalanceMutex.Unlock() if stub != nil { - return stub(arg1) + return stub(arg1, arg2) } if specificReturn { return ret.result1, ret.result2 @@ -189,17 +193,17 @@ func (fake *TokenVault) IssuedBalanceCallCount() int { return len(fake.issuedBalanceArgsForCall) } -func (fake *TokenVault) IssuedBalanceCalls(stub func(context.Context) (uint64, error)) { +func (fake *TokenVault) IssuedBalanceCalls(stub func(context.Context, token.Type) (uint64, error)) { fake.issuedBalanceMutex.Lock() defer fake.issuedBalanceMutex.Unlock() fake.IssuedBalanceStub = stub } -func (fake *TokenVault) IssuedBalanceArgsForCall(i int) context.Context { +func (fake *TokenVault) IssuedBalanceArgsForCall(i int) (context.Context, token.Type) { fake.issuedBalanceMutex.RLock() defer fake.issuedBalanceMutex.RUnlock() argsForCall := fake.issuedBalanceArgsForCall[i] - return argsForCall.arg1 + return argsForCall.arg1, argsForCall.arg2 } func (fake *TokenVault) IssuedBalanceReturns(result1 uint64, result2 error) { @@ -292,18 +296,19 @@ func (fake *TokenVault) ListHistoryIssuedTokensReturnsOnCall(i int, result1 *tok }{result1, result2} } -func (fake *TokenVault) ListRedeemedTokens(arg1 context.Context) (*token.IssuedTokens, error) { +func (fake *TokenVault) ListRedeemedTokens(arg1 context.Context, arg2 token.Type) (*token.IssuedTokens, error) { fake.listRedeemedTokensMutex.Lock() ret, specificReturn := fake.listRedeemedTokensReturnsOnCall[len(fake.listRedeemedTokensArgsForCall)] fake.listRedeemedTokensArgsForCall = append(fake.listRedeemedTokensArgsForCall, struct { arg1 context.Context - }{arg1}) + arg2 token.Type + }{arg1, arg2}) stub := fake.ListRedeemedTokensStub fakeReturns := fake.listRedeemedTokensReturns - fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1}) + fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2}) fake.listRedeemedTokensMutex.Unlock() if stub != nil { - return stub(arg1) + return stub(arg1, arg2) } if specificReturn { return ret.result1, ret.result2 @@ -317,17 +322,17 @@ func (fake *TokenVault) ListRedeemedTokensCallCount() int { return len(fake.listRedeemedTokensArgsForCall) } -func (fake *TokenVault) ListRedeemedTokensCalls(stub func(context.Context) (*token.IssuedTokens, error)) { +func (fake *TokenVault) ListRedeemedTokensCalls(stub func(context.Context, token.Type) (*token.IssuedTokens, error)) { fake.listRedeemedTokensMutex.Lock() defer fake.listRedeemedTokensMutex.Unlock() fake.ListRedeemedTokensStub = stub } -func (fake *TokenVault) ListRedeemedTokensArgsForCall(i int) context.Context { +func (fake *TokenVault) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type) { fake.listRedeemedTokensMutex.RLock() defer fake.listRedeemedTokensMutex.RUnlock() argsForCall := fake.listRedeemedTokensArgsForCall[i] - return argsForCall.arg1 + return argsForCall.arg1, argsForCall.arg2 } func (fake *TokenVault) ListRedeemedTokensReturns(result1 *token.IssuedTokens, result2 error) { @@ -356,18 +361,19 @@ func (fake *TokenVault) ListRedeemedTokensReturnsOnCall(i int, result1 *token.Is }{result1, result2} } -func (fake *TokenVault) RedeemedBalance(arg1 context.Context) (uint64, error) { +func (fake *TokenVault) RedeemedBalance(arg1 context.Context, arg2 token.Type) (uint64, error) { fake.redeemedBalanceMutex.Lock() ret, specificReturn := fake.redeemedBalanceReturnsOnCall[len(fake.redeemedBalanceArgsForCall)] fake.redeemedBalanceArgsForCall = append(fake.redeemedBalanceArgsForCall, struct { arg1 context.Context - }{arg1}) + arg2 token.Type + }{arg1, arg2}) stub := fake.RedeemedBalanceStub fakeReturns := fake.redeemedBalanceReturns - fake.recordInvocation("RedeemedBalance", []interface{}{arg1}) + fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2}) fake.redeemedBalanceMutex.Unlock() if stub != nil { - return stub(arg1) + return stub(arg1, arg2) } if specificReturn { return ret.result1, ret.result2 @@ -381,17 +387,17 @@ func (fake *TokenVault) RedeemedBalanceCallCount() int { return len(fake.redeemedBalanceArgsForCall) } -func (fake *TokenVault) RedeemedBalanceCalls(stub func(context.Context) (uint64, error)) { +func (fake *TokenVault) RedeemedBalanceCalls(stub func(context.Context, token.Type) (uint64, error)) { fake.redeemedBalanceMutex.Lock() defer fake.redeemedBalanceMutex.Unlock() fake.RedeemedBalanceStub = stub } -func (fake *TokenVault) RedeemedBalanceArgsForCall(i int) context.Context { +func (fake *TokenVault) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type) { fake.redeemedBalanceMutex.RLock() defer fake.redeemedBalanceMutex.RUnlock() argsForCall := fake.redeemedBalanceArgsForCall[i] - return argsForCall.arg1 + return argsForCall.arg1, argsForCall.arg2 } func (fake *TokenVault) RedeemedBalanceReturns(result1 uint64, result2 error) { diff --git a/token/services/identity/role/wallets.go b/token/services/identity/role/wallets.go index 79cedd3270..138ac7f2ff 100644 --- a/token/services/identity/role/wallets.go +++ b/token/services/identity/role/wallets.go @@ -118,11 +118,14 @@ func (w *AuditorWallet) GetSigner(ctx context.Context, identity Identity) (Signe type IssuerTokenVault interface { ListHistoryIssuedTokens(context.Context) (*token.IssuedTokens, error) // IssuedBalance returns the sum of amounts of non-deleted tokens issued by this node. - IssuedBalance(ctx context.Context) (uint64, error) + // If tokenType is non-empty, only tokens of that type are included. + IssuedBalance(ctx context.Context, tokenType token.Type) (uint64, error) // ListRedeemedTokens returns issued tokens that were redeemed (spent via a Redeem action). - ListRedeemedTokens(ctx context.Context) (*token.IssuedTokens, error) + // If tokenType is non-empty, only tokens of that type are included. + ListRedeemedTokens(ctx context.Context, tokenType token.Type) (*token.IssuedTokens, error) // RedeemedBalance returns the sum of amounts of redeemed tokens issued by this node. - RedeemedBalance(ctx context.Context) (uint64, error) + // If tokenType is non-empty, only tokens of that type are included. + RedeemedBalance(ctx context.Context, tokenType token.Type) (uint64, error) } // IssuerWallet represents a wallet that manages a single issuer identity. @@ -225,13 +228,13 @@ func (w *IssuerWallet) HistoryTokens(ctx context.Context, opts *driver.ListToken func (w *IssuerWallet) IssuedBalance(ctx context.Context, opts *driver.ListTokensOptions) (uint64, error) { w.Logger.DebugfContext(ctx, "issuer wallet [%s]: issued balance, type [%s]", w.ID(), opts.TokenType) - return w.TokenVault.IssuedBalance(ctx) + return w.TokenVault.IssuedBalance(ctx, opts.TokenType) } // RedeemedTokens returns the list of redeemed tokens originally issued by this wallet. func (w *IssuerWallet) RedeemedTokens(ctx context.Context, opts *driver.ListTokensOptions) (*token.IssuedTokens, error) { w.Logger.DebugfContext(ctx, "issuer wallet [%s]: redeemed tokens, type [%s]", w.ID(), opts.TokenType) - source, err := w.TokenVault.ListRedeemedTokens(ctx) + source, err := w.TokenVault.ListRedeemedTokens(ctx, opts.TokenType) if err != nil { return nil, errors.Wrap(err, "redeemed tokens query failed") } @@ -244,7 +247,7 @@ func (w *IssuerWallet) RedeemedTokens(ctx context.Context, opts *driver.ListToke func (w *IssuerWallet) RedeemedBalance(ctx context.Context, opts *driver.ListTokensOptions) (uint64, error) { w.Logger.DebugfContext(ctx, "issuer wallet [%s]: redeemed balance, type [%s]", w.ID(), opts.TokenType) - return w.TokenVault.RedeemedBalance(ctx) + return w.TokenVault.RedeemedBalance(ctx, opts.TokenType) } // filterTokens filters the source tokens by token type and wallet identity. diff --git a/token/services/identity/role/wfactory.go b/token/services/identity/role/wfactory.go index d28cc35387..dcc092d49b 100644 --- a/token/services/identity/role/wfactory.go +++ b/token/services/identity/role/wfactory.go @@ -26,10 +26,16 @@ type TokenVault interface { Balance(ctx context.Context, id string, tokenType token.Type) (*big.Int, error) ======= Balance(ctx context.Context, id string, tokenType token.Type) (uint64, error) +<<<<<<< HEAD IssuedBalance(ctx context.Context) (uint64, error) ListRedeemedTokens(ctx context.Context) (*token.IssuedTokens, error) RedeemedBalance(ctx context.Context) (uint64, error) >>>>>>> effff1e9 (Regenerate mocks and add new methods to TokenVault interface) +======= + IssuedBalance(ctx context.Context, tokenType token.Type) (uint64, error) + ListRedeemedTokens(ctx context.Context, tokenType token.Type) (*token.IssuedTokens, error) + RedeemedBalance(ctx context.Context, tokenType token.Type) (uint64, error) +>>>>>>> 1f0e98c8 (Wire TokenType filter into IssuedBalance, ListRedeemedTokens, RedeemedBalance SQL queries) } //go:generate counterfeiter -o mock/wc.go -fake-name WalletsConfiguration . WalletsConfiguration diff --git a/token/services/storage/db/driver/token.go b/token/services/storage/db/driver/token.go index f362b919b5..b45f0d511e 100644 --- a/token/services/storage/db/driver/token.go +++ b/token/services/storage/db/driver/token.go @@ -202,12 +202,15 @@ type TokenStore interface { ListAuditTokens(ctx context.Context, ids ...*token.ID) ([]*token.Token, error) // ListHistoryIssuedTokens returns the list of all issued tokens ListHistoryIssuedTokens(ctx context.Context) (*token.IssuedTokens, error) - // IssuedBalance returns the sum of amounts of non-deleted tokens flagged as issued - IssuedBalance(ctx context.Context) (uint64, error) - // ListRedeemedTokens returns issued tokens that were spent by a Redeem action - ListRedeemedTokens(ctx context.Context) (*token.IssuedTokens, error) - // RedeemedBalance returns the sum of amounts of issued tokens spent by a Redeem action - RedeemedBalance(ctx context.Context) (uint64, error) + // IssuedBalance returns the sum of amounts of non-deleted tokens flagged as issued. + // If tokenType is non-empty, only tokens of that type are included. + IssuedBalance(ctx context.Context, tokenType token.Type) (uint64, error) + // ListRedeemedTokens returns issued tokens that were spent by a Redeem action. + // If tokenType is non-empty, only tokens of that type are included. + ListRedeemedTokens(ctx context.Context, tokenType token.Type) (*token.IssuedTokens, error) + // RedeemedBalance returns the sum of amounts of issued tokens spent by a Redeem action. + // If tokenType is non-empty, only tokens of that type are included. + RedeemedBalance(ctx context.Context, tokenType token.Type) (uint64, error) // GetTokenOutputs returns the value of the tokens as they appear on the ledger for the passed ids. // For each token, the call-back function is invoked. The call-back function is invoked respecting the order of the passed ids. GetTokenOutputs(ctx context.Context, ids []*token.ID, callback driver.QueryCallbackFunc) error diff --git a/token/services/storage/db/sql/common/tokens.go b/token/services/storage/db/sql/common/tokens.go index 1302326861..6b088efbb0 100644 --- a/token/services/storage/db/sql/common/tokens.go +++ b/token/services/storage/db/sql/common/tokens.go @@ -613,11 +613,16 @@ func (db *TokenStore) ListHistoryIssuedTokens(ctx context.Context) (*token.Issue } // IssuedBalance returns the sum of amounts of non-deleted tokens flagged as issued. -func (db *TokenStore) IssuedBalance(ctx context.Context) (uint64, error) { +// If tokenType is non-empty, only tokens of that type are included. +func (db *TokenStore) IssuedBalance(ctx context.Context, tokenType token.Type) (uint64, error) { + conds := []cond.Condition{cond.Eq("issuer", true), cond.Eq("is_deleted", false)} + if len(tokenType) != 0 { + conds = append(conds, cond.Eq("token_type", tokenType)) + } query, args := q.Select(). FieldsByName("SUM(amount)"). From(q.Table(db.table.Tokens)). - Where(cond.And(cond.Eq("issuer", true), cond.Eq("is_deleted", false))). + Where(cond.And(conds...)). Format(db.ci) logging.Debug(logger, query, args) @@ -631,10 +636,18 @@ func (db *TokenStore) IssuedBalance(ctx context.Context) (uint64, error) { // ListRedeemedTokens returns issued tokens that were spent by a Redeem action. // It JOINs with the transactions table to identify tokens whose spent_by txID -// has action_type = Redeem (2). -func (db *TokenStore) ListRedeemedTokens(ctx context.Context) (*token.IssuedTokens, error) { +// has action_type = Redeem (2). If tokenType is non-empty, only tokens of that type are included. +func (db *TokenStore) ListRedeemedTokens(ctx context.Context, tokenType token.Type) (*token.IssuedTokens, error) { tokTable := q.Table(db.table.Tokens) txTable := q.Table(db.table.Transactions) + conds := []cond.Condition{ + cond.Eq("issuer", true), + cond.Eq("is_deleted", true), + cond.Eq("action_type", int(driver.Redeem)), + } + if len(tokenType) != 0 { + conds = append(conds, cond.Eq("token_type", tokenType)) + } query, args := q.Select(). Fields( tokTable.Field("tx_id"), tokTable.Field("idx"), @@ -643,11 +656,7 @@ func (db *TokenStore) ListRedeemedTokens(ctx context.Context) (*token.IssuedToke tokTable.Field("is_deleted"), tokTable.Field("spent_by"), ). From(tokTable.Join(txTable, cond.Cmp(tokTable.Field("spent_by"), "=", txTable.Field("tx_id")))). - Where(cond.And( - cond.Eq("issuer", true), - cond.Eq("is_deleted", true), - cond.Eq("action_type", int(driver.Redeem)), - )). + Where(cond.And(conds...)). Format(db.ci) logging.Debug(logger, query, args) @@ -668,17 +677,22 @@ func (db *TokenStore) ListRedeemedTokens(ctx context.Context) (*token.IssuedToke } // RedeemedBalance returns the sum of amounts of issued tokens spent by a Redeem action. -func (db *TokenStore) RedeemedBalance(ctx context.Context) (uint64, error) { +// If tokenType is non-empty, only tokens of that type are included. +func (db *TokenStore) RedeemedBalance(ctx context.Context, tokenType token.Type) (uint64, error) { tokTable := q.Table(db.table.Tokens) txTable := q.Table(db.table.Transactions) + conds := []cond.Condition{ + cond.Eq("issuer", true), + cond.Eq("is_deleted", true), + cond.Eq("action_type", int(driver.Redeem)), + } + if len(tokenType) != 0 { + conds = append(conds, cond.Eq("token_type", tokenType)) + } query, args := q.Select(). FieldsByName("SUM(amount)"). From(tokTable.Join(txTable, cond.Cmp(tokTable.Field("spent_by"), "=", txTable.Field("tx_id")))). - Where(cond.And( - cond.Eq("issuer", true), - cond.Eq("is_deleted", true), - cond.Eq("action_type", int(driver.Redeem)), - )). + Where(cond.And(conds...)). Format(db.ci) logging.Debug(logger, query, args) diff --git a/token/services/tokens/mock/token_store.go b/token/services/tokens/mock/token_store.go index 5e76e5ca69..628e161350 100644 --- a/token/services/tokens/mock/token_store.go +++ b/token/services/tokens/mock/token_store.go @@ -163,10 +163,11 @@ type FakeTokenStore struct { result1 bool result2 error } - IssuedBalanceStub func(context.Context) (uint64, error) + IssuedBalanceStub func(context.Context, token.Type) (uint64, error) issuedBalanceMutex sync.RWMutex issuedBalanceArgsForCall []struct { arg1 context.Context + arg2 token.Type } issuedBalanceReturns struct { result1 uint64 @@ -203,10 +204,11 @@ type FakeTokenStore struct { result1 *token.IssuedTokens result2 error } - ListRedeemedTokensStub func(context.Context) (*token.IssuedTokens, error) + ListRedeemedTokensStub func(context.Context, token.Type) (*token.IssuedTokens, error) listRedeemedTokensMutex sync.RWMutex listRedeemedTokensArgsForCall []struct { arg1 context.Context + arg2 token.Type } listRedeemedTokensReturns struct { result1 *token.IssuedTokens @@ -324,10 +326,11 @@ type FakeTokenStore struct { result1 []driver.TokenDetails result2 error } - RedeemedBalanceStub func(context.Context) (uint64, error) + RedeemedBalanceStub func(context.Context, token.Type) (uint64, error) redeemedBalanceMutex sync.RWMutex redeemedBalanceArgsForCall []struct { arg1 context.Context + arg2 token.Type } redeemedBalanceReturns struct { result1 uint64 @@ -1200,18 +1203,19 @@ func (fake *FakeTokenStore) IsMineReturnsOnCall(i int, result1 bool, result2 err }{result1, result2} } -func (fake *FakeTokenStore) IssuedBalance(arg1 context.Context) (uint64, error) { +func (fake *FakeTokenStore) IssuedBalance(arg1 context.Context, arg2 token.Type) (uint64, error) { fake.issuedBalanceMutex.Lock() ret, specificReturn := fake.issuedBalanceReturnsOnCall[len(fake.issuedBalanceArgsForCall)] fake.issuedBalanceArgsForCall = append(fake.issuedBalanceArgsForCall, struct { arg1 context.Context - }{arg1}) + arg2 token.Type + }{arg1, arg2}) stub := fake.IssuedBalanceStub fakeReturns := fake.issuedBalanceReturns - fake.recordInvocation("IssuedBalance", []interface{}{arg1}) + fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2}) fake.issuedBalanceMutex.Unlock() if stub != nil { - return stub(arg1) + return stub(arg1, arg2) } if specificReturn { return ret.result1, ret.result2 @@ -1225,17 +1229,17 @@ func (fake *FakeTokenStore) IssuedBalanceCallCount() int { return len(fake.issuedBalanceArgsForCall) } -func (fake *FakeTokenStore) IssuedBalanceCalls(stub func(context.Context) (uint64, error)) { +func (fake *FakeTokenStore) IssuedBalanceCalls(stub func(context.Context, token.Type) (uint64, error)) { fake.issuedBalanceMutex.Lock() defer fake.issuedBalanceMutex.Unlock() fake.IssuedBalanceStub = stub } -func (fake *FakeTokenStore) IssuedBalanceArgsForCall(i int) context.Context { +func (fake *FakeTokenStore) IssuedBalanceArgsForCall(i int) (context.Context, token.Type) { fake.issuedBalanceMutex.RLock() defer fake.issuedBalanceMutex.RUnlock() argsForCall := fake.issuedBalanceArgsForCall[i] - return argsForCall.arg1 + return argsForCall.arg1, argsForCall.arg2 } func (fake *FakeTokenStore) IssuedBalanceReturns(result1 uint64, result2 error) { @@ -1393,18 +1397,19 @@ func (fake *FakeTokenStore) ListHistoryIssuedTokensReturnsOnCall(i int, result1 }{result1, result2} } -func (fake *FakeTokenStore) ListRedeemedTokens(arg1 context.Context) (*token.IssuedTokens, error) { +func (fake *FakeTokenStore) ListRedeemedTokens(arg1 context.Context, arg2 token.Type) (*token.IssuedTokens, error) { fake.listRedeemedTokensMutex.Lock() ret, specificReturn := fake.listRedeemedTokensReturnsOnCall[len(fake.listRedeemedTokensArgsForCall)] fake.listRedeemedTokensArgsForCall = append(fake.listRedeemedTokensArgsForCall, struct { arg1 context.Context - }{arg1}) + arg2 token.Type + }{arg1, arg2}) stub := fake.ListRedeemedTokensStub fakeReturns := fake.listRedeemedTokensReturns - fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1}) + fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2}) fake.listRedeemedTokensMutex.Unlock() if stub != nil { - return stub(arg1) + return stub(arg1, arg2) } if specificReturn { return ret.result1, ret.result2 @@ -1418,17 +1423,17 @@ func (fake *FakeTokenStore) ListRedeemedTokensCallCount() int { return len(fake.listRedeemedTokensArgsForCall) } -func (fake *FakeTokenStore) ListRedeemedTokensCalls(stub func(context.Context) (*token.IssuedTokens, error)) { +func (fake *FakeTokenStore) ListRedeemedTokensCalls(stub func(context.Context, token.Type) (*token.IssuedTokens, error)) { fake.listRedeemedTokensMutex.Lock() defer fake.listRedeemedTokensMutex.Unlock() fake.ListRedeemedTokensStub = stub } -func (fake *FakeTokenStore) ListRedeemedTokensArgsForCall(i int) context.Context { +func (fake *FakeTokenStore) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type) { fake.listRedeemedTokensMutex.RLock() defer fake.listRedeemedTokensMutex.RUnlock() argsForCall := fake.listRedeemedTokensArgsForCall[i] - return argsForCall.arg1 + return argsForCall.arg1, argsForCall.arg2 } func (fake *FakeTokenStore) ListRedeemedTokensReturns(result1 *token.IssuedTokens, result2 error) { @@ -1964,18 +1969,19 @@ func (fake *FakeTokenStore) QueryTokenDetailsReturnsOnCall(i int, result1 []driv }{result1, result2} } -func (fake *FakeTokenStore) RedeemedBalance(arg1 context.Context) (uint64, error) { +func (fake *FakeTokenStore) RedeemedBalance(arg1 context.Context, arg2 token.Type) (uint64, error) { fake.redeemedBalanceMutex.Lock() ret, specificReturn := fake.redeemedBalanceReturnsOnCall[len(fake.redeemedBalanceArgsForCall)] fake.redeemedBalanceArgsForCall = append(fake.redeemedBalanceArgsForCall, struct { arg1 context.Context - }{arg1}) + arg2 token.Type + }{arg1, arg2}) stub := fake.RedeemedBalanceStub fakeReturns := fake.redeemedBalanceReturns - fake.recordInvocation("RedeemedBalance", []interface{}{arg1}) + fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2}) fake.redeemedBalanceMutex.Unlock() if stub != nil { - return stub(arg1) + return stub(arg1, arg2) } if specificReturn { return ret.result1, ret.result2 @@ -1989,17 +1995,17 @@ func (fake *FakeTokenStore) RedeemedBalanceCallCount() int { return len(fake.redeemedBalanceArgsForCall) } -func (fake *FakeTokenStore) RedeemedBalanceCalls(stub func(context.Context) (uint64, error)) { +func (fake *FakeTokenStore) RedeemedBalanceCalls(stub func(context.Context, token.Type) (uint64, error)) { fake.redeemedBalanceMutex.Lock() defer fake.redeemedBalanceMutex.Unlock() fake.RedeemedBalanceStub = stub } -func (fake *FakeTokenStore) RedeemedBalanceArgsForCall(i int) context.Context { +func (fake *FakeTokenStore) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type) { fake.redeemedBalanceMutex.RLock() defer fake.redeemedBalanceMutex.RUnlock() argsForCall := fake.redeemedBalanceArgsForCall[i] - return argsForCall.arg1 + return argsForCall.arg1, argsForCall.arg2 } func (fake *FakeTokenStore) RedeemedBalanceReturns(result1 uint64, result2 error) { diff --git a/token/vault.go b/token/vault.go index 553fdac533..924f92cab1 100644 --- a/token/vault.go +++ b/token/vault.go @@ -123,16 +123,16 @@ func (q *QueryEngine) ListHistoryIssuedTokens(ctx context.Context) (*token.Issue return q.qe.ListHistoryIssuedTokens(ctx) } -func (q *QueryEngine) IssuedBalance(ctx context.Context) (uint64, error) { - return q.qe.IssuedBalance(ctx) +func (q *QueryEngine) IssuedBalance(ctx context.Context, tokenType token.Type) (uint64, error) { + return q.qe.IssuedBalance(ctx, tokenType) } -func (q *QueryEngine) ListRedeemedTokens(ctx context.Context) (*token.IssuedTokens, error) { - return q.qe.ListRedeemedTokens(ctx) +func (q *QueryEngine) ListRedeemedTokens(ctx context.Context, tokenType token.Type) (*token.IssuedTokens, error) { + return q.qe.ListRedeemedTokens(ctx, tokenType) } -func (q *QueryEngine) RedeemedBalance(ctx context.Context) (uint64, error) { - return q.qe.RedeemedBalance(ctx) +func (q *QueryEngine) RedeemedBalance(ctx context.Context, tokenType token.Type) (uint64, error) { + return q.qe.RedeemedBalance(ctx, tokenType) } // PublicParams returns the public parameters stored in the vault From 1c2d630b9f862ae85c1a0051df34ccb6c0aaef7f Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Sat, 2 May 2026 18:55:00 +0530 Subject: [PATCH 11/27] Add issuer identity filter to IssuedBalance, ListRedeemedTokens, RedeemedBalance Signed-off-by: Soumya Mohapatra --- token/driver/mock/qe.go | 54 ++++++++++--------- token/driver/vault.go | 9 ++-- token/services/identity/role/mock/itv.go | 54 ++++++++++--------- token/services/identity/role/mock/tv.go | 54 ++++++++++--------- token/services/identity/role/wallets.go | 15 +++--- token/services/identity/role/wfactory.go | 6 +++ token/services/storage/db/driver/token.go | 9 ++-- .../services/storage/db/sql/common/tokens.go | 15 ++++-- token/services/tokens/mock/token_store.go | 54 ++++++++++--------- token/vault.go | 12 ++--- 10 files changed, 165 insertions(+), 117 deletions(-) diff --git a/token/driver/mock/qe.go b/token/driver/mock/qe.go index 1f16214555..0e79b49100 100644 --- a/token/driver/mock/qe.go +++ b/token/driver/mock/qe.go @@ -129,11 +129,12 @@ type QueryEngine struct { result1 bool result2 error } - IssuedBalanceStub func(context.Context, token.Type) (uint64, error) + IssuedBalanceStub func(context.Context, token.Type, driver.Identity) (uint64, error) issuedBalanceMutex sync.RWMutex issuedBalanceArgsForCall []struct { arg1 context.Context arg2 token.Type + arg3 driver.Identity } issuedBalanceReturns struct { result1 uint64 @@ -170,11 +171,12 @@ type QueryEngine struct { result1 *token.IssuedTokens result2 error } - ListRedeemedTokensStub func(context.Context, token.Type) (*token.IssuedTokens, error) + ListRedeemedTokensStub func(context.Context, token.Type, driver.Identity) (*token.IssuedTokens, error) listRedeemedTokensMutex sync.RWMutex listRedeemedTokensArgsForCall []struct { arg1 context.Context arg2 token.Type + arg3 driver.Identity } listRedeemedTokensReturns struct { result1 *token.IssuedTokens @@ -210,11 +212,12 @@ type QueryEngine struct { result1 []byte result2 error } - RedeemedBalanceStub func(context.Context, token.Type) (uint64, error) + RedeemedBalanceStub func(context.Context, token.Type, driver.Identity) (uint64, error) redeemedBalanceMutex sync.RWMutex redeemedBalanceArgsForCall []struct { arg1 context.Context arg2 token.Type + arg3 driver.Identity } redeemedBalanceReturns struct { result1 uint64 @@ -828,19 +831,20 @@ func (fake *QueryEngine) IsPendingReturnsOnCall(i int, result1 bool, result2 err }{result1, result2} } -func (fake *QueryEngine) IssuedBalance(arg1 context.Context, arg2 token.Type) (uint64, error) { +func (fake *QueryEngine) IssuedBalance(arg1 context.Context, arg2 token.Type, arg3 driver.Identity) (uint64, error) { fake.issuedBalanceMutex.Lock() ret, specificReturn := fake.issuedBalanceReturnsOnCall[len(fake.issuedBalanceArgsForCall)] fake.issuedBalanceArgsForCall = append(fake.issuedBalanceArgsForCall, struct { arg1 context.Context arg2 token.Type - }{arg1, arg2}) + arg3 driver.Identity + }{arg1, arg2, arg3}) stub := fake.IssuedBalanceStub fakeReturns := fake.issuedBalanceReturns - fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2}) + fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2, arg3}) fake.issuedBalanceMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1, arg2, arg3) } if specificReturn { return ret.result1, ret.result2 @@ -854,17 +858,17 @@ func (fake *QueryEngine) IssuedBalanceCallCount() int { return len(fake.issuedBalanceArgsForCall) } -func (fake *QueryEngine) IssuedBalanceCalls(stub func(context.Context, token.Type) (uint64, error)) { +func (fake *QueryEngine) IssuedBalanceCalls(stub func(context.Context, token.Type, driver.Identity) (uint64, error)) { fake.issuedBalanceMutex.Lock() defer fake.issuedBalanceMutex.Unlock() fake.IssuedBalanceStub = stub } -func (fake *QueryEngine) IssuedBalanceArgsForCall(i int) (context.Context, token.Type) { +func (fake *QueryEngine) IssuedBalanceArgsForCall(i int) (context.Context, token.Type, driver.Identity) { fake.issuedBalanceMutex.RLock() defer fake.issuedBalanceMutex.RUnlock() argsForCall := fake.issuedBalanceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *QueryEngine) IssuedBalanceReturns(result1 uint64, result2 error) { @@ -1022,19 +1026,20 @@ func (fake *QueryEngine) ListHistoryIssuedTokensReturnsOnCall(i int, result1 *to }{result1, result2} } -func (fake *QueryEngine) ListRedeemedTokens(arg1 context.Context, arg2 token.Type) (*token.IssuedTokens, error) { +func (fake *QueryEngine) ListRedeemedTokens(arg1 context.Context, arg2 token.Type, arg3 driver.Identity) (*token.IssuedTokens, error) { fake.listRedeemedTokensMutex.Lock() ret, specificReturn := fake.listRedeemedTokensReturnsOnCall[len(fake.listRedeemedTokensArgsForCall)] fake.listRedeemedTokensArgsForCall = append(fake.listRedeemedTokensArgsForCall, struct { arg1 context.Context arg2 token.Type - }{arg1, arg2}) + arg3 driver.Identity + }{arg1, arg2, arg3}) stub := fake.ListRedeemedTokensStub fakeReturns := fake.listRedeemedTokensReturns - fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2}) + fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2, arg3}) fake.listRedeemedTokensMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1, arg2, arg3) } if specificReturn { return ret.result1, ret.result2 @@ -1048,17 +1053,17 @@ func (fake *QueryEngine) ListRedeemedTokensCallCount() int { return len(fake.listRedeemedTokensArgsForCall) } -func (fake *QueryEngine) ListRedeemedTokensCalls(stub func(context.Context, token.Type) (*token.IssuedTokens, error)) { +func (fake *QueryEngine) ListRedeemedTokensCalls(stub func(context.Context, token.Type, driver.Identity) (*token.IssuedTokens, error)) { fake.listRedeemedTokensMutex.Lock() defer fake.listRedeemedTokensMutex.Unlock() fake.ListRedeemedTokensStub = stub } -func (fake *QueryEngine) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type) { +func (fake *QueryEngine) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type, driver.Identity) { fake.listRedeemedTokensMutex.RLock() defer fake.listRedeemedTokensMutex.RUnlock() argsForCall := fake.listRedeemedTokensArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *QueryEngine) ListRedeemedTokensReturns(result1 *token.IssuedTokens, result2 error) { @@ -1215,19 +1220,20 @@ func (fake *QueryEngine) PublicParamsReturnsOnCall(i int, result1 []byte, result }{result1, result2} } -func (fake *QueryEngine) RedeemedBalance(arg1 context.Context, arg2 token.Type) (uint64, error) { +func (fake *QueryEngine) RedeemedBalance(arg1 context.Context, arg2 token.Type, arg3 driver.Identity) (uint64, error) { fake.redeemedBalanceMutex.Lock() ret, specificReturn := fake.redeemedBalanceReturnsOnCall[len(fake.redeemedBalanceArgsForCall)] fake.redeemedBalanceArgsForCall = append(fake.redeemedBalanceArgsForCall, struct { arg1 context.Context arg2 token.Type - }{arg1, arg2}) + arg3 driver.Identity + }{arg1, arg2, arg3}) stub := fake.RedeemedBalanceStub fakeReturns := fake.redeemedBalanceReturns - fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2}) + fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2, arg3}) fake.redeemedBalanceMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1, arg2, arg3) } if specificReturn { return ret.result1, ret.result2 @@ -1241,17 +1247,17 @@ func (fake *QueryEngine) RedeemedBalanceCallCount() int { return len(fake.redeemedBalanceArgsForCall) } -func (fake *QueryEngine) RedeemedBalanceCalls(stub func(context.Context, token.Type) (uint64, error)) { +func (fake *QueryEngine) RedeemedBalanceCalls(stub func(context.Context, token.Type, driver.Identity) (uint64, error)) { fake.redeemedBalanceMutex.Lock() defer fake.redeemedBalanceMutex.Unlock() fake.RedeemedBalanceStub = stub } -func (fake *QueryEngine) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type) { +func (fake *QueryEngine) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type, driver.Identity) { fake.redeemedBalanceMutex.RLock() defer fake.redeemedBalanceMutex.RUnlock() argsForCall := fake.redeemedBalanceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *QueryEngine) RedeemedBalanceReturns(result1 uint64, result2 error) { diff --git a/token/driver/vault.go b/token/driver/vault.go index aa286c66cd..4687175947 100644 --- a/token/driver/vault.go +++ b/token/driver/vault.go @@ -89,13 +89,16 @@ type QueryEngine interface { ListHistoryIssuedTokens(ctx context.Context) (*token.IssuedTokens, error) // IssuedBalance returns the sum of amounts of non-deleted tokens flagged as issued. // If tokenType is non-empty, only tokens of that type are included. - IssuedBalance(ctx context.Context, tokenType token.Type) (uint64, error) + // If issuerRaw is non-empty, only tokens issued by that identity are included. + IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw Identity) (uint64, error) // ListRedeemedTokens returns issued tokens that were spent by a Redeem action. // If tokenType is non-empty, only tokens of that type are included. - ListRedeemedTokens(ctx context.Context, tokenType token.Type) (*token.IssuedTokens, error) + // If issuerRaw is non-empty, only tokens issued by that identity are included. + ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw Identity) (*token.IssuedTokens, error) // RedeemedBalance returns the sum of amounts of issued tokens spent by a Redeem action. // If tokenType is non-empty, only tokens of that type are included. - RedeemedBalance(ctx context.Context, tokenType token.Type) (uint64, error) + // If issuerRaw is non-empty, only tokens issued by that identity are included. + RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw Identity) (uint64, error) // PublicParams returns the serialized public parameters used by the driver. PublicParams(ctx context.Context) ([]byte, error) // GetTokenMetadata retrieves the private information (metadata) for the given token IDs. diff --git a/token/services/identity/role/mock/itv.go b/token/services/identity/role/mock/itv.go index 900c2da273..a45908eb04 100644 --- a/token/services/identity/role/mock/itv.go +++ b/token/services/identity/role/mock/itv.go @@ -10,11 +10,12 @@ import ( ) type IssuerTokenVault struct { - IssuedBalanceStub func(context.Context, token.Type) (uint64, error) + IssuedBalanceStub func(context.Context, token.Type, role.Identity) (uint64, error) issuedBalanceMutex sync.RWMutex issuedBalanceArgsForCall []struct { arg1 context.Context arg2 token.Type + arg3 role.Identity } issuedBalanceReturns struct { result1 uint64 @@ -37,11 +38,12 @@ type IssuerTokenVault struct { result1 *token.IssuedTokens result2 error } - ListRedeemedTokensStub func(context.Context, token.Type) (*token.IssuedTokens, error) + ListRedeemedTokensStub func(context.Context, token.Type, role.Identity) (*token.IssuedTokens, error) listRedeemedTokensMutex sync.RWMutex listRedeemedTokensArgsForCall []struct { arg1 context.Context arg2 token.Type + arg3 role.Identity } listRedeemedTokensReturns struct { result1 *token.IssuedTokens @@ -51,11 +53,12 @@ type IssuerTokenVault struct { result1 *token.IssuedTokens result2 error } - RedeemedBalanceStub func(context.Context, token.Type) (uint64, error) + RedeemedBalanceStub func(context.Context, token.Type, role.Identity) (uint64, error) redeemedBalanceMutex sync.RWMutex redeemedBalanceArgsForCall []struct { arg1 context.Context arg2 token.Type + arg3 role.Identity } redeemedBalanceReturns struct { result1 uint64 @@ -69,19 +72,20 @@ type IssuerTokenVault struct { invocationsMutex sync.RWMutex } -func (fake *IssuerTokenVault) IssuedBalance(arg1 context.Context, arg2 token.Type) (uint64, error) { +func (fake *IssuerTokenVault) IssuedBalance(arg1 context.Context, arg2 token.Type, arg3 role.Identity) (uint64, error) { fake.issuedBalanceMutex.Lock() ret, specificReturn := fake.issuedBalanceReturnsOnCall[len(fake.issuedBalanceArgsForCall)] fake.issuedBalanceArgsForCall = append(fake.issuedBalanceArgsForCall, struct { arg1 context.Context arg2 token.Type - }{arg1, arg2}) + arg3 role.Identity + }{arg1, arg2, arg3}) stub := fake.IssuedBalanceStub fakeReturns := fake.issuedBalanceReturns - fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2}) + fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2, arg3}) fake.issuedBalanceMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1, arg2, arg3) } if specificReturn { return ret.result1, ret.result2 @@ -95,17 +99,17 @@ func (fake *IssuerTokenVault) IssuedBalanceCallCount() int { return len(fake.issuedBalanceArgsForCall) } -func (fake *IssuerTokenVault) IssuedBalanceCalls(stub func(context.Context, token.Type) (uint64, error)) { +func (fake *IssuerTokenVault) IssuedBalanceCalls(stub func(context.Context, token.Type, role.Identity) (uint64, error)) { fake.issuedBalanceMutex.Lock() defer fake.issuedBalanceMutex.Unlock() fake.IssuedBalanceStub = stub } -func (fake *IssuerTokenVault) IssuedBalanceArgsForCall(i int) (context.Context, token.Type) { +func (fake *IssuerTokenVault) IssuedBalanceArgsForCall(i int) (context.Context, token.Type, role.Identity) { fake.issuedBalanceMutex.RLock() defer fake.issuedBalanceMutex.RUnlock() argsForCall := fake.issuedBalanceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *IssuerTokenVault) IssuedBalanceReturns(result1 uint64, result2 error) { @@ -198,19 +202,20 @@ func (fake *IssuerTokenVault) ListHistoryIssuedTokensReturnsOnCall(i int, result }{result1, result2} } -func (fake *IssuerTokenVault) ListRedeemedTokens(arg1 context.Context, arg2 token.Type) (*token.IssuedTokens, error) { +func (fake *IssuerTokenVault) ListRedeemedTokens(arg1 context.Context, arg2 token.Type, arg3 role.Identity) (*token.IssuedTokens, error) { fake.listRedeemedTokensMutex.Lock() ret, specificReturn := fake.listRedeemedTokensReturnsOnCall[len(fake.listRedeemedTokensArgsForCall)] fake.listRedeemedTokensArgsForCall = append(fake.listRedeemedTokensArgsForCall, struct { arg1 context.Context arg2 token.Type - }{arg1, arg2}) + arg3 role.Identity + }{arg1, arg2, arg3}) stub := fake.ListRedeemedTokensStub fakeReturns := fake.listRedeemedTokensReturns - fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2}) + fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2, arg3}) fake.listRedeemedTokensMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1, arg2, arg3) } if specificReturn { return ret.result1, ret.result2 @@ -224,17 +229,17 @@ func (fake *IssuerTokenVault) ListRedeemedTokensCallCount() int { return len(fake.listRedeemedTokensArgsForCall) } -func (fake *IssuerTokenVault) ListRedeemedTokensCalls(stub func(context.Context, token.Type) (*token.IssuedTokens, error)) { +func (fake *IssuerTokenVault) ListRedeemedTokensCalls(stub func(context.Context, token.Type, role.Identity) (*token.IssuedTokens, error)) { fake.listRedeemedTokensMutex.Lock() defer fake.listRedeemedTokensMutex.Unlock() fake.ListRedeemedTokensStub = stub } -func (fake *IssuerTokenVault) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type) { +func (fake *IssuerTokenVault) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type, role.Identity) { fake.listRedeemedTokensMutex.RLock() defer fake.listRedeemedTokensMutex.RUnlock() argsForCall := fake.listRedeemedTokensArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *IssuerTokenVault) ListRedeemedTokensReturns(result1 *token.IssuedTokens, result2 error) { @@ -263,19 +268,20 @@ func (fake *IssuerTokenVault) ListRedeemedTokensReturnsOnCall(i int, result1 *to }{result1, result2} } -func (fake *IssuerTokenVault) RedeemedBalance(arg1 context.Context, arg2 token.Type) (uint64, error) { +func (fake *IssuerTokenVault) RedeemedBalance(arg1 context.Context, arg2 token.Type, arg3 role.Identity) (uint64, error) { fake.redeemedBalanceMutex.Lock() ret, specificReturn := fake.redeemedBalanceReturnsOnCall[len(fake.redeemedBalanceArgsForCall)] fake.redeemedBalanceArgsForCall = append(fake.redeemedBalanceArgsForCall, struct { arg1 context.Context arg2 token.Type - }{arg1, arg2}) + arg3 role.Identity + }{arg1, arg2, arg3}) stub := fake.RedeemedBalanceStub fakeReturns := fake.redeemedBalanceReturns - fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2}) + fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2, arg3}) fake.redeemedBalanceMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1, arg2, arg3) } if specificReturn { return ret.result1, ret.result2 @@ -289,17 +295,17 @@ func (fake *IssuerTokenVault) RedeemedBalanceCallCount() int { return len(fake.redeemedBalanceArgsForCall) } -func (fake *IssuerTokenVault) RedeemedBalanceCalls(stub func(context.Context, token.Type) (uint64, error)) { +func (fake *IssuerTokenVault) RedeemedBalanceCalls(stub func(context.Context, token.Type, role.Identity) (uint64, error)) { fake.redeemedBalanceMutex.Lock() defer fake.redeemedBalanceMutex.Unlock() fake.RedeemedBalanceStub = stub } -func (fake *IssuerTokenVault) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type) { +func (fake *IssuerTokenVault) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type, role.Identity) { fake.redeemedBalanceMutex.RLock() defer fake.redeemedBalanceMutex.RUnlock() argsForCall := fake.redeemedBalanceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *IssuerTokenVault) RedeemedBalanceReturns(result1 uint64, result2 error) { diff --git a/token/services/identity/role/mock/tv.go b/token/services/identity/role/mock/tv.go index 231fcfda22..27e03c4cf2 100644 --- a/token/services/identity/role/mock/tv.go +++ b/token/services/identity/role/mock/tv.go @@ -27,11 +27,12 @@ type TokenVault struct { result1 *big.Int result2 error } - IssuedBalanceStub func(context.Context, token.Type) (uint64, error) + IssuedBalanceStub func(context.Context, token.Type, driver.Identity) (uint64, error) issuedBalanceMutex sync.RWMutex issuedBalanceArgsForCall []struct { arg1 context.Context arg2 token.Type + arg3 driver.Identity } issuedBalanceReturns struct { result1 uint64 @@ -54,11 +55,12 @@ type TokenVault struct { result1 *token.IssuedTokens result2 error } - ListRedeemedTokensStub func(context.Context, token.Type) (*token.IssuedTokens, error) + ListRedeemedTokensStub func(context.Context, token.Type, driver.Identity) (*token.IssuedTokens, error) listRedeemedTokensMutex sync.RWMutex listRedeemedTokensArgsForCall []struct { arg1 context.Context arg2 token.Type + arg3 driver.Identity } listRedeemedTokensReturns struct { result1 *token.IssuedTokens @@ -68,11 +70,12 @@ type TokenVault struct { result1 *token.IssuedTokens result2 error } - RedeemedBalanceStub func(context.Context, token.Type) (uint64, error) + RedeemedBalanceStub func(context.Context, token.Type, driver.Identity) (uint64, error) redeemedBalanceMutex sync.RWMutex redeemedBalanceArgsForCall []struct { arg1 context.Context arg2 token.Type + arg3 driver.Identity } redeemedBalanceReturns struct { result1 uint64 @@ -167,19 +170,20 @@ func (fake *TokenVault) BalanceReturnsOnCall(i int, result1 *big.Int, result2 er }{result1, result2} } -func (fake *TokenVault) IssuedBalance(arg1 context.Context, arg2 token.Type) (uint64, error) { +func (fake *TokenVault) IssuedBalance(arg1 context.Context, arg2 token.Type, arg3 driver.Identity) (uint64, error) { fake.issuedBalanceMutex.Lock() ret, specificReturn := fake.issuedBalanceReturnsOnCall[len(fake.issuedBalanceArgsForCall)] fake.issuedBalanceArgsForCall = append(fake.issuedBalanceArgsForCall, struct { arg1 context.Context arg2 token.Type - }{arg1, arg2}) + arg3 driver.Identity + }{arg1, arg2, arg3}) stub := fake.IssuedBalanceStub fakeReturns := fake.issuedBalanceReturns - fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2}) + fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2, arg3}) fake.issuedBalanceMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1, arg2, arg3) } if specificReturn { return ret.result1, ret.result2 @@ -193,17 +197,17 @@ func (fake *TokenVault) IssuedBalanceCallCount() int { return len(fake.issuedBalanceArgsForCall) } -func (fake *TokenVault) IssuedBalanceCalls(stub func(context.Context, token.Type) (uint64, error)) { +func (fake *TokenVault) IssuedBalanceCalls(stub func(context.Context, token.Type, driver.Identity) (uint64, error)) { fake.issuedBalanceMutex.Lock() defer fake.issuedBalanceMutex.Unlock() fake.IssuedBalanceStub = stub } -func (fake *TokenVault) IssuedBalanceArgsForCall(i int) (context.Context, token.Type) { +func (fake *TokenVault) IssuedBalanceArgsForCall(i int) (context.Context, token.Type, driver.Identity) { fake.issuedBalanceMutex.RLock() defer fake.issuedBalanceMutex.RUnlock() argsForCall := fake.issuedBalanceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *TokenVault) IssuedBalanceReturns(result1 uint64, result2 error) { @@ -296,19 +300,20 @@ func (fake *TokenVault) ListHistoryIssuedTokensReturnsOnCall(i int, result1 *tok }{result1, result2} } -func (fake *TokenVault) ListRedeemedTokens(arg1 context.Context, arg2 token.Type) (*token.IssuedTokens, error) { +func (fake *TokenVault) ListRedeemedTokens(arg1 context.Context, arg2 token.Type, arg3 driver.Identity) (*token.IssuedTokens, error) { fake.listRedeemedTokensMutex.Lock() ret, specificReturn := fake.listRedeemedTokensReturnsOnCall[len(fake.listRedeemedTokensArgsForCall)] fake.listRedeemedTokensArgsForCall = append(fake.listRedeemedTokensArgsForCall, struct { arg1 context.Context arg2 token.Type - }{arg1, arg2}) + arg3 driver.Identity + }{arg1, arg2, arg3}) stub := fake.ListRedeemedTokensStub fakeReturns := fake.listRedeemedTokensReturns - fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2}) + fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2, arg3}) fake.listRedeemedTokensMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1, arg2, arg3) } if specificReturn { return ret.result1, ret.result2 @@ -322,17 +327,17 @@ func (fake *TokenVault) ListRedeemedTokensCallCount() int { return len(fake.listRedeemedTokensArgsForCall) } -func (fake *TokenVault) ListRedeemedTokensCalls(stub func(context.Context, token.Type) (*token.IssuedTokens, error)) { +func (fake *TokenVault) ListRedeemedTokensCalls(stub func(context.Context, token.Type, driver.Identity) (*token.IssuedTokens, error)) { fake.listRedeemedTokensMutex.Lock() defer fake.listRedeemedTokensMutex.Unlock() fake.ListRedeemedTokensStub = stub } -func (fake *TokenVault) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type) { +func (fake *TokenVault) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type, driver.Identity) { fake.listRedeemedTokensMutex.RLock() defer fake.listRedeemedTokensMutex.RUnlock() argsForCall := fake.listRedeemedTokensArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *TokenVault) ListRedeemedTokensReturns(result1 *token.IssuedTokens, result2 error) { @@ -361,19 +366,20 @@ func (fake *TokenVault) ListRedeemedTokensReturnsOnCall(i int, result1 *token.Is }{result1, result2} } -func (fake *TokenVault) RedeemedBalance(arg1 context.Context, arg2 token.Type) (uint64, error) { +func (fake *TokenVault) RedeemedBalance(arg1 context.Context, arg2 token.Type, arg3 driver.Identity) (uint64, error) { fake.redeemedBalanceMutex.Lock() ret, specificReturn := fake.redeemedBalanceReturnsOnCall[len(fake.redeemedBalanceArgsForCall)] fake.redeemedBalanceArgsForCall = append(fake.redeemedBalanceArgsForCall, struct { arg1 context.Context arg2 token.Type - }{arg1, arg2}) + arg3 driver.Identity + }{arg1, arg2, arg3}) stub := fake.RedeemedBalanceStub fakeReturns := fake.redeemedBalanceReturns - fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2}) + fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2, arg3}) fake.redeemedBalanceMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1, arg2, arg3) } if specificReturn { return ret.result1, ret.result2 @@ -387,17 +393,17 @@ func (fake *TokenVault) RedeemedBalanceCallCount() int { return len(fake.redeemedBalanceArgsForCall) } -func (fake *TokenVault) RedeemedBalanceCalls(stub func(context.Context, token.Type) (uint64, error)) { +func (fake *TokenVault) RedeemedBalanceCalls(stub func(context.Context, token.Type, driver.Identity) (uint64, error)) { fake.redeemedBalanceMutex.Lock() defer fake.redeemedBalanceMutex.Unlock() fake.RedeemedBalanceStub = stub } -func (fake *TokenVault) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type) { +func (fake *TokenVault) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type, driver.Identity) { fake.redeemedBalanceMutex.RLock() defer fake.redeemedBalanceMutex.RUnlock() argsForCall := fake.redeemedBalanceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *TokenVault) RedeemedBalanceReturns(result1 uint64, result2 error) { diff --git a/token/services/identity/role/wallets.go b/token/services/identity/role/wallets.go index 138ac7f2ff..dcea33da5a 100644 --- a/token/services/identity/role/wallets.go +++ b/token/services/identity/role/wallets.go @@ -119,13 +119,16 @@ type IssuerTokenVault interface { ListHistoryIssuedTokens(context.Context) (*token.IssuedTokens, error) // IssuedBalance returns the sum of amounts of non-deleted tokens issued by this node. // If tokenType is non-empty, only tokens of that type are included. - IssuedBalance(ctx context.Context, tokenType token.Type) (uint64, error) + // If issuerRaw is non-empty, only tokens issued by that identity are included. + IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw Identity) (uint64, error) // ListRedeemedTokens returns issued tokens that were redeemed (spent via a Redeem action). // If tokenType is non-empty, only tokens of that type are included. - ListRedeemedTokens(ctx context.Context, tokenType token.Type) (*token.IssuedTokens, error) + // If issuerRaw is non-empty, only tokens issued by that identity are included. + ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw Identity) (*token.IssuedTokens, error) // RedeemedBalance returns the sum of amounts of redeemed tokens issued by this node. // If tokenType is non-empty, only tokens of that type are included. - RedeemedBalance(ctx context.Context, tokenType token.Type) (uint64, error) + // If issuerRaw is non-empty, only tokens issued by that identity are included. + RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw Identity) (uint64, error) } // IssuerWallet represents a wallet that manages a single issuer identity. @@ -228,13 +231,13 @@ func (w *IssuerWallet) HistoryTokens(ctx context.Context, opts *driver.ListToken func (w *IssuerWallet) IssuedBalance(ctx context.Context, opts *driver.ListTokensOptions) (uint64, error) { w.Logger.DebugfContext(ctx, "issuer wallet [%s]: issued balance, type [%s]", w.ID(), opts.TokenType) - return w.TokenVault.IssuedBalance(ctx, opts.TokenType) + return w.TokenVault.IssuedBalance(ctx, opts.TokenType, w.Identity) } // RedeemedTokens returns the list of redeemed tokens originally issued by this wallet. func (w *IssuerWallet) RedeemedTokens(ctx context.Context, opts *driver.ListTokensOptions) (*token.IssuedTokens, error) { w.Logger.DebugfContext(ctx, "issuer wallet [%s]: redeemed tokens, type [%s]", w.ID(), opts.TokenType) - source, err := w.TokenVault.ListRedeemedTokens(ctx, opts.TokenType) + source, err := w.TokenVault.ListRedeemedTokens(ctx, opts.TokenType, w.Identity) if err != nil { return nil, errors.Wrap(err, "redeemed tokens query failed") } @@ -247,7 +250,7 @@ func (w *IssuerWallet) RedeemedTokens(ctx context.Context, opts *driver.ListToke func (w *IssuerWallet) RedeemedBalance(ctx context.Context, opts *driver.ListTokensOptions) (uint64, error) { w.Logger.DebugfContext(ctx, "issuer wallet [%s]: redeemed balance, type [%s]", w.ID(), opts.TokenType) - return w.TokenVault.RedeemedBalance(ctx, opts.TokenType) + return w.TokenVault.RedeemedBalance(ctx, opts.TokenType, w.Identity) } // filterTokens filters the source tokens by token type and wallet identity. diff --git a/token/services/identity/role/wfactory.go b/token/services/identity/role/wfactory.go index dcc092d49b..006b5c9c6c 100644 --- a/token/services/identity/role/wfactory.go +++ b/token/services/identity/role/wfactory.go @@ -26,6 +26,7 @@ type TokenVault interface { Balance(ctx context.Context, id string, tokenType token.Type) (*big.Int, error) ======= Balance(ctx context.Context, id string, tokenType token.Type) (uint64, error) +<<<<<<< HEAD <<<<<<< HEAD IssuedBalance(ctx context.Context) (uint64, error) ListRedeemedTokens(ctx context.Context) (*token.IssuedTokens, error) @@ -36,6 +37,11 @@ type TokenVault interface { ListRedeemedTokens(ctx context.Context, tokenType token.Type) (*token.IssuedTokens, error) RedeemedBalance(ctx context.Context, tokenType token.Type) (uint64, error) >>>>>>> 1f0e98c8 (Wire TokenType filter into IssuedBalance, ListRedeemedTokens, RedeemedBalance SQL queries) +======= + IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity) (uint64, error) + ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity) (*token.IssuedTokens, error) + RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity) (uint64, error) +>>>>>>> d6bffdd0 (Add issuer identity filter to IssuedBalance, ListRedeemedTokens, RedeemedBalance) } //go:generate counterfeiter -o mock/wc.go -fake-name WalletsConfiguration . WalletsConfiguration diff --git a/token/services/storage/db/driver/token.go b/token/services/storage/db/driver/token.go index b45f0d511e..4ebad81b89 100644 --- a/token/services/storage/db/driver/token.go +++ b/token/services/storage/db/driver/token.go @@ -204,13 +204,16 @@ type TokenStore interface { ListHistoryIssuedTokens(ctx context.Context) (*token.IssuedTokens, error) // IssuedBalance returns the sum of amounts of non-deleted tokens flagged as issued. // If tokenType is non-empty, only tokens of that type are included. - IssuedBalance(ctx context.Context, tokenType token.Type) (uint64, error) + // If issuerRaw is non-empty, only tokens issued by that identity are included. + IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity) (uint64, error) // ListRedeemedTokens returns issued tokens that were spent by a Redeem action. // If tokenType is non-empty, only tokens of that type are included. - ListRedeemedTokens(ctx context.Context, tokenType token.Type) (*token.IssuedTokens, error) + // If issuerRaw is non-empty, only tokens issued by that identity are included. + ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity) (*token.IssuedTokens, error) // RedeemedBalance returns the sum of amounts of issued tokens spent by a Redeem action. // If tokenType is non-empty, only tokens of that type are included. - RedeemedBalance(ctx context.Context, tokenType token.Type) (uint64, error) + // If issuerRaw is non-empty, only tokens issued by that identity are included. + RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity) (uint64, error) // GetTokenOutputs returns the value of the tokens as they appear on the ledger for the passed ids. // For each token, the call-back function is invoked. The call-back function is invoked respecting the order of the passed ids. GetTokenOutputs(ctx context.Context, ids []*token.ID, callback driver.QueryCallbackFunc) error diff --git a/token/services/storage/db/sql/common/tokens.go b/token/services/storage/db/sql/common/tokens.go index 6b088efbb0..d289484ba3 100644 --- a/token/services/storage/db/sql/common/tokens.go +++ b/token/services/storage/db/sql/common/tokens.go @@ -614,11 +614,14 @@ func (db *TokenStore) ListHistoryIssuedTokens(ctx context.Context) (*token.Issue // IssuedBalance returns the sum of amounts of non-deleted tokens flagged as issued. // If tokenType is non-empty, only tokens of that type are included. -func (db *TokenStore) IssuedBalance(ctx context.Context, tokenType token.Type) (uint64, error) { +func (db *TokenStore) IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw tdriver.Identity) (uint64, error) { conds := []cond.Condition{cond.Eq("issuer", true), cond.Eq("is_deleted", false)} if len(tokenType) != 0 { conds = append(conds, cond.Eq("token_type", tokenType)) } + if len(issuerRaw) != 0 { + conds = append(conds, cond.Eq("issuer_raw", issuerRaw)) + } query, args := q.Select(). FieldsByName("SUM(amount)"). From(q.Table(db.table.Tokens)). @@ -637,7 +640,7 @@ func (db *TokenStore) IssuedBalance(ctx context.Context, tokenType token.Type) ( // ListRedeemedTokens returns issued tokens that were spent by a Redeem action. // It JOINs with the transactions table to identify tokens whose spent_by txID // has action_type = Redeem (2). If tokenType is non-empty, only tokens of that type are included. -func (db *TokenStore) ListRedeemedTokens(ctx context.Context, tokenType token.Type) (*token.IssuedTokens, error) { +func (db *TokenStore) ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw tdriver.Identity) (*token.IssuedTokens, error) { tokTable := q.Table(db.table.Tokens) txTable := q.Table(db.table.Transactions) conds := []cond.Condition{ @@ -648,6 +651,9 @@ func (db *TokenStore) ListRedeemedTokens(ctx context.Context, tokenType token.Ty if len(tokenType) != 0 { conds = append(conds, cond.Eq("token_type", tokenType)) } + if len(issuerRaw) != 0 { + conds = append(conds, cond.Eq("issuer_raw", issuerRaw)) + } query, args := q.Select(). Fields( tokTable.Field("tx_id"), tokTable.Field("idx"), @@ -678,7 +684,7 @@ func (db *TokenStore) ListRedeemedTokens(ctx context.Context, tokenType token.Ty // RedeemedBalance returns the sum of amounts of issued tokens spent by a Redeem action. // If tokenType is non-empty, only tokens of that type are included. -func (db *TokenStore) RedeemedBalance(ctx context.Context, tokenType token.Type) (uint64, error) { +func (db *TokenStore) RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw tdriver.Identity) (uint64, error) { tokTable := q.Table(db.table.Tokens) txTable := q.Table(db.table.Transactions) conds := []cond.Condition{ @@ -689,6 +695,9 @@ func (db *TokenStore) RedeemedBalance(ctx context.Context, tokenType token.Type) if len(tokenType) != 0 { conds = append(conds, cond.Eq("token_type", tokenType)) } + if len(issuerRaw) != 0 { + conds = append(conds, cond.Eq("issuer_raw", issuerRaw)) + } query, args := q.Select(). FieldsByName("SUM(amount)"). From(tokTable.Join(txTable, cond.Cmp(tokTable.Field("spent_by"), "=", txTable.Field("tx_id")))). diff --git a/token/services/tokens/mock/token_store.go b/token/services/tokens/mock/token_store.go index 628e161350..bd1241c5c4 100644 --- a/token/services/tokens/mock/token_store.go +++ b/token/services/tokens/mock/token_store.go @@ -163,11 +163,12 @@ type FakeTokenStore struct { result1 bool result2 error } - IssuedBalanceStub func(context.Context, token.Type) (uint64, error) + IssuedBalanceStub func(context.Context, token.Type, drivera.Identity) (uint64, error) issuedBalanceMutex sync.RWMutex issuedBalanceArgsForCall []struct { arg1 context.Context arg2 token.Type + arg3 drivera.Identity } issuedBalanceReturns struct { result1 uint64 @@ -204,11 +205,12 @@ type FakeTokenStore struct { result1 *token.IssuedTokens result2 error } - ListRedeemedTokensStub func(context.Context, token.Type) (*token.IssuedTokens, error) + ListRedeemedTokensStub func(context.Context, token.Type, drivera.Identity) (*token.IssuedTokens, error) listRedeemedTokensMutex sync.RWMutex listRedeemedTokensArgsForCall []struct { arg1 context.Context arg2 token.Type + arg3 drivera.Identity } listRedeemedTokensReturns struct { result1 *token.IssuedTokens @@ -326,11 +328,12 @@ type FakeTokenStore struct { result1 []driver.TokenDetails result2 error } - RedeemedBalanceStub func(context.Context, token.Type) (uint64, error) + RedeemedBalanceStub func(context.Context, token.Type, drivera.Identity) (uint64, error) redeemedBalanceMutex sync.RWMutex redeemedBalanceArgsForCall []struct { arg1 context.Context arg2 token.Type + arg3 drivera.Identity } redeemedBalanceReturns struct { result1 uint64 @@ -1203,19 +1206,20 @@ func (fake *FakeTokenStore) IsMineReturnsOnCall(i int, result1 bool, result2 err }{result1, result2} } -func (fake *FakeTokenStore) IssuedBalance(arg1 context.Context, arg2 token.Type) (uint64, error) { +func (fake *FakeTokenStore) IssuedBalance(arg1 context.Context, arg2 token.Type, arg3 drivera.Identity) (uint64, error) { fake.issuedBalanceMutex.Lock() ret, specificReturn := fake.issuedBalanceReturnsOnCall[len(fake.issuedBalanceArgsForCall)] fake.issuedBalanceArgsForCall = append(fake.issuedBalanceArgsForCall, struct { arg1 context.Context arg2 token.Type - }{arg1, arg2}) + arg3 drivera.Identity + }{arg1, arg2, arg3}) stub := fake.IssuedBalanceStub fakeReturns := fake.issuedBalanceReturns - fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2}) + fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2, arg3}) fake.issuedBalanceMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1, arg2, arg3) } if specificReturn { return ret.result1, ret.result2 @@ -1229,17 +1233,17 @@ func (fake *FakeTokenStore) IssuedBalanceCallCount() int { return len(fake.issuedBalanceArgsForCall) } -func (fake *FakeTokenStore) IssuedBalanceCalls(stub func(context.Context, token.Type) (uint64, error)) { +func (fake *FakeTokenStore) IssuedBalanceCalls(stub func(context.Context, token.Type, drivera.Identity) (uint64, error)) { fake.issuedBalanceMutex.Lock() defer fake.issuedBalanceMutex.Unlock() fake.IssuedBalanceStub = stub } -func (fake *FakeTokenStore) IssuedBalanceArgsForCall(i int) (context.Context, token.Type) { +func (fake *FakeTokenStore) IssuedBalanceArgsForCall(i int) (context.Context, token.Type, drivera.Identity) { fake.issuedBalanceMutex.RLock() defer fake.issuedBalanceMutex.RUnlock() argsForCall := fake.issuedBalanceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *FakeTokenStore) IssuedBalanceReturns(result1 uint64, result2 error) { @@ -1397,19 +1401,20 @@ func (fake *FakeTokenStore) ListHistoryIssuedTokensReturnsOnCall(i int, result1 }{result1, result2} } -func (fake *FakeTokenStore) ListRedeemedTokens(arg1 context.Context, arg2 token.Type) (*token.IssuedTokens, error) { +func (fake *FakeTokenStore) ListRedeemedTokens(arg1 context.Context, arg2 token.Type, arg3 drivera.Identity) (*token.IssuedTokens, error) { fake.listRedeemedTokensMutex.Lock() ret, specificReturn := fake.listRedeemedTokensReturnsOnCall[len(fake.listRedeemedTokensArgsForCall)] fake.listRedeemedTokensArgsForCall = append(fake.listRedeemedTokensArgsForCall, struct { arg1 context.Context arg2 token.Type - }{arg1, arg2}) + arg3 drivera.Identity + }{arg1, arg2, arg3}) stub := fake.ListRedeemedTokensStub fakeReturns := fake.listRedeemedTokensReturns - fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2}) + fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2, arg3}) fake.listRedeemedTokensMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1, arg2, arg3) } if specificReturn { return ret.result1, ret.result2 @@ -1423,17 +1428,17 @@ func (fake *FakeTokenStore) ListRedeemedTokensCallCount() int { return len(fake.listRedeemedTokensArgsForCall) } -func (fake *FakeTokenStore) ListRedeemedTokensCalls(stub func(context.Context, token.Type) (*token.IssuedTokens, error)) { +func (fake *FakeTokenStore) ListRedeemedTokensCalls(stub func(context.Context, token.Type, drivera.Identity) (*token.IssuedTokens, error)) { fake.listRedeemedTokensMutex.Lock() defer fake.listRedeemedTokensMutex.Unlock() fake.ListRedeemedTokensStub = stub } -func (fake *FakeTokenStore) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type) { +func (fake *FakeTokenStore) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type, drivera.Identity) { fake.listRedeemedTokensMutex.RLock() defer fake.listRedeemedTokensMutex.RUnlock() argsForCall := fake.listRedeemedTokensArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *FakeTokenStore) ListRedeemedTokensReturns(result1 *token.IssuedTokens, result2 error) { @@ -1969,19 +1974,20 @@ func (fake *FakeTokenStore) QueryTokenDetailsReturnsOnCall(i int, result1 []driv }{result1, result2} } -func (fake *FakeTokenStore) RedeemedBalance(arg1 context.Context, arg2 token.Type) (uint64, error) { +func (fake *FakeTokenStore) RedeemedBalance(arg1 context.Context, arg2 token.Type, arg3 drivera.Identity) (uint64, error) { fake.redeemedBalanceMutex.Lock() ret, specificReturn := fake.redeemedBalanceReturnsOnCall[len(fake.redeemedBalanceArgsForCall)] fake.redeemedBalanceArgsForCall = append(fake.redeemedBalanceArgsForCall, struct { arg1 context.Context arg2 token.Type - }{arg1, arg2}) + arg3 drivera.Identity + }{arg1, arg2, arg3}) stub := fake.RedeemedBalanceStub fakeReturns := fake.redeemedBalanceReturns - fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2}) + fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2, arg3}) fake.redeemedBalanceMutex.Unlock() if stub != nil { - return stub(arg1, arg2) + return stub(arg1, arg2, arg3) } if specificReturn { return ret.result1, ret.result2 @@ -1995,17 +2001,17 @@ func (fake *FakeTokenStore) RedeemedBalanceCallCount() int { return len(fake.redeemedBalanceArgsForCall) } -func (fake *FakeTokenStore) RedeemedBalanceCalls(stub func(context.Context, token.Type) (uint64, error)) { +func (fake *FakeTokenStore) RedeemedBalanceCalls(stub func(context.Context, token.Type, drivera.Identity) (uint64, error)) { fake.redeemedBalanceMutex.Lock() defer fake.redeemedBalanceMutex.Unlock() fake.RedeemedBalanceStub = stub } -func (fake *FakeTokenStore) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type) { +func (fake *FakeTokenStore) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type, drivera.Identity) { fake.redeemedBalanceMutex.RLock() defer fake.redeemedBalanceMutex.RUnlock() argsForCall := fake.redeemedBalanceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 } func (fake *FakeTokenStore) RedeemedBalanceReturns(result1 uint64, result2 error) { diff --git a/token/vault.go b/token/vault.go index 924f92cab1..0402b16d0e 100644 --- a/token/vault.go +++ b/token/vault.go @@ -123,16 +123,16 @@ func (q *QueryEngine) ListHistoryIssuedTokens(ctx context.Context) (*token.Issue return q.qe.ListHistoryIssuedTokens(ctx) } -func (q *QueryEngine) IssuedBalance(ctx context.Context, tokenType token.Type) (uint64, error) { - return q.qe.IssuedBalance(ctx, tokenType) +func (q *QueryEngine) IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity) (uint64, error) { + return q.qe.IssuedBalance(ctx, tokenType, issuerRaw) } -func (q *QueryEngine) ListRedeemedTokens(ctx context.Context, tokenType token.Type) (*token.IssuedTokens, error) { - return q.qe.ListRedeemedTokens(ctx, tokenType) +func (q *QueryEngine) ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity) (*token.IssuedTokens, error) { + return q.qe.ListRedeemedTokens(ctx, tokenType, issuerRaw) } -func (q *QueryEngine) RedeemedBalance(ctx context.Context, tokenType token.Type) (uint64, error) { - return q.qe.RedeemedBalance(ctx, tokenType) +func (q *QueryEngine) RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity) (uint64, error) { + return q.qe.RedeemedBalance(ctx, tokenType, issuerRaw) } // PublicParams returns the public parameters stored in the vault From 7dcc7b07c0e8bf028fd83ef462b45c98c753a917 Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Sat, 2 May 2026 20:12:21 +0530 Subject: [PATCH 12/27] Add time-range filtering, ORDER BY, OutstandingBalance, and unit tests - Issue #3: Thread From/To *time.Time params through all 6 layers for IssuedBalance, ListRedeemedTokens, and RedeemedBalance. SQL uses cond.Gte/cond.Lte on stored_at column. - Issue #4: Thread SortBy/SortDirection to ListRedeemedTokens SQL. SortByQuantity maps to ORDER BY amount ASC/DESC. - Issue #5: Add unit tests for IssuedBalance, RedeemedTokens, RedeemedBalance, and OutstandingBalance (happy path, error propagation, edge cases). - Issue #6: Add OutstandingBalance method to IssuerWallet (IssuedBalance minus RedeemedBalance) with overflow guard. Signed-off-by: Soumya Mohapatra --- integration/token/dvp/views/balance.go | 2 +- integration/token/fungible/views/accept.go | 2 +- integration/token/fungible/views/balance.go | 2 +- integration/token/interop/views/balance.go | 2 +- token/driver/mock/iw.go | 79 ++++++++++++++++ token/driver/mock/qe.go | 65 ++++++++----- token/driver/vault.go | 12 +-- token/driver/wallet.go | 33 +++++-- token/services/identity/role/mock/itv.go | 66 ++++++++----- token/services/identity/role/mock/tv.go | 65 ++++++++----- token/services/identity/role/wallets.go | 44 +++++++-- token/services/identity/role/wallets_test.go | 88 +++++++++++++++++ token/services/identity/role/wfactory.go | 23 +---- token/services/storage/db/driver/token.go | 8 +- .../services/storage/db/sql/common/tokens.go | 88 +++++++++-------- token/services/tokens/mock/token_store.go | 65 ++++++++----- token/vault.go | 12 +-- token/wallet.go | 94 +++++++++++++------ 18 files changed, 530 insertions(+), 220 deletions(-) diff --git a/integration/token/dvp/views/balance.go b/integration/token/dvp/views/balance.go index f2c924d74b..a1e088efd8 100644 --- a/integration/token/dvp/views/balance.go +++ b/integration/token/dvp/views/balance.go @@ -39,7 +39,7 @@ func (b *BalanceView) Call(context view.Context) (any, error) { return nil, fmt.Errorf("wallet %s not found: %w", b.Wallet, err) } - balance, err := wallet.Balance(context.Context(), token.WithType(b.Type)) + balance, err := wallet.Balance(context.Context(), token.WithBalanceTokenType(b.Type)) if err != nil { return nil, err } diff --git a/integration/token/fungible/views/accept.go b/integration/token/fungible/views/accept.go index 6a70ec862d..df89ab35a5 100644 --- a/integration/token/fungible/views/accept.go +++ b/integration/token/fungible/views/accept.go @@ -44,7 +44,7 @@ func (a *AcceptCashView) Call(context view.Context) (any, error) { if output.Type == "MAX" { continue } - balance, err := ttx.MyWallet(context, token.WithTMSID(tx.TMSID())).Balance(context.Context(), ttx.WithType(output.Type)) + balance, err := ttx.MyWallet(context, token.WithTMSID(tx.TMSID())).Balance(context.Context(), token.WithBalanceTokenType(output.Type)) assert.NoError(err, "failed retrieving balance for type [%s]", output.Type) assert.True(balance.Cmp(big.NewInt(3000)) <= 0, "cannot have more than 3000 unspent quantity for type [%s]", output.Type) } diff --git a/integration/token/fungible/views/balance.go b/integration/token/fungible/views/balance.go index 6579a6840d..8d1a7e9e4c 100644 --- a/integration/token/fungible/views/balance.go +++ b/integration/token/fungible/views/balance.go @@ -63,7 +63,7 @@ func (b *BalanceView) Call(context view.Context) (any, error) { assert.NoError(err, "failed to compute the sum of the co-owned tokens") if !b.SkipCheck { - balance, err := wallet.Balance(context.Context(), token.WithType(b.Type)) + balance, err := wallet.Balance(context.Context(), token.WithBalanceTokenType(b.Type)) if err != nil { return nil, err } diff --git a/integration/token/interop/views/balance.go b/integration/token/interop/views/balance.go index 006a9c0f39..7184e2b429 100644 --- a/integration/token/interop/views/balance.go +++ b/integration/token/interop/views/balance.go @@ -47,7 +47,7 @@ func (b *BalanceView) Call(context view.Context) (any, error) { precision := tms.PublicParametersManager().PublicParameters().Precision() // owned - balance, err := wallet.Balance(context.Context(), token.WithType(b.Type)) + balance, err := wallet.Balance(context.Context(), token.WithBalanceTokenType(b.Type)) assert.NoError(err, "failed to get unspent tokens") htlcWallet := htlc.Wallet(context, wallet) diff --git a/token/driver/mock/iw.go b/token/driver/mock/iw.go index dbf9dc2faa..a6eb40a3c2 100644 --- a/token/driver/mock/iw.go +++ b/token/driver/mock/iw.go @@ -99,6 +99,20 @@ type IssuerWallet struct { result1 uint64 result2 error } + OutstandingBalanceStub func(context.Context, *driver.ListTokensOptions) (uint64, error) + outstandingBalanceMutex sync.RWMutex + outstandingBalanceArgsForCall []struct { + arg1 context.Context + arg2 *driver.ListTokensOptions + } + outstandingBalanceReturns struct { + result1 uint64 + result2 error + } + outstandingBalanceReturnsOnCall map[int]struct { + result1 uint64 + result2 error + } RedeemedBalanceStub func(context.Context, *driver.ListTokensOptions) (uint64, error) redeemedBalanceMutex sync.RWMutex redeemedBalanceArgsForCall []struct { @@ -567,6 +581,71 @@ func (fake *IssuerWallet) IssuedBalanceReturnsOnCall(i int, result1 uint64, resu }{result1, result2} } +func (fake *IssuerWallet) OutstandingBalance(arg1 context.Context, arg2 *driver.ListTokensOptions) (uint64, error) { + fake.outstandingBalanceMutex.Lock() + ret, specificReturn := fake.outstandingBalanceReturnsOnCall[len(fake.outstandingBalanceArgsForCall)] + fake.outstandingBalanceArgsForCall = append(fake.outstandingBalanceArgsForCall, struct { + arg1 context.Context + arg2 *driver.ListTokensOptions + }{arg1, arg2}) + stub := fake.OutstandingBalanceStub + fakeReturns := fake.outstandingBalanceReturns + fake.recordInvocation("OutstandingBalance", []interface{}{arg1, arg2}) + fake.outstandingBalanceMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *IssuerWallet) OutstandingBalanceCallCount() int { + fake.outstandingBalanceMutex.RLock() + defer fake.outstandingBalanceMutex.RUnlock() + return len(fake.outstandingBalanceArgsForCall) +} + +func (fake *IssuerWallet) OutstandingBalanceCalls(stub func(context.Context, *driver.ListTokensOptions) (uint64, error)) { + fake.outstandingBalanceMutex.Lock() + defer fake.outstandingBalanceMutex.Unlock() + fake.OutstandingBalanceStub = stub +} + +func (fake *IssuerWallet) OutstandingBalanceArgsForCall(i int) (context.Context, *driver.ListTokensOptions) { + fake.outstandingBalanceMutex.RLock() + defer fake.outstandingBalanceMutex.RUnlock() + argsForCall := fake.outstandingBalanceArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *IssuerWallet) OutstandingBalanceReturns(result1 uint64, result2 error) { + fake.outstandingBalanceMutex.Lock() + defer fake.outstandingBalanceMutex.Unlock() + fake.OutstandingBalanceStub = nil + fake.outstandingBalanceReturns = struct { + result1 uint64 + result2 error + }{result1, result2} +} + +func (fake *IssuerWallet) OutstandingBalanceReturnsOnCall(i int, result1 uint64, result2 error) { + fake.outstandingBalanceMutex.Lock() + defer fake.outstandingBalanceMutex.Unlock() + fake.OutstandingBalanceStub = nil + if fake.outstandingBalanceReturnsOnCall == nil { + fake.outstandingBalanceReturnsOnCall = make(map[int]struct { + result1 uint64 + result2 error + }) + } + fake.outstandingBalanceReturnsOnCall[i] = struct { + result1 uint64 + result2 error + }{result1, result2} +} + func (fake *IssuerWallet) RedeemedBalance(arg1 context.Context, arg2 *driver.ListTokensOptions) (uint64, error) { fake.redeemedBalanceMutex.Lock() ret, specificReturn := fake.redeemedBalanceReturnsOnCall[len(fake.redeemedBalanceArgsForCall)] diff --git a/token/driver/mock/qe.go b/token/driver/mock/qe.go index 0e79b49100..ae64f83baf 100644 --- a/token/driver/mock/qe.go +++ b/token/driver/mock/qe.go @@ -5,6 +5,7 @@ import ( "context" "math/big" "sync" + "time" "github.com/hyperledger-labs/fabric-token-sdk/token/driver" "github.com/hyperledger-labs/fabric-token-sdk/token/token" @@ -129,12 +130,14 @@ type QueryEngine struct { result1 bool result2 error } - IssuedBalanceStub func(context.Context, token.Type, driver.Identity) (uint64, error) + IssuedBalanceStub func(context.Context, token.Type, driver.Identity, *time.Time, *time.Time) (uint64, error) issuedBalanceMutex sync.RWMutex issuedBalanceArgsForCall []struct { arg1 context.Context arg2 token.Type arg3 driver.Identity + arg4 *time.Time + arg5 *time.Time } issuedBalanceReturns struct { result1 uint64 @@ -171,12 +174,16 @@ type QueryEngine struct { result1 *token.IssuedTokens result2 error } - ListRedeemedTokensStub func(context.Context, token.Type, driver.Identity) (*token.IssuedTokens, error) + ListRedeemedTokensStub func(context.Context, token.Type, driver.Identity, *time.Time, *time.Time, driver.SortField, driver.SortDirection) (*token.IssuedTokens, error) listRedeemedTokensMutex sync.RWMutex listRedeemedTokensArgsForCall []struct { arg1 context.Context arg2 token.Type arg3 driver.Identity + arg4 *time.Time + arg5 *time.Time + arg6 driver.SortField + arg7 driver.SortDirection } listRedeemedTokensReturns struct { result1 *token.IssuedTokens @@ -212,12 +219,14 @@ type QueryEngine struct { result1 []byte result2 error } - RedeemedBalanceStub func(context.Context, token.Type, driver.Identity) (uint64, error) + RedeemedBalanceStub func(context.Context, token.Type, driver.Identity, *time.Time, *time.Time) (uint64, error) redeemedBalanceMutex sync.RWMutex redeemedBalanceArgsForCall []struct { arg1 context.Context arg2 token.Type arg3 driver.Identity + arg4 *time.Time + arg5 *time.Time } redeemedBalanceReturns struct { result1 uint64 @@ -831,20 +840,22 @@ func (fake *QueryEngine) IsPendingReturnsOnCall(i int, result1 bool, result2 err }{result1, result2} } -func (fake *QueryEngine) IssuedBalance(arg1 context.Context, arg2 token.Type, arg3 driver.Identity) (uint64, error) { +func (fake *QueryEngine) IssuedBalance(arg1 context.Context, arg2 token.Type, arg3 driver.Identity, arg4 *time.Time, arg5 *time.Time) (uint64, error) { fake.issuedBalanceMutex.Lock() ret, specificReturn := fake.issuedBalanceReturnsOnCall[len(fake.issuedBalanceArgsForCall)] fake.issuedBalanceArgsForCall = append(fake.issuedBalanceArgsForCall, struct { arg1 context.Context arg2 token.Type arg3 driver.Identity - }{arg1, arg2, arg3}) + arg4 *time.Time + arg5 *time.Time + }{arg1, arg2, arg3, arg4, arg5}) stub := fake.IssuedBalanceStub fakeReturns := fake.issuedBalanceReturns - fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2, arg3}) + fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2, arg3, arg4, arg5}) fake.issuedBalanceMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3) + return stub(arg1, arg2, arg3, arg4, arg5) } if specificReturn { return ret.result1, ret.result2 @@ -858,17 +869,17 @@ func (fake *QueryEngine) IssuedBalanceCallCount() int { return len(fake.issuedBalanceArgsForCall) } -func (fake *QueryEngine) IssuedBalanceCalls(stub func(context.Context, token.Type, driver.Identity) (uint64, error)) { +func (fake *QueryEngine) IssuedBalanceCalls(stub func(context.Context, token.Type, driver.Identity, *time.Time, *time.Time) (uint64, error)) { fake.issuedBalanceMutex.Lock() defer fake.issuedBalanceMutex.Unlock() fake.IssuedBalanceStub = stub } -func (fake *QueryEngine) IssuedBalanceArgsForCall(i int) (context.Context, token.Type, driver.Identity) { +func (fake *QueryEngine) IssuedBalanceArgsForCall(i int) (context.Context, token.Type, driver.Identity, *time.Time, *time.Time) { fake.issuedBalanceMutex.RLock() defer fake.issuedBalanceMutex.RUnlock() argsForCall := fake.issuedBalanceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 } func (fake *QueryEngine) IssuedBalanceReturns(result1 uint64, result2 error) { @@ -1026,20 +1037,24 @@ func (fake *QueryEngine) ListHistoryIssuedTokensReturnsOnCall(i int, result1 *to }{result1, result2} } -func (fake *QueryEngine) ListRedeemedTokens(arg1 context.Context, arg2 token.Type, arg3 driver.Identity) (*token.IssuedTokens, error) { +func (fake *QueryEngine) ListRedeemedTokens(arg1 context.Context, arg2 token.Type, arg3 driver.Identity, arg4 *time.Time, arg5 *time.Time, arg6 driver.SortField, arg7 driver.SortDirection) (*token.IssuedTokens, error) { fake.listRedeemedTokensMutex.Lock() ret, specificReturn := fake.listRedeemedTokensReturnsOnCall[len(fake.listRedeemedTokensArgsForCall)] fake.listRedeemedTokensArgsForCall = append(fake.listRedeemedTokensArgsForCall, struct { arg1 context.Context arg2 token.Type arg3 driver.Identity - }{arg1, arg2, arg3}) + arg4 *time.Time + arg5 *time.Time + arg6 driver.SortField + arg7 driver.SortDirection + }{arg1, arg2, arg3, arg4, arg5, arg6, arg7}) stub := fake.ListRedeemedTokensStub fakeReturns := fake.listRedeemedTokensReturns - fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2, arg3}) + fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2, arg3, arg4, arg5, arg6, arg7}) fake.listRedeemedTokensMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3) + return stub(arg1, arg2, arg3, arg4, arg5, arg6, arg7) } if specificReturn { return ret.result1, ret.result2 @@ -1053,17 +1068,17 @@ func (fake *QueryEngine) ListRedeemedTokensCallCount() int { return len(fake.listRedeemedTokensArgsForCall) } -func (fake *QueryEngine) ListRedeemedTokensCalls(stub func(context.Context, token.Type, driver.Identity) (*token.IssuedTokens, error)) { +func (fake *QueryEngine) ListRedeemedTokensCalls(stub func(context.Context, token.Type, driver.Identity, *time.Time, *time.Time, driver.SortField, driver.SortDirection) (*token.IssuedTokens, error)) { fake.listRedeemedTokensMutex.Lock() defer fake.listRedeemedTokensMutex.Unlock() fake.ListRedeemedTokensStub = stub } -func (fake *QueryEngine) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type, driver.Identity) { +func (fake *QueryEngine) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type, driver.Identity, *time.Time, *time.Time, driver.SortField, driver.SortDirection) { fake.listRedeemedTokensMutex.RLock() defer fake.listRedeemedTokensMutex.RUnlock() argsForCall := fake.listRedeemedTokensArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5, argsForCall.arg6, argsForCall.arg7 } func (fake *QueryEngine) ListRedeemedTokensReturns(result1 *token.IssuedTokens, result2 error) { @@ -1220,20 +1235,22 @@ func (fake *QueryEngine) PublicParamsReturnsOnCall(i int, result1 []byte, result }{result1, result2} } -func (fake *QueryEngine) RedeemedBalance(arg1 context.Context, arg2 token.Type, arg3 driver.Identity) (uint64, error) { +func (fake *QueryEngine) RedeemedBalance(arg1 context.Context, arg2 token.Type, arg3 driver.Identity, arg4 *time.Time, arg5 *time.Time) (uint64, error) { fake.redeemedBalanceMutex.Lock() ret, specificReturn := fake.redeemedBalanceReturnsOnCall[len(fake.redeemedBalanceArgsForCall)] fake.redeemedBalanceArgsForCall = append(fake.redeemedBalanceArgsForCall, struct { arg1 context.Context arg2 token.Type arg3 driver.Identity - }{arg1, arg2, arg3}) + arg4 *time.Time + arg5 *time.Time + }{arg1, arg2, arg3, arg4, arg5}) stub := fake.RedeemedBalanceStub fakeReturns := fake.redeemedBalanceReturns - fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2, arg3}) + fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2, arg3, arg4, arg5}) fake.redeemedBalanceMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3) + return stub(arg1, arg2, arg3, arg4, arg5) } if specificReturn { return ret.result1, ret.result2 @@ -1247,17 +1264,17 @@ func (fake *QueryEngine) RedeemedBalanceCallCount() int { return len(fake.redeemedBalanceArgsForCall) } -func (fake *QueryEngine) RedeemedBalanceCalls(stub func(context.Context, token.Type, driver.Identity) (uint64, error)) { +func (fake *QueryEngine) RedeemedBalanceCalls(stub func(context.Context, token.Type, driver.Identity, *time.Time, *time.Time) (uint64, error)) { fake.redeemedBalanceMutex.Lock() defer fake.redeemedBalanceMutex.Unlock() fake.RedeemedBalanceStub = stub } -func (fake *QueryEngine) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type, driver.Identity) { +func (fake *QueryEngine) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type, driver.Identity, *time.Time, *time.Time) { fake.redeemedBalanceMutex.RLock() defer fake.redeemedBalanceMutex.RUnlock() argsForCall := fake.redeemedBalanceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 } func (fake *QueryEngine) RedeemedBalanceReturns(result1 uint64, result2 error) { diff --git a/token/driver/vault.go b/token/driver/vault.go index 4687175947..3863407c00 100644 --- a/token/driver/vault.go +++ b/token/driver/vault.go @@ -9,6 +9,7 @@ package driver import ( "context" "math/big" + "time" "github.com/hyperledger-labs/fabric-smart-client/platform/common/utils/collections/iterators" "github.com/hyperledger-labs/fabric-token-sdk/token/token" @@ -90,15 +91,17 @@ type QueryEngine interface { // IssuedBalance returns the sum of amounts of non-deleted tokens flagged as issued. // If tokenType is non-empty, only tokens of that type are included. // If issuerRaw is non-empty, only tokens issued by that identity are included. - IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw Identity) (uint64, error) + IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw Identity, from, to *time.Time) (uint64, error) // ListRedeemedTokens returns issued tokens that were spent by a Redeem action. // If tokenType is non-empty, only tokens of that type are included. // If issuerRaw is non-empty, only tokens issued by that identity are included. - ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw Identity) (*token.IssuedTokens, error) + // If from/to are non-nil, only tokens stored within that time range are included. + ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw Identity, from, to *time.Time, sortBy SortField, sortDirection SortDirection) (*token.IssuedTokens, error) // RedeemedBalance returns the sum of amounts of issued tokens spent by a Redeem action. // If tokenType is non-empty, only tokens of that type are included. // If issuerRaw is non-empty, only tokens issued by that identity are included. - RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw Identity) (uint64, error) + // If from/to are non-nil, only tokens stored within that time range are included. + RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw Identity, from, to *time.Time) (uint64, error) // PublicParams returns the serialized public parameters used by the driver. PublicParams(ctx context.Context) ([]byte, error) // GetTokenMetadata retrieves the private information (metadata) for the given token IDs. @@ -117,7 +120,6 @@ type QueryEngine interface { } //go:generate counterfeiter -o mock/token_vault.go -fake-name TokenVault . TokenVault - type TokenVault interface { IsPending(ctx context.Context, id *token.ID) (bool, error) GetTokenOutputsAndMeta(ctx context.Context, ids []*token.ID) ([][]byte, [][]byte, []token.Format, error) @@ -129,13 +131,11 @@ type TokenVault interface { } //go:generate counterfeiter -o mock/ledger_token.go -fake-name LedgerToken . LedgerToken - type LedgerToken interface { GetOwner() []byte } //go:generate counterfeiter -o mock/token_certification_storage.go -fake-name TokenCertificationStorage . TokenCertificationStorage - type TokenCertificationStorage interface { GetCertifications(ctx context.Context, ids []*token.ID) ([][]byte, error) } diff --git a/token/driver/wallet.go b/token/driver/wallet.go index d1308e97de..dda93be45c 100644 --- a/token/driver/wallet.go +++ b/token/driver/wallet.go @@ -9,6 +9,7 @@ package driver import ( "context" "math/big" + "time" "github.com/hyperledger-labs/fabric-smart-client/platform/view/view" "github.com/hyperledger-labs/fabric-token-sdk/token/token" @@ -93,15 +94,27 @@ const ( type ListTokensOptions struct { // TokenType is the type of token to list TokenType token.Type -<<<<<<< HEAD -======= - // Context is used to track the operation - Context context.Context // SortBy specifies which field to use for ordering the results. SortBy SortField // SortDirection specifies ascending or descending order. SortDirection SortDirection ->>>>>>> f26e9668 (Add sorting options to ListTokensOptions) + // From, if non-nil, restricts results to tokens stored at or after this time. + From *time.Time + // To, if non-nil, restricts results to tokens stored at or before this time. + To *time.Time +} + +// BalanceOpts contains options for balance queries (methods that return a +// scalar total). Sort fields are intentionally omitted because balance +// results are single numbers, not ordered lists. +type BalanceOpts struct { + // TokenType restricts the balance computation to tokens of this type. + // An empty value means all token types. + TokenType token.Type + // From, if non-nil, restricts the computation to tokens stored at or after this time. + From *time.Time + // To, if non-nil, restricts the computation to tokens stored at or before this time. + To *time.Time } // Wallet models a generic wallet @@ -153,8 +166,7 @@ type OwnerWallet interface { // ListTokensIterator returns an iterator of unspent tokens owned by this wallet filtered using the passed options. ListTokensIterator(ctx context.Context, opts *ListTokensOptions) (UnspentTokensIterator, error) - // Balance returns the sum of the amounts of the tokens with type and EID equal to those passed as arguments. - // The result is returned as a *big.Int to support arbitrary precision and prevent overflow. + // Balance returns the sun of the amounts, with 64 bits of precision, of the tokens with type and EID equal to those passed as arguments. Balance(ctx context.Context, opts *ListTokensOptions) (*big.Int, error) // EnrollmentID returns the enrollment ID of the owner wallet @@ -182,13 +194,16 @@ type IssuerWallet interface { HistoryTokens(ctx context.Context, opts *ListTokensOptions) (*token.IssuedTokens, error) // IssuedBalance returns the total amount of tokens issued by this wallet (not yet redeemed). - IssuedBalance(ctx context.Context, opts *ListTokensOptions) (uint64, error) + IssuedBalance(ctx context.Context, opts *BalanceOpts) (uint64, error) // RedeemedTokens returns the list of redeemed tokens that were originally issued by this wallet. RedeemedTokens(ctx context.Context, opts *ListTokensOptions) (*token.IssuedTokens, error) // RedeemedBalance returns the total amount of redeemed tokens that were originally issued by this wallet. - RedeemedBalance(ctx context.Context, opts *ListTokensOptions) (uint64, error) + RedeemedBalance(ctx context.Context, opts *BalanceOpts) (uint64, error) + + // OutstandingBalance returns the net amount still in circulation: IssuedBalance − RedeemedBalance. + OutstandingBalance(ctx context.Context, opts *BalanceOpts) (uint64, error) } // AuditorWallet models the wallet of an auditor diff --git a/token/services/identity/role/mock/itv.go b/token/services/identity/role/mock/itv.go index a45908eb04..0cb9ede7ab 100644 --- a/token/services/identity/role/mock/itv.go +++ b/token/services/identity/role/mock/itv.go @@ -4,18 +4,22 @@ package mock import ( "context" "sync" + "time" + "github.com/hyperledger-labs/fabric-token-sdk/token/driver" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/role" "github.com/hyperledger-labs/fabric-token-sdk/token/token" ) type IssuerTokenVault struct { - IssuedBalanceStub func(context.Context, token.Type, role.Identity) (uint64, error) + IssuedBalanceStub func(context.Context, token.Type, role.Identity, *time.Time, *time.Time) (uint64, error) issuedBalanceMutex sync.RWMutex issuedBalanceArgsForCall []struct { arg1 context.Context arg2 token.Type arg3 role.Identity + arg4 *time.Time + arg5 *time.Time } issuedBalanceReturns struct { result1 uint64 @@ -38,12 +42,16 @@ type IssuerTokenVault struct { result1 *token.IssuedTokens result2 error } - ListRedeemedTokensStub func(context.Context, token.Type, role.Identity) (*token.IssuedTokens, error) + ListRedeemedTokensStub func(context.Context, token.Type, role.Identity, *time.Time, *time.Time, driver.SortField, driver.SortDirection) (*token.IssuedTokens, error) listRedeemedTokensMutex sync.RWMutex listRedeemedTokensArgsForCall []struct { arg1 context.Context arg2 token.Type arg3 role.Identity + arg4 *time.Time + arg5 *time.Time + arg6 driver.SortField + arg7 driver.SortDirection } listRedeemedTokensReturns struct { result1 *token.IssuedTokens @@ -53,12 +61,14 @@ type IssuerTokenVault struct { result1 *token.IssuedTokens result2 error } - RedeemedBalanceStub func(context.Context, token.Type, role.Identity) (uint64, error) + RedeemedBalanceStub func(context.Context, token.Type, role.Identity, *time.Time, *time.Time) (uint64, error) redeemedBalanceMutex sync.RWMutex redeemedBalanceArgsForCall []struct { arg1 context.Context arg2 token.Type arg3 role.Identity + arg4 *time.Time + arg5 *time.Time } redeemedBalanceReturns struct { result1 uint64 @@ -72,20 +82,22 @@ type IssuerTokenVault struct { invocationsMutex sync.RWMutex } -func (fake *IssuerTokenVault) IssuedBalance(arg1 context.Context, arg2 token.Type, arg3 role.Identity) (uint64, error) { +func (fake *IssuerTokenVault) IssuedBalance(arg1 context.Context, arg2 token.Type, arg3 role.Identity, arg4 *time.Time, arg5 *time.Time) (uint64, error) { fake.issuedBalanceMutex.Lock() ret, specificReturn := fake.issuedBalanceReturnsOnCall[len(fake.issuedBalanceArgsForCall)] fake.issuedBalanceArgsForCall = append(fake.issuedBalanceArgsForCall, struct { arg1 context.Context arg2 token.Type arg3 role.Identity - }{arg1, arg2, arg3}) + arg4 *time.Time + arg5 *time.Time + }{arg1, arg2, arg3, arg4, arg5}) stub := fake.IssuedBalanceStub fakeReturns := fake.issuedBalanceReturns - fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2, arg3}) + fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2, arg3, arg4, arg5}) fake.issuedBalanceMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3) + return stub(arg1, arg2, arg3, arg4, arg5) } if specificReturn { return ret.result1, ret.result2 @@ -99,17 +111,17 @@ func (fake *IssuerTokenVault) IssuedBalanceCallCount() int { return len(fake.issuedBalanceArgsForCall) } -func (fake *IssuerTokenVault) IssuedBalanceCalls(stub func(context.Context, token.Type, role.Identity) (uint64, error)) { +func (fake *IssuerTokenVault) IssuedBalanceCalls(stub func(context.Context, token.Type, role.Identity, *time.Time, *time.Time) (uint64, error)) { fake.issuedBalanceMutex.Lock() defer fake.issuedBalanceMutex.Unlock() fake.IssuedBalanceStub = stub } -func (fake *IssuerTokenVault) IssuedBalanceArgsForCall(i int) (context.Context, token.Type, role.Identity) { +func (fake *IssuerTokenVault) IssuedBalanceArgsForCall(i int) (context.Context, token.Type, role.Identity, *time.Time, *time.Time) { fake.issuedBalanceMutex.RLock() defer fake.issuedBalanceMutex.RUnlock() argsForCall := fake.issuedBalanceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 } func (fake *IssuerTokenVault) IssuedBalanceReturns(result1 uint64, result2 error) { @@ -202,20 +214,24 @@ func (fake *IssuerTokenVault) ListHistoryIssuedTokensReturnsOnCall(i int, result }{result1, result2} } -func (fake *IssuerTokenVault) ListRedeemedTokens(arg1 context.Context, arg2 token.Type, arg3 role.Identity) (*token.IssuedTokens, error) { +func (fake *IssuerTokenVault) ListRedeemedTokens(arg1 context.Context, arg2 token.Type, arg3 role.Identity, arg4 *time.Time, arg5 *time.Time, arg6 driver.SortField, arg7 driver.SortDirection) (*token.IssuedTokens, error) { fake.listRedeemedTokensMutex.Lock() ret, specificReturn := fake.listRedeemedTokensReturnsOnCall[len(fake.listRedeemedTokensArgsForCall)] fake.listRedeemedTokensArgsForCall = append(fake.listRedeemedTokensArgsForCall, struct { arg1 context.Context arg2 token.Type arg3 role.Identity - }{arg1, arg2, arg3}) + arg4 *time.Time + arg5 *time.Time + arg6 driver.SortField + arg7 driver.SortDirection + }{arg1, arg2, arg3, arg4, arg5, arg6, arg7}) stub := fake.ListRedeemedTokensStub fakeReturns := fake.listRedeemedTokensReturns - fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2, arg3}) + fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2, arg3, arg4, arg5, arg6, arg7}) fake.listRedeemedTokensMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3) + return stub(arg1, arg2, arg3, arg4, arg5, arg6, arg7) } if specificReturn { return ret.result1, ret.result2 @@ -229,17 +245,17 @@ func (fake *IssuerTokenVault) ListRedeemedTokensCallCount() int { return len(fake.listRedeemedTokensArgsForCall) } -func (fake *IssuerTokenVault) ListRedeemedTokensCalls(stub func(context.Context, token.Type, role.Identity) (*token.IssuedTokens, error)) { +func (fake *IssuerTokenVault) ListRedeemedTokensCalls(stub func(context.Context, token.Type, role.Identity, *time.Time, *time.Time, driver.SortField, driver.SortDirection) (*token.IssuedTokens, error)) { fake.listRedeemedTokensMutex.Lock() defer fake.listRedeemedTokensMutex.Unlock() fake.ListRedeemedTokensStub = stub } -func (fake *IssuerTokenVault) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type, role.Identity) { +func (fake *IssuerTokenVault) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type, role.Identity, *time.Time, *time.Time, driver.SortField, driver.SortDirection) { fake.listRedeemedTokensMutex.RLock() defer fake.listRedeemedTokensMutex.RUnlock() argsForCall := fake.listRedeemedTokensArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5, argsForCall.arg6, argsForCall.arg7 } func (fake *IssuerTokenVault) ListRedeemedTokensReturns(result1 *token.IssuedTokens, result2 error) { @@ -268,20 +284,22 @@ func (fake *IssuerTokenVault) ListRedeemedTokensReturnsOnCall(i int, result1 *to }{result1, result2} } -func (fake *IssuerTokenVault) RedeemedBalance(arg1 context.Context, arg2 token.Type, arg3 role.Identity) (uint64, error) { +func (fake *IssuerTokenVault) RedeemedBalance(arg1 context.Context, arg2 token.Type, arg3 role.Identity, arg4 *time.Time, arg5 *time.Time) (uint64, error) { fake.redeemedBalanceMutex.Lock() ret, specificReturn := fake.redeemedBalanceReturnsOnCall[len(fake.redeemedBalanceArgsForCall)] fake.redeemedBalanceArgsForCall = append(fake.redeemedBalanceArgsForCall, struct { arg1 context.Context arg2 token.Type arg3 role.Identity - }{arg1, arg2, arg3}) + arg4 *time.Time + arg5 *time.Time + }{arg1, arg2, arg3, arg4, arg5}) stub := fake.RedeemedBalanceStub fakeReturns := fake.redeemedBalanceReturns - fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2, arg3}) + fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2, arg3, arg4, arg5}) fake.redeemedBalanceMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3) + return stub(arg1, arg2, arg3, arg4, arg5) } if specificReturn { return ret.result1, ret.result2 @@ -295,17 +313,17 @@ func (fake *IssuerTokenVault) RedeemedBalanceCallCount() int { return len(fake.redeemedBalanceArgsForCall) } -func (fake *IssuerTokenVault) RedeemedBalanceCalls(stub func(context.Context, token.Type, role.Identity) (uint64, error)) { +func (fake *IssuerTokenVault) RedeemedBalanceCalls(stub func(context.Context, token.Type, role.Identity, *time.Time, *time.Time) (uint64, error)) { fake.redeemedBalanceMutex.Lock() defer fake.redeemedBalanceMutex.Unlock() fake.RedeemedBalanceStub = stub } -func (fake *IssuerTokenVault) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type, role.Identity) { +func (fake *IssuerTokenVault) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type, role.Identity, *time.Time, *time.Time) { fake.redeemedBalanceMutex.RLock() defer fake.redeemedBalanceMutex.RUnlock() argsForCall := fake.redeemedBalanceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 } func (fake *IssuerTokenVault) RedeemedBalanceReturns(result1 uint64, result2 error) { diff --git a/token/services/identity/role/mock/tv.go b/token/services/identity/role/mock/tv.go index 27e03c4cf2..4ab3d15a58 100644 --- a/token/services/identity/role/mock/tv.go +++ b/token/services/identity/role/mock/tv.go @@ -5,6 +5,7 @@ import ( "context" "math/big" "sync" + "time" "github.com/hyperledger-labs/fabric-token-sdk/token/driver" "github.com/hyperledger-labs/fabric-token-sdk/token/services/identity/role" @@ -27,12 +28,14 @@ type TokenVault struct { result1 *big.Int result2 error } - IssuedBalanceStub func(context.Context, token.Type, driver.Identity) (uint64, error) + IssuedBalanceStub func(context.Context, token.Type, driver.Identity, *time.Time, *time.Time) (uint64, error) issuedBalanceMutex sync.RWMutex issuedBalanceArgsForCall []struct { arg1 context.Context arg2 token.Type arg3 driver.Identity + arg4 *time.Time + arg5 *time.Time } issuedBalanceReturns struct { result1 uint64 @@ -55,12 +58,16 @@ type TokenVault struct { result1 *token.IssuedTokens result2 error } - ListRedeemedTokensStub func(context.Context, token.Type, driver.Identity) (*token.IssuedTokens, error) + ListRedeemedTokensStub func(context.Context, token.Type, driver.Identity, *time.Time, *time.Time, driver.SortField, driver.SortDirection) (*token.IssuedTokens, error) listRedeemedTokensMutex sync.RWMutex listRedeemedTokensArgsForCall []struct { arg1 context.Context arg2 token.Type arg3 driver.Identity + arg4 *time.Time + arg5 *time.Time + arg6 driver.SortField + arg7 driver.SortDirection } listRedeemedTokensReturns struct { result1 *token.IssuedTokens @@ -70,12 +77,14 @@ type TokenVault struct { result1 *token.IssuedTokens result2 error } - RedeemedBalanceStub func(context.Context, token.Type, driver.Identity) (uint64, error) + RedeemedBalanceStub func(context.Context, token.Type, driver.Identity, *time.Time, *time.Time) (uint64, error) redeemedBalanceMutex sync.RWMutex redeemedBalanceArgsForCall []struct { arg1 context.Context arg2 token.Type arg3 driver.Identity + arg4 *time.Time + arg5 *time.Time } redeemedBalanceReturns struct { result1 uint64 @@ -170,20 +179,22 @@ func (fake *TokenVault) BalanceReturnsOnCall(i int, result1 *big.Int, result2 er }{result1, result2} } -func (fake *TokenVault) IssuedBalance(arg1 context.Context, arg2 token.Type, arg3 driver.Identity) (uint64, error) { +func (fake *TokenVault) IssuedBalance(arg1 context.Context, arg2 token.Type, arg3 driver.Identity, arg4 *time.Time, arg5 *time.Time) (uint64, error) { fake.issuedBalanceMutex.Lock() ret, specificReturn := fake.issuedBalanceReturnsOnCall[len(fake.issuedBalanceArgsForCall)] fake.issuedBalanceArgsForCall = append(fake.issuedBalanceArgsForCall, struct { arg1 context.Context arg2 token.Type arg3 driver.Identity - }{arg1, arg2, arg3}) + arg4 *time.Time + arg5 *time.Time + }{arg1, arg2, arg3, arg4, arg5}) stub := fake.IssuedBalanceStub fakeReturns := fake.issuedBalanceReturns - fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2, arg3}) + fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2, arg3, arg4, arg5}) fake.issuedBalanceMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3) + return stub(arg1, arg2, arg3, arg4, arg5) } if specificReturn { return ret.result1, ret.result2 @@ -197,17 +208,17 @@ func (fake *TokenVault) IssuedBalanceCallCount() int { return len(fake.issuedBalanceArgsForCall) } -func (fake *TokenVault) IssuedBalanceCalls(stub func(context.Context, token.Type, driver.Identity) (uint64, error)) { +func (fake *TokenVault) IssuedBalanceCalls(stub func(context.Context, token.Type, driver.Identity, *time.Time, *time.Time) (uint64, error)) { fake.issuedBalanceMutex.Lock() defer fake.issuedBalanceMutex.Unlock() fake.IssuedBalanceStub = stub } -func (fake *TokenVault) IssuedBalanceArgsForCall(i int) (context.Context, token.Type, driver.Identity) { +func (fake *TokenVault) IssuedBalanceArgsForCall(i int) (context.Context, token.Type, driver.Identity, *time.Time, *time.Time) { fake.issuedBalanceMutex.RLock() defer fake.issuedBalanceMutex.RUnlock() argsForCall := fake.issuedBalanceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 } func (fake *TokenVault) IssuedBalanceReturns(result1 uint64, result2 error) { @@ -300,20 +311,24 @@ func (fake *TokenVault) ListHistoryIssuedTokensReturnsOnCall(i int, result1 *tok }{result1, result2} } -func (fake *TokenVault) ListRedeemedTokens(arg1 context.Context, arg2 token.Type, arg3 driver.Identity) (*token.IssuedTokens, error) { +func (fake *TokenVault) ListRedeemedTokens(arg1 context.Context, arg2 token.Type, arg3 driver.Identity, arg4 *time.Time, arg5 *time.Time, arg6 driver.SortField, arg7 driver.SortDirection) (*token.IssuedTokens, error) { fake.listRedeemedTokensMutex.Lock() ret, specificReturn := fake.listRedeemedTokensReturnsOnCall[len(fake.listRedeemedTokensArgsForCall)] fake.listRedeemedTokensArgsForCall = append(fake.listRedeemedTokensArgsForCall, struct { arg1 context.Context arg2 token.Type arg3 driver.Identity - }{arg1, arg2, arg3}) + arg4 *time.Time + arg5 *time.Time + arg6 driver.SortField + arg7 driver.SortDirection + }{arg1, arg2, arg3, arg4, arg5, arg6, arg7}) stub := fake.ListRedeemedTokensStub fakeReturns := fake.listRedeemedTokensReturns - fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2, arg3}) + fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2, arg3, arg4, arg5, arg6, arg7}) fake.listRedeemedTokensMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3) + return stub(arg1, arg2, arg3, arg4, arg5, arg6, arg7) } if specificReturn { return ret.result1, ret.result2 @@ -327,17 +342,17 @@ func (fake *TokenVault) ListRedeemedTokensCallCount() int { return len(fake.listRedeemedTokensArgsForCall) } -func (fake *TokenVault) ListRedeemedTokensCalls(stub func(context.Context, token.Type, driver.Identity) (*token.IssuedTokens, error)) { +func (fake *TokenVault) ListRedeemedTokensCalls(stub func(context.Context, token.Type, driver.Identity, *time.Time, *time.Time, driver.SortField, driver.SortDirection) (*token.IssuedTokens, error)) { fake.listRedeemedTokensMutex.Lock() defer fake.listRedeemedTokensMutex.Unlock() fake.ListRedeemedTokensStub = stub } -func (fake *TokenVault) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type, driver.Identity) { +func (fake *TokenVault) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type, driver.Identity, *time.Time, *time.Time, driver.SortField, driver.SortDirection) { fake.listRedeemedTokensMutex.RLock() defer fake.listRedeemedTokensMutex.RUnlock() argsForCall := fake.listRedeemedTokensArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5, argsForCall.arg6, argsForCall.arg7 } func (fake *TokenVault) ListRedeemedTokensReturns(result1 *token.IssuedTokens, result2 error) { @@ -366,20 +381,22 @@ func (fake *TokenVault) ListRedeemedTokensReturnsOnCall(i int, result1 *token.Is }{result1, result2} } -func (fake *TokenVault) RedeemedBalance(arg1 context.Context, arg2 token.Type, arg3 driver.Identity) (uint64, error) { +func (fake *TokenVault) RedeemedBalance(arg1 context.Context, arg2 token.Type, arg3 driver.Identity, arg4 *time.Time, arg5 *time.Time) (uint64, error) { fake.redeemedBalanceMutex.Lock() ret, specificReturn := fake.redeemedBalanceReturnsOnCall[len(fake.redeemedBalanceArgsForCall)] fake.redeemedBalanceArgsForCall = append(fake.redeemedBalanceArgsForCall, struct { arg1 context.Context arg2 token.Type arg3 driver.Identity - }{arg1, arg2, arg3}) + arg4 *time.Time + arg5 *time.Time + }{arg1, arg2, arg3, arg4, arg5}) stub := fake.RedeemedBalanceStub fakeReturns := fake.redeemedBalanceReturns - fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2, arg3}) + fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2, arg3, arg4, arg5}) fake.redeemedBalanceMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3) + return stub(arg1, arg2, arg3, arg4, arg5) } if specificReturn { return ret.result1, ret.result2 @@ -393,17 +410,17 @@ func (fake *TokenVault) RedeemedBalanceCallCount() int { return len(fake.redeemedBalanceArgsForCall) } -func (fake *TokenVault) RedeemedBalanceCalls(stub func(context.Context, token.Type, driver.Identity) (uint64, error)) { +func (fake *TokenVault) RedeemedBalanceCalls(stub func(context.Context, token.Type, driver.Identity, *time.Time, *time.Time) (uint64, error)) { fake.redeemedBalanceMutex.Lock() defer fake.redeemedBalanceMutex.Unlock() fake.RedeemedBalanceStub = stub } -func (fake *TokenVault) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type, driver.Identity) { +func (fake *TokenVault) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type, driver.Identity, *time.Time, *time.Time) { fake.redeemedBalanceMutex.RLock() defer fake.redeemedBalanceMutex.RUnlock() argsForCall := fake.redeemedBalanceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 } func (fake *TokenVault) RedeemedBalanceReturns(result1 uint64, result2 error) { diff --git a/token/services/identity/role/wallets.go b/token/services/identity/role/wallets.go index dcea33da5a..18f5ed2ecb 100644 --- a/token/services/identity/role/wallets.go +++ b/token/services/identity/role/wallets.go @@ -9,6 +9,7 @@ package role import ( "context" "math/big" + "time" "github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors" "github.com/hyperledger-labs/fabric-smart-client/platform/common/utils/collections/iterators" @@ -120,15 +121,17 @@ type IssuerTokenVault interface { // IssuedBalance returns the sum of amounts of non-deleted tokens issued by this node. // If tokenType is non-empty, only tokens of that type are included. // If issuerRaw is non-empty, only tokens issued by that identity are included. - IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw Identity) (uint64, error) + IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw Identity, from, to *time.Time) (uint64, error) // ListRedeemedTokens returns issued tokens that were redeemed (spent via a Redeem action). // If tokenType is non-empty, only tokens of that type are included. // If issuerRaw is non-empty, only tokens issued by that identity are included. - ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw Identity) (*token.IssuedTokens, error) + // If from/to are non-nil, only tokens stored within that time range are included. + ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw Identity, from, to *time.Time, sortBy driver.SortField, sortDirection driver.SortDirection) (*token.IssuedTokens, error) // RedeemedBalance returns the sum of amounts of redeemed tokens issued by this node. // If tokenType is non-empty, only tokens of that type are included. // If issuerRaw is non-empty, only tokens issued by that identity are included. - RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw Identity) (uint64, error) + // If from/to are non-nil, only tokens stored within that time range are included. + RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw Identity, from, to *time.Time) (uint64, error) } // IssuerWallet represents a wallet that manages a single issuer identity. @@ -228,16 +231,16 @@ func (w *IssuerWallet) HistoryTokens(ctx context.Context, opts *driver.ListToken // IssuedBalance returns the total amount of non-redeemed tokens issued by // this wallet. -func (w *IssuerWallet) IssuedBalance(ctx context.Context, opts *driver.ListTokensOptions) (uint64, error) { +func (w *IssuerWallet) IssuedBalance(ctx context.Context, opts *driver.BalanceOpts) (uint64, error) { w.Logger.DebugfContext(ctx, "issuer wallet [%s]: issued balance, type [%s]", w.ID(), opts.TokenType) - return w.TokenVault.IssuedBalance(ctx, opts.TokenType, w.Identity) + return w.TokenVault.IssuedBalance(ctx, opts.TokenType, w.Identity, opts.From, opts.To) } // RedeemedTokens returns the list of redeemed tokens originally issued by this wallet. func (w *IssuerWallet) RedeemedTokens(ctx context.Context, opts *driver.ListTokensOptions) (*token.IssuedTokens, error) { w.Logger.DebugfContext(ctx, "issuer wallet [%s]: redeemed tokens, type [%s]", w.ID(), opts.TokenType) - source, err := w.TokenVault.ListRedeemedTokens(ctx, opts.TokenType, w.Identity) + source, err := w.TokenVault.ListRedeemedTokens(ctx, opts.TokenType, w.Identity, opts.From, opts.To, opts.SortBy, opts.SortDirection) if err != nil { return nil, errors.Wrap(err, "redeemed tokens query failed") } @@ -247,10 +250,32 @@ func (w *IssuerWallet) RedeemedTokens(ctx context.Context, opts *driver.ListToke // RedeemedBalance returns the total amount of redeemed tokens originally // issued by this wallet. -func (w *IssuerWallet) RedeemedBalance(ctx context.Context, opts *driver.ListTokensOptions) (uint64, error) { +func (w *IssuerWallet) RedeemedBalance(ctx context.Context, opts *driver.BalanceOpts) (uint64, error) { w.Logger.DebugfContext(ctx, "issuer wallet [%s]: redeemed balance, type [%s]", w.ID(), opts.TokenType) - return w.TokenVault.RedeemedBalance(ctx, opts.TokenType, w.Identity) + return w.TokenVault.RedeemedBalance(ctx, opts.TokenType, w.Identity, opts.From, opts.To) +} + +// OutstandingBalance returns the net amount still in circulation: +// IssuedBalance − RedeemedBalance. +func (w *IssuerWallet) OutstandingBalance(ctx context.Context, opts *driver.BalanceOpts) (uint64, error) { + w.Logger.DebugfContext(ctx, "issuer wallet [%s]: outstanding balance, type [%s]", w.ID(), opts.TokenType) + + issued, err := w.IssuedBalance(ctx, opts) + if err != nil { + return 0, errors.Wrap(err, "outstanding balance: issued query failed") + } + + redeemed, err := w.RedeemedBalance(ctx, opts) + if err != nil { + return 0, errors.Wrap(err, "outstanding balance: redeemed query failed") + } + + if redeemed > issued { + return 0, errors.Errorf("outstanding balance: redeemed (%d) exceeds issued (%d)", redeemed, issued) + } + + return issued - redeemed, nil } // filterTokens filters the source tokens by token type and wallet identity. @@ -434,8 +459,7 @@ func (w *LongTermOwnerWallet) ListTokens(ctx context.Context, opts *driver.ListT } // Balance returns the on-chain balance for the wallet for the given token -// type in the listing options. The result is returned as a *big.Int to support -// arbitrary precision and prevent overflow. +// type in the listing options. func (w *LongTermOwnerWallet) Balance(ctx context.Context, opts *driver.ListTokensOptions) (*big.Int, error) { balance, err := w.TokenVault.Balance(ctx, w.WalletID, opts.TokenType) if err != nil { diff --git a/token/services/identity/role/wallets_test.go b/token/services/identity/role/wallets_test.go index 598e97f393..6a3a8ae74d 100644 --- a/token/services/identity/role/wallets_test.go +++ b/token/services/identity/role/wallets_test.go @@ -155,6 +155,94 @@ func TestIssuerWallet(t *testing.T) { _, err = w.HistoryTokens(t.Context(), &driver.ListTokensOptions{}) require.Error(t, err) }) + + t.Run("IssuedBalance", func(t *testing.T) { + w, tv, _ := setup() + + // Happy path + tv.IssuedBalanceReturns(500, nil) + bal, err := w.IssuedBalance(t.Context(), &driver.ListTokensOptions{TokenType: "USD"}) + require.NoError(t, err) + assert.Equal(t, uint64(500), bal) + + // Error propagation + tv.IssuedBalanceReturns(0, errors.New("db down")) + _, err = w.IssuedBalance(t.Context(), &driver.ListTokensOptions{}) + require.Error(t, err) + }) + + t.Run("RedeemedTokens", func(t *testing.T) { + w, tv, _ := setup() + id := driver.Identity("issuerIdentity") + + tokens := &token.IssuedTokens{ + Tokens: []*token.IssuedToken{ + {Type: "USD", Quantity: "100", Issuer: id}, + {Type: "USD", Quantity: "200", Issuer: driver.Identity("other")}, + }, + } + tv.ListRedeemedTokensReturns(tokens, nil) + + // Filters out tokens from other issuers + res, err := w.RedeemedTokens(t.Context(), &driver.ListTokensOptions{TokenType: "USD"}) + require.NoError(t, err) + assert.Len(t, res.Tokens, 1) + assert.Equal(t, "100", res.Tokens[0].Quantity) + + // Error propagation + tv.ListRedeemedTokensReturns(nil, errors.New("vault error")) + _, err = w.RedeemedTokens(t.Context(), &driver.ListTokensOptions{}) + require.Error(t, err) + }) + + t.Run("RedeemedBalance", func(t *testing.T) { + w, tv, _ := setup() + + tv.RedeemedBalanceReturns(300, nil) + bal, err := w.RedeemedBalance(t.Context(), &driver.ListTokensOptions{TokenType: "USD"}) + require.NoError(t, err) + assert.Equal(t, uint64(300), bal) + + // Error propagation + tv.RedeemedBalanceReturns(0, errors.New("db down")) + _, err = w.RedeemedBalance(t.Context(), &driver.ListTokensOptions{}) + require.Error(t, err) + }) + + t.Run("OutstandingBalance", func(t *testing.T) { + w, tv, _ := setup() + + // Normal case: issued > redeemed + tv.IssuedBalanceReturns(1000, nil) + tv.RedeemedBalanceReturns(300, nil) + bal, err := w.OutstandingBalance(t.Context(), &driver.ListTokensOptions{TokenType: "USD"}) + require.NoError(t, err) + assert.Equal(t, uint64(700), bal) + + // Edge case: issued == redeemed + tv.IssuedBalanceReturns(500, nil) + tv.RedeemedBalanceReturns(500, nil) + bal, err = w.OutstandingBalance(t.Context(), &driver.ListTokensOptions{}) + require.NoError(t, err) + assert.Equal(t, uint64(0), bal) + + // Error case: redeemed > issued + tv.IssuedBalanceReturns(100, nil) + tv.RedeemedBalanceReturns(200, nil) + _, err = w.OutstandingBalance(t.Context(), &driver.ListTokensOptions{}) + require.Error(t, err) + + // Error propagation from issued + tv.IssuedBalanceReturns(0, errors.New("issued fail")) + _, err = w.OutstandingBalance(t.Context(), &driver.ListTokensOptions{}) + require.Error(t, err) + + // Error propagation from redeemed + tv.IssuedBalanceReturns(1000, nil) + tv.RedeemedBalanceReturns(0, errors.New("redeemed fail")) + _, err = w.OutstandingBalance(t.Context(), &driver.ListTokensOptions{}) + require.Error(t, err) + }) } func TestLongTermOwnerWallet(t *testing.T) { diff --git a/token/services/identity/role/wfactory.go b/token/services/identity/role/wfactory.go index 006b5c9c6c..bc54c73201 100644 --- a/token/services/identity/role/wfactory.go +++ b/token/services/identity/role/wfactory.go @@ -9,6 +9,7 @@ package role import ( "context" "math/big" + "time" "github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors" "github.com/hyperledger-labs/fabric-token-sdk/token/core/common/metrics" @@ -22,26 +23,10 @@ import ( type TokenVault interface { UnspentTokensIteratorBy(ctx context.Context, id string, tokenType token.Type) (driver.UnspentTokensIterator, error) ListHistoryIssuedTokens(ctx context.Context) (*token.IssuedTokens, error) -<<<<<<< HEAD Balance(ctx context.Context, id string, tokenType token.Type) (*big.Int, error) -======= - Balance(ctx context.Context, id string, tokenType token.Type) (uint64, error) -<<<<<<< HEAD -<<<<<<< HEAD - IssuedBalance(ctx context.Context) (uint64, error) - ListRedeemedTokens(ctx context.Context) (*token.IssuedTokens, error) - RedeemedBalance(ctx context.Context) (uint64, error) ->>>>>>> effff1e9 (Regenerate mocks and add new methods to TokenVault interface) -======= - IssuedBalance(ctx context.Context, tokenType token.Type) (uint64, error) - ListRedeemedTokens(ctx context.Context, tokenType token.Type) (*token.IssuedTokens, error) - RedeemedBalance(ctx context.Context, tokenType token.Type) (uint64, error) ->>>>>>> 1f0e98c8 (Wire TokenType filter into IssuedBalance, ListRedeemedTokens, RedeemedBalance SQL queries) -======= - IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity) (uint64, error) - ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity) (*token.IssuedTokens, error) - RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity) (uint64, error) ->>>>>>> d6bffdd0 (Add issuer identity filter to IssuedBalance, ListRedeemedTokens, RedeemedBalance) + IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity, from, to *time.Time) (uint64, error) + ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity, from, to *time.Time, sortBy driver.SortField, sortDirection driver.SortDirection) (*token.IssuedTokens, error) + RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity, from, to *time.Time) (uint64, error) } //go:generate counterfeiter -o mock/wc.go -fake-name WalletsConfiguration . WalletsConfiguration diff --git a/token/services/storage/db/driver/token.go b/token/services/storage/db/driver/token.go index 4ebad81b89..b8273519cf 100644 --- a/token/services/storage/db/driver/token.go +++ b/token/services/storage/db/driver/token.go @@ -205,15 +205,17 @@ type TokenStore interface { // IssuedBalance returns the sum of amounts of non-deleted tokens flagged as issued. // If tokenType is non-empty, only tokens of that type are included. // If issuerRaw is non-empty, only tokens issued by that identity are included. - IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity) (uint64, error) + IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity, from, to *time.Time) (uint64, error) // ListRedeemedTokens returns issued tokens that were spent by a Redeem action. // If tokenType is non-empty, only tokens of that type are included. // If issuerRaw is non-empty, only tokens issued by that identity are included. - ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity) (*token.IssuedTokens, error) + // If from/to are non-nil, only tokens stored within that time range are included. + ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity, from, to *time.Time, sortBy driver.SortField, sortDirection driver.SortDirection) (*token.IssuedTokens, error) // RedeemedBalance returns the sum of amounts of issued tokens spent by a Redeem action. // If tokenType is non-empty, only tokens of that type are included. // If issuerRaw is non-empty, only tokens issued by that identity are included. - RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity) (uint64, error) + // If from/to are non-nil, only tokens stored within that time range are included. + RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity, from, to *time.Time) (uint64, error) // GetTokenOutputs returns the value of the tokens as they appear on the ledger for the passed ids. // For each token, the call-back function is invoked. The call-back function is invoked respecting the order of the passed ids. GetTokenOutputs(ctx context.Context, ids []*token.ID, callback driver.QueryCallbackFunc) error diff --git a/token/services/storage/db/sql/common/tokens.go b/token/services/storage/db/sql/common/tokens.go index d289484ba3..cc05269d6e 100644 --- a/token/services/storage/db/sql/common/tokens.go +++ b/token/services/storage/db/sql/common/tokens.go @@ -10,7 +10,6 @@ import ( "context" "database/sql" "fmt" - "math/big" "runtime/debug" "strings" "sync" @@ -134,7 +133,7 @@ func (db *TokenStore) IsMine(ctx context.Context, txID string, index uint64) (bo Limit(1). Format(db.ci) - id, err := common.QueryUniqueContext[string](ctx, db.readDB, query, args...) + id, err := common.QueryUnique[string](db.readDB, query, args...) logger.DebugfContext(ctx, "token [%s:%d] is mine [%s]", txID, index, id) @@ -367,16 +366,15 @@ func (db *TokenStore) queryLedgerTokens(ctx context.Context, details driver.Quer }), nil } -// Balance returns the sum of the amounts of the tokens with type and EID equal to those passed as arguments. -// The result is returned as a *big.Int to support arbitrary precision and prevent overflow. -func (db *TokenStore) Balance(ctx context.Context, walletID string, typ token.Type) (*big.Int, error) { +// Balance returns the sun of the amounts, with 64 bits of precision, of the tokens with type and EID equal to those passed as arguments. +func (db *TokenStore) Balance(ctx context.Context, walletID string, typ token.Type) (uint64, error) { return db.balance(ctx, driver.QueryTokenDetailsParams{ WalletID: walletID, TokenType: typ, }) } -func (db *TokenStore) balance(ctx context.Context, opts driver.QueryTokenDetailsParams) (*big.Int, error) { +func (db *TokenStore) balance(ctx context.Context, opts driver.QueryTokenDetailsParams) (uint64, error) { tokenTable, ownershipTable := q.Table(db.table.Tokens), q.Table(db.table.Ownership) query, args := q.Select().FieldsByName("SUM(amount)"). From(tokenTable.Join(ownershipTable, cond.And( @@ -386,20 +384,12 @@ func (db *TokenStore) balance(ctx context.Context, opts driver.QueryTokenDetails Where(HasTokenDetails(opts, tokenTable)). Format(db.ci) - var sum BigInt - err := db.readDB.QueryRowContext(ctx, query, args...).Scan(&sum) - if err != nil { - if err == sql.ErrNoRows { - return big.NewInt(0), nil - } - - return nil, err - } - if sum.Int == nil { - return big.NewInt(0), nil + sum, err := common.QueryUnique[*uint64](db.readDB, query, args...) + if err != nil || sum == nil { + return 0, err } - return sum.Int, nil + return *sum, nil } // ListUnspentTokensBy returns the list of unspent tokens, filtered by owner and token type @@ -614,7 +604,7 @@ func (db *TokenStore) ListHistoryIssuedTokens(ctx context.Context) (*token.Issue // IssuedBalance returns the sum of amounts of non-deleted tokens flagged as issued. // If tokenType is non-empty, only tokens of that type are included. -func (db *TokenStore) IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw tdriver.Identity) (uint64, error) { +func (db *TokenStore) IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw tdriver.Identity, from, to *time.Time) (uint64, error) { conds := []cond.Condition{cond.Eq("issuer", true), cond.Eq("is_deleted", false)} if len(tokenType) != 0 { conds = append(conds, cond.Eq("token_type", tokenType)) @@ -622,6 +612,12 @@ func (db *TokenStore) IssuedBalance(ctx context.Context, tokenType token.Type, i if len(issuerRaw) != 0 { conds = append(conds, cond.Eq("issuer_raw", issuerRaw)) } + if from != nil { + conds = append(conds, cond.Gte("stored_at", from.UTC())) + } + if to != nil { + conds = append(conds, cond.Lte("stored_at", to.UTC())) + } query, args := q.Select(). FieldsByName("SUM(amount)"). From(q.Table(db.table.Tokens)). @@ -640,7 +636,7 @@ func (db *TokenStore) IssuedBalance(ctx context.Context, tokenType token.Type, i // ListRedeemedTokens returns issued tokens that were spent by a Redeem action. // It JOINs with the transactions table to identify tokens whose spent_by txID // has action_type = Redeem (2). If tokenType is non-empty, only tokens of that type are included. -func (db *TokenStore) ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw tdriver.Identity) (*token.IssuedTokens, error) { +func (db *TokenStore) ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw tdriver.Identity, from, to *time.Time, sortBy tdriver.SortField, sortDirection tdriver.SortDirection) (*token.IssuedTokens, error) { tokTable := q.Table(db.table.Tokens) txTable := q.Table(db.table.Transactions) conds := []cond.Condition{ @@ -649,12 +645,18 @@ func (db *TokenStore) ListRedeemedTokens(ctx context.Context, tokenType token.Ty cond.Eq("action_type", int(driver.Redeem)), } if len(tokenType) != 0 { - conds = append(conds, cond.Eq("token_type", tokenType)) + conds = append(conds, cond.CmpVal(tokTable.Field("token_type"), "=", tokenType)) } if len(issuerRaw) != 0 { conds = append(conds, cond.Eq("issuer_raw", issuerRaw)) } - query, args := q.Select(). + if from != nil { + conds = append(conds, cond.CmpVal(tokTable.Field("stored_at"), ">=", from.UTC())) + } + if to != nil { + conds = append(conds, cond.CmpVal(tokTable.Field("stored_at"), "<=", to.UTC())) + } + sel := q.Select(). Fields( tokTable.Field("tx_id"), tokTable.Field("idx"), tokTable.Field("owner_raw"), tokTable.Field("token_type"), @@ -662,8 +664,18 @@ func (db *TokenStore) ListRedeemedTokens(ctx context.Context, tokenType token.Ty tokTable.Field("is_deleted"), tokTable.Field("spent_by"), ). From(tokTable.Join(txTable, cond.Cmp(tokTable.Field("spent_by"), "=", txTable.Field("tx_id")))). - Where(cond.And(conds...)). - Format(db.ci) + Where(cond.And(conds...)) + var query string + var args []any + if sortBy == tdriver.SortByQuantity { + if sortDirection == tdriver.SortDescending { + query, args = sel.OrderBy(q.Desc(tokTable.Field("amount"))).Format(db.ci) + } else { + query, args = sel.OrderBy(q.Asc(tokTable.Field("amount"))).Format(db.ci) + } + } else { + query, args = sel.Format(db.ci) + } logging.Debug(logger, query, args) rows, err := db.readDB.QueryContext(ctx, query, args...) @@ -684,7 +696,7 @@ func (db *TokenStore) ListRedeemedTokens(ctx context.Context, tokenType token.Ty // RedeemedBalance returns the sum of amounts of issued tokens spent by a Redeem action. // If tokenType is non-empty, only tokens of that type are included. -func (db *TokenStore) RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw tdriver.Identity) (uint64, error) { +func (db *TokenStore) RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw tdriver.Identity, from, to *time.Time) (uint64, error) { tokTable := q.Table(db.table.Tokens) txTable := q.Table(db.table.Transactions) conds := []cond.Condition{ @@ -693,13 +705,19 @@ func (db *TokenStore) RedeemedBalance(ctx context.Context, tokenType token.Type, cond.Eq("action_type", int(driver.Redeem)), } if len(tokenType) != 0 { - conds = append(conds, cond.Eq("token_type", tokenType)) + conds = append(conds, cond.CmpVal(tokTable.Field("token_type"), "=", tokenType)) } if len(issuerRaw) != 0 { conds = append(conds, cond.Eq("issuer_raw", issuerRaw)) } + if from != nil { + conds = append(conds, cond.CmpVal(tokTable.Field("stored_at"), ">=", from.UTC())) + } + if to != nil { + conds = append(conds, cond.CmpVal(tokTable.Field("stored_at"), "<=", to.UTC())) + } query, args := q.Select(). - FieldsByName("SUM(amount)"). + FieldsByName(common3.FieldName("SUM(" + db.table.Tokens + ".amount)")). From(tokTable.Join(txTable, cond.Cmp(tokTable.Field("spent_by"), "=", txTable.Field("tx_id")))). Where(cond.And(conds...)). Format(db.ci) @@ -947,13 +965,7 @@ func (db *TokenStore) QueryTokenDetails(ctx context.Context, params driver.Query } it := common.NewIterator(rows, func(td *driver.TokenDetails) error { - var amount BigInt - if err := rows.Scan(&td.TxID, &td.Index, &td.OwnerIdentity, &td.OwnerType, &td.OwnerEnrollment, &td.Type, &amount, &td.IsSpent, &td.SpentBy, &td.StoredAt); err != nil { - return err - } - td.Amount = amount.Int - - return nil + return rows.Scan(&td.TxID, &td.Index, &td.OwnerIdentity, &td.OwnerType, &td.OwnerEnrollment, &td.Type, &td.Amount, &td.IsSpent, &td.SpentBy, &td.StoredAt) }) return iterators.ReadAllValues(it) @@ -1064,7 +1076,7 @@ func (db *TokenStore) PublicParams(ctx context.Context) ([]byte, error) { Limit(1). Format(db.ci) - return common.QueryUniqueContext[[]byte](ctx, db.readDB, query, args...) + return common.QueryUnique[[]byte](db.readDB, query, args...) } func (db *TokenStore) PublicParamsByHash(ctx context.Context, rawHash tdriver.PPHash) ([]byte, error) { @@ -1074,7 +1086,7 @@ func (db *TokenStore) PublicParamsByHash(ctx context.Context, rawHash tdriver.PP Where(cond.Eq("raw_hash", rawHash)). Format(db.ci) - return common.QueryUniqueContext[[]byte](ctx, db.readDB, query, args...) + return common.QueryUnique[[]byte](db.readDB, query, args...) } func (db *TokenStore) StoreCertifications(ctx context.Context, certifications map[*token.ID][]byte) error { @@ -1112,7 +1124,7 @@ func (db *TokenStore) ExistsCertification(ctx context.Context, tokenID *token.ID Where(HasTokens("tx_id", "idx", tokenID)). Format(db.ci) - certification, err := common.QueryUniqueContext[[]byte](ctx, db.readDB, query, args...) + certification, err := common.QueryUnique[[]byte](db.readDB, query, args...) if err != nil { logger.Warnf("tried to check certification existence for token id %s, err %s", tokenID, err) @@ -1194,7 +1206,7 @@ func (db *TokenStore) GetSchema() string { CREATE TABLE IF NOT EXISTS %s ( tx_id TEXT NOT NULL, idx INT NOT NULL, - amount NUMERIC(78, 0) NOT NULL, + amount BIGINT NOT NULL, token_type TEXT NOT NULL, quantity TEXT NOT NULL, issuer_raw BYTEA, diff --git a/token/services/tokens/mock/token_store.go b/token/services/tokens/mock/token_store.go index bd1241c5c4..286c3dd11c 100644 --- a/token/services/tokens/mock/token_store.go +++ b/token/services/tokens/mock/token_store.go @@ -5,6 +5,7 @@ import ( "context" "math/big" "sync" + "time" drivera "github.com/hyperledger-labs/fabric-token-sdk/token/driver" "github.com/hyperledger-labs/fabric-token-sdk/token/services/storage/db/driver" @@ -163,12 +164,14 @@ type FakeTokenStore struct { result1 bool result2 error } - IssuedBalanceStub func(context.Context, token.Type, drivera.Identity) (uint64, error) + IssuedBalanceStub func(context.Context, token.Type, drivera.Identity, *time.Time, *time.Time) (uint64, error) issuedBalanceMutex sync.RWMutex issuedBalanceArgsForCall []struct { arg1 context.Context arg2 token.Type arg3 drivera.Identity + arg4 *time.Time + arg5 *time.Time } issuedBalanceReturns struct { result1 uint64 @@ -205,12 +208,16 @@ type FakeTokenStore struct { result1 *token.IssuedTokens result2 error } - ListRedeemedTokensStub func(context.Context, token.Type, drivera.Identity) (*token.IssuedTokens, error) + ListRedeemedTokensStub func(context.Context, token.Type, drivera.Identity, *time.Time, *time.Time, drivera.SortField, drivera.SortDirection) (*token.IssuedTokens, error) listRedeemedTokensMutex sync.RWMutex listRedeemedTokensArgsForCall []struct { arg1 context.Context arg2 token.Type arg3 drivera.Identity + arg4 *time.Time + arg5 *time.Time + arg6 drivera.SortField + arg7 drivera.SortDirection } listRedeemedTokensReturns struct { result1 *token.IssuedTokens @@ -328,12 +335,14 @@ type FakeTokenStore struct { result1 []driver.TokenDetails result2 error } - RedeemedBalanceStub func(context.Context, token.Type, drivera.Identity) (uint64, error) + RedeemedBalanceStub func(context.Context, token.Type, drivera.Identity, *time.Time, *time.Time) (uint64, error) redeemedBalanceMutex sync.RWMutex redeemedBalanceArgsForCall []struct { arg1 context.Context arg2 token.Type arg3 drivera.Identity + arg4 *time.Time + arg5 *time.Time } redeemedBalanceReturns struct { result1 uint64 @@ -1206,20 +1215,22 @@ func (fake *FakeTokenStore) IsMineReturnsOnCall(i int, result1 bool, result2 err }{result1, result2} } -func (fake *FakeTokenStore) IssuedBalance(arg1 context.Context, arg2 token.Type, arg3 drivera.Identity) (uint64, error) { +func (fake *FakeTokenStore) IssuedBalance(arg1 context.Context, arg2 token.Type, arg3 drivera.Identity, arg4 *time.Time, arg5 *time.Time) (uint64, error) { fake.issuedBalanceMutex.Lock() ret, specificReturn := fake.issuedBalanceReturnsOnCall[len(fake.issuedBalanceArgsForCall)] fake.issuedBalanceArgsForCall = append(fake.issuedBalanceArgsForCall, struct { arg1 context.Context arg2 token.Type arg3 drivera.Identity - }{arg1, arg2, arg3}) + arg4 *time.Time + arg5 *time.Time + }{arg1, arg2, arg3, arg4, arg5}) stub := fake.IssuedBalanceStub fakeReturns := fake.issuedBalanceReturns - fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2, arg3}) + fake.recordInvocation("IssuedBalance", []interface{}{arg1, arg2, arg3, arg4, arg5}) fake.issuedBalanceMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3) + return stub(arg1, arg2, arg3, arg4, arg5) } if specificReturn { return ret.result1, ret.result2 @@ -1233,17 +1244,17 @@ func (fake *FakeTokenStore) IssuedBalanceCallCount() int { return len(fake.issuedBalanceArgsForCall) } -func (fake *FakeTokenStore) IssuedBalanceCalls(stub func(context.Context, token.Type, drivera.Identity) (uint64, error)) { +func (fake *FakeTokenStore) IssuedBalanceCalls(stub func(context.Context, token.Type, drivera.Identity, *time.Time, *time.Time) (uint64, error)) { fake.issuedBalanceMutex.Lock() defer fake.issuedBalanceMutex.Unlock() fake.IssuedBalanceStub = stub } -func (fake *FakeTokenStore) IssuedBalanceArgsForCall(i int) (context.Context, token.Type, drivera.Identity) { +func (fake *FakeTokenStore) IssuedBalanceArgsForCall(i int) (context.Context, token.Type, drivera.Identity, *time.Time, *time.Time) { fake.issuedBalanceMutex.RLock() defer fake.issuedBalanceMutex.RUnlock() argsForCall := fake.issuedBalanceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 } func (fake *FakeTokenStore) IssuedBalanceReturns(result1 uint64, result2 error) { @@ -1401,20 +1412,24 @@ func (fake *FakeTokenStore) ListHistoryIssuedTokensReturnsOnCall(i int, result1 }{result1, result2} } -func (fake *FakeTokenStore) ListRedeemedTokens(arg1 context.Context, arg2 token.Type, arg3 drivera.Identity) (*token.IssuedTokens, error) { +func (fake *FakeTokenStore) ListRedeemedTokens(arg1 context.Context, arg2 token.Type, arg3 drivera.Identity, arg4 *time.Time, arg5 *time.Time, arg6 drivera.SortField, arg7 drivera.SortDirection) (*token.IssuedTokens, error) { fake.listRedeemedTokensMutex.Lock() ret, specificReturn := fake.listRedeemedTokensReturnsOnCall[len(fake.listRedeemedTokensArgsForCall)] fake.listRedeemedTokensArgsForCall = append(fake.listRedeemedTokensArgsForCall, struct { arg1 context.Context arg2 token.Type arg3 drivera.Identity - }{arg1, arg2, arg3}) + arg4 *time.Time + arg5 *time.Time + arg6 drivera.SortField + arg7 drivera.SortDirection + }{arg1, arg2, arg3, arg4, arg5, arg6, arg7}) stub := fake.ListRedeemedTokensStub fakeReturns := fake.listRedeemedTokensReturns - fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2, arg3}) + fake.recordInvocation("ListRedeemedTokens", []interface{}{arg1, arg2, arg3, arg4, arg5, arg6, arg7}) fake.listRedeemedTokensMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3) + return stub(arg1, arg2, arg3, arg4, arg5, arg6, arg7) } if specificReturn { return ret.result1, ret.result2 @@ -1428,17 +1443,17 @@ func (fake *FakeTokenStore) ListRedeemedTokensCallCount() int { return len(fake.listRedeemedTokensArgsForCall) } -func (fake *FakeTokenStore) ListRedeemedTokensCalls(stub func(context.Context, token.Type, drivera.Identity) (*token.IssuedTokens, error)) { +func (fake *FakeTokenStore) ListRedeemedTokensCalls(stub func(context.Context, token.Type, drivera.Identity, *time.Time, *time.Time, drivera.SortField, drivera.SortDirection) (*token.IssuedTokens, error)) { fake.listRedeemedTokensMutex.Lock() defer fake.listRedeemedTokensMutex.Unlock() fake.ListRedeemedTokensStub = stub } -func (fake *FakeTokenStore) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type, drivera.Identity) { +func (fake *FakeTokenStore) ListRedeemedTokensArgsForCall(i int) (context.Context, token.Type, drivera.Identity, *time.Time, *time.Time, drivera.SortField, drivera.SortDirection) { fake.listRedeemedTokensMutex.RLock() defer fake.listRedeemedTokensMutex.RUnlock() argsForCall := fake.listRedeemedTokensArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5, argsForCall.arg6, argsForCall.arg7 } func (fake *FakeTokenStore) ListRedeemedTokensReturns(result1 *token.IssuedTokens, result2 error) { @@ -1974,20 +1989,22 @@ func (fake *FakeTokenStore) QueryTokenDetailsReturnsOnCall(i int, result1 []driv }{result1, result2} } -func (fake *FakeTokenStore) RedeemedBalance(arg1 context.Context, arg2 token.Type, arg3 drivera.Identity) (uint64, error) { +func (fake *FakeTokenStore) RedeemedBalance(arg1 context.Context, arg2 token.Type, arg3 drivera.Identity, arg4 *time.Time, arg5 *time.Time) (uint64, error) { fake.redeemedBalanceMutex.Lock() ret, specificReturn := fake.redeemedBalanceReturnsOnCall[len(fake.redeemedBalanceArgsForCall)] fake.redeemedBalanceArgsForCall = append(fake.redeemedBalanceArgsForCall, struct { arg1 context.Context arg2 token.Type arg3 drivera.Identity - }{arg1, arg2, arg3}) + arg4 *time.Time + arg5 *time.Time + }{arg1, arg2, arg3, arg4, arg5}) stub := fake.RedeemedBalanceStub fakeReturns := fake.redeemedBalanceReturns - fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2, arg3}) + fake.recordInvocation("RedeemedBalance", []interface{}{arg1, arg2, arg3, arg4, arg5}) fake.redeemedBalanceMutex.Unlock() if stub != nil { - return stub(arg1, arg2, arg3) + return stub(arg1, arg2, arg3, arg4, arg5) } if specificReturn { return ret.result1, ret.result2 @@ -2001,17 +2018,17 @@ func (fake *FakeTokenStore) RedeemedBalanceCallCount() int { return len(fake.redeemedBalanceArgsForCall) } -func (fake *FakeTokenStore) RedeemedBalanceCalls(stub func(context.Context, token.Type, drivera.Identity) (uint64, error)) { +func (fake *FakeTokenStore) RedeemedBalanceCalls(stub func(context.Context, token.Type, drivera.Identity, *time.Time, *time.Time) (uint64, error)) { fake.redeemedBalanceMutex.Lock() defer fake.redeemedBalanceMutex.Unlock() fake.RedeemedBalanceStub = stub } -func (fake *FakeTokenStore) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type, drivera.Identity) { +func (fake *FakeTokenStore) RedeemedBalanceArgsForCall(i int) (context.Context, token.Type, drivera.Identity, *time.Time, *time.Time) { fake.redeemedBalanceMutex.RLock() defer fake.redeemedBalanceMutex.RUnlock() argsForCall := fake.redeemedBalanceArgsForCall[i] - return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5 } func (fake *FakeTokenStore) RedeemedBalanceReturns(result1 uint64, result2 error) { diff --git a/token/vault.go b/token/vault.go index 0402b16d0e..375b41d37f 100644 --- a/token/vault.go +++ b/token/vault.go @@ -123,16 +123,16 @@ func (q *QueryEngine) ListHistoryIssuedTokens(ctx context.Context) (*token.Issue return q.qe.ListHistoryIssuedTokens(ctx) } -func (q *QueryEngine) IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity) (uint64, error) { - return q.qe.IssuedBalance(ctx, tokenType, issuerRaw) +func (q *QueryEngine) IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity, from, to *time.Time) (uint64, error) { + return q.qe.IssuedBalance(ctx, tokenType, issuerRaw, from, to) } -func (q *QueryEngine) ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity) (*token.IssuedTokens, error) { - return q.qe.ListRedeemedTokens(ctx, tokenType, issuerRaw) +func (q *QueryEngine) ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity, from, to *time.Time, sortBy driver.SortField, sortDirection driver.SortDirection) (*token.IssuedTokens, error) { + return q.qe.ListRedeemedTokens(ctx, tokenType, issuerRaw, from, to, sortBy, sortDirection) } -func (q *QueryEngine) RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity) (uint64, error) { - return q.qe.RedeemedBalance(ctx, tokenType, issuerRaw) +func (q *QueryEngine) RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity, from, to *time.Time) (uint64, error) { + return q.qe.RedeemedBalance(ctx, tokenType, issuerRaw, from, to) } // PublicParams returns the public parameters stored in the vault diff --git a/token/wallet.go b/token/wallet.go index cf77ba1146..cbbe2c1717 100644 --- a/token/wallet.go +++ b/token/wallet.go @@ -9,7 +9,7 @@ package token import ( "context" - "math/big" + "time" "github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors" "github.com/hyperledger-labs/fabric-token-sdk/token/driver" @@ -37,28 +37,46 @@ func WithType(tokenType token.Type) ListTokensOption { } } -<<<<<<< HEAD -======= -// WithContext return a list tokens option that contains the passed context -func WithContext(ctx context.Context) ListTokensOption { +// WithSortBy returns a list token option that specifies the sort field and direction. +func WithSortBy(field driver.SortField, dir driver.SortDirection) ListTokensOption { return func(o *ListTokensOptions) error { - o.Context = ctx + o.SortBy = field + o.SortDirection = dir return nil } } -// WithSortBy returns a list token option that specifies the sort field and direction. -func WithSortBy(field driver.SortField, dir driver.SortDirection) ListTokensOption { - return func(o *ListTokensOptions) error { - o.SortBy = field - o.SortDirection = dir +// BalanceOpts contains options for balance queries (methods that return a scalar +// total rather than a list of tokens). Sort fields are intentionally excluded +// because the result is a single number. +type BalanceOpts = driver.BalanceOpts + +// BalanceOption is a functional option that configures a BalanceOpts struct. +// Use WithBalanceTokenType and WithTimeRange to construct values. +type BalanceOption func(*BalanceOpts) error + +// WithBalanceTokenType returns a BalanceOption that restricts the balance +// computation to the given token type. An empty token type means all types. +func WithBalanceTokenType(tokenType token.Type) BalanceOption { + return func(o *BalanceOpts) error { + o.TokenType = tokenType + + return nil + } +} + +// WithTimeRange returns a balance option that restricts the computation to +// tokens stored within [from, to]. Nil values are treated as unbounded. +func WithTimeRange(from, to *time.Time) BalanceOption { + return func(o *BalanceOpts) error { + o.From = from + o.To = to return nil } } ->>>>>>> f26e9668 (Add sorting options to ListTokensOptions) type IdentityConfiguration = driver.IdentityConfiguration // WalletManager defines the interface for managing wallets. @@ -313,16 +331,15 @@ func (o *OwnerWallet) ListUnspentTokensIterator(ctx context.Context, opts ...Lis return &UnspentTokensIterator{UnspentTokensIterator: it}, nil } -// Balance returns the sum of the amounts of the tokens with type and EID equal to those passed as arguments. -// The result is returned as a *big.Int to support arbitrary precision and prevent overflow. -func (o *OwnerWallet) Balance(ctx context.Context, opts ...ListTokensOption) (*big.Int, error) { - compiledOpts, err := CompileListTokensOption(opts...) +// Balance returns the sun of the amounts, with 64 bits of precision, of the tokens with type and EID equal to those passed as arguments. +func (o *OwnerWallet) Balance(ctx context.Context, opts ...BalanceOption) (uint64, error) { + compiledOpts, err := CompileBalanceOption(opts...) if err != nil { - return nil, err + return 0, err } sum, err := o.w.Balance(ctx, compiledOpts) if err != nil { - return nil, err + return 0, err } return sum, nil @@ -372,9 +389,9 @@ func (i *IssuerWallet) ListIssuedTokens(ctx context.Context, opts ...ListTokensO } // IssuedBalance returns the total amount of non-redeemed tokens issued by this wallet. -// Options: WithType -func (i *IssuerWallet) IssuedBalance(ctx context.Context, opts ...ListTokensOption) (uint64, error) { - compiledOpts, err := CompileListTokensOption(opts...) +// Options: WithBalanceTokenType, WithTimeRange +func (i *IssuerWallet) IssuedBalance(ctx context.Context, opts ...BalanceOption) (uint64, error) { + compiledOpts, err := CompileBalanceOption(opts...) if err != nil { return 0, err } @@ -394,9 +411,9 @@ func (i *IssuerWallet) ListRedeemedTokens(ctx context.Context, opts ...ListToken } // RedeemedBalance returns the total amount of redeemed tokens originally issued by this wallet. -// Options: WithType -func (i *IssuerWallet) RedeemedBalance(ctx context.Context, opts ...ListTokensOption) (uint64, error) { - compiledOpts, err := CompileListTokensOption(opts...) +// Options: WithBalanceTokenType, WithTimeRange +func (i *IssuerWallet) RedeemedBalance(ctx context.Context, opts ...BalanceOption) (uint64, error) { + compiledOpts, err := CompileBalanceOption(opts...) if err != nil { return 0, err } @@ -404,6 +421,17 @@ func (i *IssuerWallet) RedeemedBalance(ctx context.Context, opts ...ListTokensOp return i.w.RedeemedBalance(ctx, compiledOpts) } +// OutstandingBalance returns the net amount still in circulation: IssuedBalance − RedeemedBalance. +// Options: WithBalanceTokenType, WithTimeRange +func (i *IssuerWallet) OutstandingBalance(ctx context.Context, opts ...BalanceOption) (uint64, error) { + compiledOpts, err := CompileBalanceOption(opts...) + if err != nil { + return 0, err + } + + return i.w.OutstandingBalance(ctx, compiledOpts) +} + func CompileListTokensOption(opts ...ListTokensOption) (*driver.ListTokensOptions, error) { txOptions := &ListTokensOptions{} for _, opt := range opts { @@ -413,13 +441,21 @@ func CompileListTokensOption(opts ...ListTokensOption) (*driver.ListTokensOption } return &driver.ListTokensOptions{ -<<<<<<< HEAD - TokenType: txOptions.TokenType, -======= TokenType: txOptions.TokenType, - Context: txOptions.Context, SortBy: txOptions.SortBy, SortDirection: txOptions.SortDirection, ->>>>>>> f26e9668 (Add sorting options to ListTokensOptions) }, nil } + +// CompileBalanceOption compiles variadic BalanceOption values into a single +// BalanceOpts struct ready to be passed to a driver-level balance method. +func CompileBalanceOption(opts ...BalanceOption) (*driver.BalanceOpts, error) { + bo := &driver.BalanceOpts{} + for _, opt := range opts { + if err := opt(bo); err != nil { + return nil, err + } + } + + return bo, nil +} From e93c0112df7676fc7b338ef5f444bf74a78f34d0 Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Tue, 5 May 2026 02:56:56 +0530 Subject: [PATCH 13/27] Extend fungible integration test with issuer balance APIs Add IssuedBalanceView, RedeemedBalanceView, and OutstandingBalanceView with corresponding view factories, support helpers (CheckIssuedBalance, CheckRedeemedBalance, CheckOutstandingBalance), and two assertion blocks in TestAll that exercise the new APIs after issue+redeem cycles. Signed-off-by: Soumya Mohapatra Signed-off-by: Soumya Mohapatra --- integration/token/fungible/sdk/issuer/sdk.go | 3 + integration/token/fungible/support.go | 30 +++++++ integration/token/fungible/tests.go | 16 ++++ integration/token/fungible/views/history.go | 82 ++++++++++++++++++++ 4 files changed, 131 insertions(+) diff --git a/integration/token/fungible/sdk/issuer/sdk.go b/integration/token/fungible/sdk/issuer/sdk.go index bd67b2e25a..31a9798d85 100644 --- a/integration/token/fungible/sdk/issuer/sdk.go +++ b/integration/token/fungible/sdk/issuer/sdk.go @@ -39,6 +39,9 @@ func (p *SDK) Install() error { registry.RegisterFactory("balance", &views.BalanceViewFactory{}), registry.RegisterFactory("historyIssuedToken", &views.ListIssuedTokensViewFactory{}), registry.RegisterFactory("issuedTokenQuery", &views.ListIssuedTokensViewFactory{}), + registry.RegisterFactory("issuedBalance", &views.IssuedBalanceViewFactory{}), + registry.RegisterFactory("redeemedBalance", &views.RedeemedBalanceViewFactory{}), + registry.RegisterFactory("outstandingBalance", &views.OutstandingBalanceViewFactory{}), registry.RegisterFactory("GetEnrollmentID", &views.GetEnrollmentIDViewFactory{}), registry.RegisterFactory("acceptedTransactionHistory", &views.ListAcceptedTransactionsViewFactory{}), registry.RegisterFactory("transactionInfo", &views.TransactionInfoViewFactory{}), diff --git a/integration/token/fungible/support.go b/integration/token/fungible/support.go index f450a407e1..4409e283e8 100644 --- a/integration/token/fungible/support.go +++ b/integration/token/fungible/support.go @@ -423,6 +423,36 @@ func ListUnspentTokens(network *integration.Infrastructure, id *token3.NodeRefer return ListUnspentTokensForTMSID(network, id, wallet, typ, nil) } +func CheckIssuedBalance(network *integration.Infrastructure, issuer *token3.NodeReference, wallet string, typ token.Type, expected uint64) { + res, err := network.Client(issuer.ReplicaName()).CallView("issuedBalance", common.JSONMarshall(&views.IssuerBalanceQuery{ + Wallet: wallet, + TokenType: typ, + })) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + balance := res.(uint64) + gomega.Expect(balance).To(gomega.Equal(expected), "issued balance: got %d, expected %d", balance, expected) +} + +func CheckRedeemedBalance(network *integration.Infrastructure, issuer *token3.NodeReference, wallet string, typ token.Type, expected uint64) { + res, err := network.Client(issuer.ReplicaName()).CallView("redeemedBalance", common.JSONMarshall(&views.IssuerBalanceQuery{ + Wallet: wallet, + TokenType: typ, + })) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + balance := res.(uint64) + gomega.Expect(balance).To(gomega.Equal(expected), "redeemed balance: got %d, expected %d", balance, expected) +} + +func CheckOutstandingBalance(network *integration.Infrastructure, issuer *token3.NodeReference, wallet string, typ token.Type, expected uint64) { + res, err := network.Client(issuer.ReplicaName()).CallView("outstandingBalance", common.JSONMarshall(&views.IssuerBalanceQuery{ + Wallet: wallet, + TokenType: typ, + })) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + balance := res.(uint64) + gomega.Expect(balance).To(gomega.Equal(expected), "outstanding balance: got %d, expected %d", balance, expected) +} + func ListUnspentTokensForTMSID(network *integration.Infrastructure, id *token3.NodeReference, wallet string, typ token.Type, tmsId *token2.TMSID) *token.UnspentTokens { res, err := network.Client(id.ReplicaName()).CallView("history", common.JSONMarshall(&views.ListUnspentTokens{ Wallet: wallet, diff --git a/integration/token/fungible/tests.go b/integration/token/fungible/tests.go index 0f16252db5..a43427f6a3 100644 --- a/integration/token/fungible/tests.go +++ b/integration/token/fungible/tests.go @@ -445,6 +445,12 @@ func TestAll(network *integration.Infrastructure, auditorId string, onRestart On CheckAuditedTransactions(network, auditor, AuditedTransactions[:10], &t0, &t12) CheckSpending(network, bob, "", "USD", auditor, 11) + // Verify issuer balance APIs after issue + redeem cycle. + // Default issuer wallet issued 110 + 10 (withdraw) + 10 (post-redeem) = 130 USD; redeemed 11 USD. + CheckIssuedBalance(network, issuer, "", "USD", 130) + CheckRedeemedBalance(network, issuer, "", "USD", 11) + CheckOutstandingBalance(network, issuer, "", "USD", 119) + // test multi action transfer... t13 := time.Now() IssueCash(network, "", "LIRA", 3, alice, auditor, true, issuer) @@ -506,6 +512,16 @@ func TestAll(network *integration.Infrastructure, auditorId string, onRestart On gomega.Expect(h.Sum(64).ToBigInt().Cmp(big.NewInt(180))).To(gomega.BeEquivalentTo(0)) gomega.Expect(h.ByType("EUR").Count()).To(gomega.BeEquivalentTo(h.Count())) + // Verify issuer balances after all issue and redeem operations. + // Default issuer: 241 USD issued, 21 redeemed (11 + 10). + CheckIssuedBalance(network, issuer, "", "USD", 241) + CheckRedeemedBalance(network, issuer, "", "USD", 21) + CheckOutstandingBalance(network, issuer, "", "USD", 220) + // New issuer wallet: 180 EUR issued, 0 redeemed. + CheckIssuedBalance(network, issuer, "newIssuerWallet", "EUR", 180) + CheckRedeemedBalance(network, issuer, "newIssuerWallet", "EUR", 0) + CheckOutstandingBalance(network, issuer, "newIssuerWallet", "EUR", 180) + CheckBalanceAndHolding(network, issuer, "", "USD", 110, auditor) CheckBalanceAndHolding(network, issuer, "", "EUR", 150, auditor) CheckBalanceAndHolding(network, issuer, "issuer.owner", "EUR", 10, auditor) diff --git a/integration/token/fungible/views/history.go b/integration/token/fungible/views/history.go index 87971f4f8e..9f312a8cf0 100644 --- a/integration/token/fungible/views/history.go +++ b/integration/token/fungible/views/history.go @@ -181,3 +181,85 @@ func (p *TransactionInfoViewFactory) NewView(in []byte) (view.View, error) { return f, nil } + +// IssuerBalanceQuery contains the input parameters for issuer balance views. +type IssuerBalanceQuery struct { + Wallet string + TokenType token2.Type + TMSID *token.TMSID +} + +// IssuedBalanceView returns the total amount of non-redeemed tokens issued by the given wallet. +type IssuedBalanceView struct { + *IssuerBalanceQuery +} + +func (p *IssuedBalanceView) Call(context view.Context) (interface{}, error) { + wallet := ttx.GetIssuerWallet(context, p.Wallet, ServiceOpts(p.TMSID)...) + if wallet == nil { + return nil, errors.Errorf("wallet [%s] not found", p.Wallet) + } + + return wallet.IssuedBalance(context.Context(), token.WithBalanceType(p.TokenType)) +} + +type IssuedBalanceViewFactory struct{} + +func (f *IssuedBalanceViewFactory) NewView(in []byte) (view.View, error) { + v := &IssuedBalanceView{IssuerBalanceQuery: &IssuerBalanceQuery{}} + if err := json.Unmarshal(in, v.IssuerBalanceQuery); err != nil { + return nil, errors.Wrapf(err, "failed unmarshalling input") + } + + return v, nil +} + +// RedeemedBalanceView returns the total amount of redeemed tokens originally issued by the given wallet. +type RedeemedBalanceView struct { + *IssuerBalanceQuery +} + +func (p *RedeemedBalanceView) Call(context view.Context) (interface{}, error) { + wallet := ttx.GetIssuerWallet(context, p.Wallet, ServiceOpts(p.TMSID)...) + if wallet == nil { + return nil, errors.Errorf("wallet [%s] not found", p.Wallet) + } + + return wallet.RedeemedBalance(context.Context(), token.WithBalanceType(p.TokenType)) +} + +type RedeemedBalanceViewFactory struct{} + +func (f *RedeemedBalanceViewFactory) NewView(in []byte) (view.View, error) { + v := &RedeemedBalanceView{IssuerBalanceQuery: &IssuerBalanceQuery{}} + if err := json.Unmarshal(in, v.IssuerBalanceQuery); err != nil { + return nil, errors.Wrapf(err, "failed unmarshalling input") + } + + return v, nil +} + +// OutstandingBalanceView returns IssuedBalance − RedeemedBalance for the given wallet. +type OutstandingBalanceView struct { + *IssuerBalanceQuery +} + +func (p *OutstandingBalanceView) Call(context view.Context) (interface{}, error) { + wallet := ttx.GetIssuerWallet(context, p.Wallet, ServiceOpts(p.TMSID)...) + if wallet == nil { + return nil, errors.Errorf("wallet [%s] not found", p.Wallet) + } + + return wallet.OutstandingBalance(context.Context(), token.WithBalanceType(p.TokenType)) +} + +type OutstandingBalanceViewFactory struct{} + +func (f *OutstandingBalanceViewFactory) NewView(in []byte) (view.View, error) { + v := &OutstandingBalanceView{IssuerBalanceQuery: &IssuerBalanceQuery{}} + if err := json.Unmarshal(in, v.IssuerBalanceQuery); err != nil { + return nil, errors.Wrapf(err, "failed unmarshalling input") + } + + return v, nil +} From 2015802d77de13dd2891ead25957fdeb8707db04 Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Wed, 6 May 2026 00:18:55 +0530 Subject: [PATCH 14/27] Exercise WithSortBy and WithTimeRange in tests; rename WithBalanceType; add GoDocs Signed-off-by: Soumya Mohapatra Signed-off-by: Soumya Mohapatra --- integration/token/fungible/views/history.go | 6 +-- token/vault.go | 6 +++ token/wallet_test.go | 46 +++++++++++++++++++++ 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/integration/token/fungible/views/history.go b/integration/token/fungible/views/history.go index 9f312a8cf0..241b33a8fd 100644 --- a/integration/token/fungible/views/history.go +++ b/integration/token/fungible/views/history.go @@ -200,7 +200,7 @@ func (p *IssuedBalanceView) Call(context view.Context) (interface{}, error) { return nil, errors.Errorf("wallet [%s] not found", p.Wallet) } - return wallet.IssuedBalance(context.Context(), token.WithBalanceType(p.TokenType)) + return wallet.IssuedBalance(context.Context(), token.WithBalanceTokenType(p.TokenType)) } type IssuedBalanceViewFactory struct{} @@ -225,7 +225,7 @@ func (p *RedeemedBalanceView) Call(context view.Context) (interface{}, error) { return nil, errors.Errorf("wallet [%s] not found", p.Wallet) } - return wallet.RedeemedBalance(context.Context(), token.WithBalanceType(p.TokenType)) + return wallet.RedeemedBalance(context.Context(), token.WithBalanceTokenType(p.TokenType)) } type RedeemedBalanceViewFactory struct{} @@ -250,7 +250,7 @@ func (p *OutstandingBalanceView) Call(context view.Context) (interface{}, error) return nil, errors.Errorf("wallet [%s] not found", p.Wallet) } - return wallet.OutstandingBalance(context.Context(), token.WithBalanceType(p.TokenType)) + return wallet.OutstandingBalance(context.Context(), token.WithBalanceTokenType(p.TokenType)) } type OutstandingBalanceViewFactory struct{} diff --git a/token/vault.go b/token/vault.go index 375b41d37f..3906962437 100644 --- a/token/vault.go +++ b/token/vault.go @@ -123,14 +123,20 @@ func (q *QueryEngine) ListHistoryIssuedTokens(ctx context.Context) (*token.Issue return q.qe.ListHistoryIssuedTokens(ctx) } +// IssuedBalance returns the total amount of tokens issued by the given issuer that have not been redeemed, +// optionally filtered by token type and time range. func (q *QueryEngine) IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity, from, to *time.Time) (uint64, error) { return q.qe.IssuedBalance(ctx, tokenType, issuerRaw, from, to) } +// ListRedeemedTokens returns the list of redeemed tokens originally issued by the given issuer, +// optionally filtered by token type, time range, and sorted by the specified field and direction. func (q *QueryEngine) ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity, from, to *time.Time, sortBy driver.SortField, sortDirection driver.SortDirection) (*token.IssuedTokens, error) { return q.qe.ListRedeemedTokens(ctx, tokenType, issuerRaw, from, to, sortBy, sortDirection) } +// RedeemedBalance returns the total amount of redeemed tokens originally issued by the given issuer, +// optionally filtered by token type and time range. func (q *QueryEngine) RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw driver.Identity, from, to *time.Time) (uint64, error) { return q.qe.RedeemedBalance(ctx, tokenType, issuerRaw, from, to) } diff --git a/token/wallet_test.go b/token/wallet_test.go index f48bfeff6c..59ec775f6c 100644 --- a/token/wallet_test.go +++ b/token/wallet_test.go @@ -11,6 +11,7 @@ import ( "context" "math/big" "testing" + "time" "github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors" "github.com/hyperledger-labs/fabric-token-sdk/token/driver" @@ -600,6 +601,51 @@ func TestIssuerWallet_ListIssuedTokens(t *testing.T) { assert.Equal(t, expectedTokens, tokens) } +// TestIssuerWallet_ListIssuedTokens_WithSortBy verifies that WithSortBy passes the sort +// field and direction through to the driver. +func TestIssuerWallet_ListIssuedTokens_WithSortBy(t *testing.T) { + mockIW := &mock.IssuerWallet{} + expectedTokens := &token.IssuedTokens{} + mockIW.HistoryTokensReturns(expectedTokens, nil) + + wallet := &IssuerWallet{w: mockIW} + ctx := context.Background() + + tokens, err := wallet.ListIssuedTokens(ctx, WithType("USD"), WithSortBy(driver.SortByQuantity, driver.SortDescending)) + + require.NoError(t, err) + assert.Equal(t, expectedTokens, tokens) + + // Verify the compiled options include sort fields. + _, opts := mockIW.HistoryTokensArgsForCall(0) + assert.Equal(t, driver.SortByQuantity, opts.SortBy) + assert.Equal(t, driver.SortDescending, opts.SortDirection) +} + +// TestOwnerWallet_Balance_WithTimeRange verifies that WithTimeRange passes the +// time bounds through to the driver. +func TestOwnerWallet_Balance_WithTimeRange(t *testing.T) { + mockOW := &mock.OwnerWallet{} + mockOW.BalanceReturns(uint64(500), nil) + + wallet := &OwnerWallet{w: mockOW} + ctx := context.Background() + + from := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC) + to := time.Date(2025, 12, 31, 23, 59, 59, 0, time.UTC) + + balance, err := wallet.Balance(ctx, WithBalanceTokenType("USD"), WithTimeRange(&from, &to)) + + require.NoError(t, err) + assert.Equal(t, uint64(500), balance) + + // Verify the compiled options include time range. + _, opts := mockOW.BalanceArgsForCall(0) + assert.Equal(t, &from, opts.From) + assert.Equal(t, &to, opts.To) + assert.Equal(t, token.Type("USD"), opts.TokenType) +} + // TestWalletManager_OwnerWalletIDs_Error verifies error handling func TestWalletManager_OwnerWalletIDs_Error(t *testing.T) { mockWS := &mock.WalletService{} From e3812bc684128e5399651b81f8d8e7502e7bf4a3 Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Fri, 8 May 2026 19:01:37 +0530 Subject: [PATCH 15/27] fix: unmarshal CallView result as []byte before asserting uint64 CallView returns JSON-encoded []byte, not the raw Go type. The Check*Balance functions were doing res.(uint64) which panics because res is actually []byte. Use JSONUnmarshalUint64 helper (analogous to existing JSONUnmarshalFloat64) to properly decode the result. Signed-off-by: Soumya Mohapatra --- integration/token/fungible/support.go | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/integration/token/fungible/support.go b/integration/token/fungible/support.go index 4409e283e8..3a61c450d8 100644 --- a/integration/token/fungible/support.go +++ b/integration/token/fungible/support.go @@ -429,7 +429,7 @@ func CheckIssuedBalance(network *integration.Infrastructure, issuer *token3.Node TokenType: typ, })) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - balance := res.(uint64) + balance := JSONUnmarshalUint64(res) gomega.Expect(balance).To(gomega.Equal(expected), "issued balance: got %d, expected %d", balance, expected) } @@ -439,7 +439,7 @@ func CheckRedeemedBalance(network *integration.Infrastructure, issuer *token3.No TokenType: typ, })) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - balance := res.(uint64) + balance := JSONUnmarshalUint64(res) gomega.Expect(balance).To(gomega.Equal(expected), "redeemed balance: got %d, expected %d", balance, expected) } @@ -449,7 +449,7 @@ func CheckOutstandingBalance(network *integration.Infrastructure, issuer *token3 TokenType: typ, })) gomega.Expect(err).NotTo(gomega.HaveOccurred()) - balance := res.(uint64) + balance := JSONUnmarshalUint64(res) gomega.Expect(balance).To(gomega.Equal(expected), "outstanding balance: got %d, expected %d", balance, expected) } @@ -1267,6 +1267,22 @@ func JSONUnmarshalFloat64(v any) float64 { return s } +func JSONUnmarshalUint64(v interface{}) uint64 { + var s uint64 + switch v := v.(type) { + case []byte: + err := json.Unmarshal(v, &s) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + case string: + err := json.Unmarshal([]byte(v), &s) + gomega.Expect(err).NotTo(gomega.HaveOccurred()) + default: + panic(fmt.Sprintf("type not recognized [%T]", v)) + } + + return s +} + type deleteVaultPlatform interface { DeleteVault(id string) } From 664275c4338e9bfbcc7d6a722768a9622dad65db Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Mon, 11 May 2026 13:11:15 +0530 Subject: [PATCH 16/27] fix: use self-JOIN on tokens table for redeemed token queries ListRedeemedTokens and RedeemedBalance previously JOINed with the transactions table to identify tokens spent by a Redeem action. However, the issuer node does not have entries in the transactions table for redeem transactions initiated by other nodes, because StoreTransactionRecords is only called by view initiators/responders and the issuer is not in the redeem distribution list. Instead, identify redeemed tokens by LEFT JOINing the tokens table with itself: a redeemed token (issuer=true, is_deleted=true) has a spent_by tx_id that has NO output tokens in the tokens table (Parse() skips redeem outputs with empty owner), whereas a transferred token's spent_by tx_id DOES have output tokens. This fixes all 8 failing CI integration tests that were panicking with 'close of closed channel' due to the view assertion failure when CheckRedeemedBalance returned 0 instead of the expected value. Signed-off-by: Soumya Mohapatra --- .../services/storage/db/sql/common/tokens.go | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/token/services/storage/db/sql/common/tokens.go b/token/services/storage/db/sql/common/tokens.go index cc05269d6e..da8f6547ea 100644 --- a/token/services/storage/db/sql/common/tokens.go +++ b/token/services/storage/db/sql/common/tokens.go @@ -633,22 +633,38 @@ func (db *TokenStore) IssuedBalance(ctx context.Context, tokenType token.Type, i return *sum, nil } +// nullCond is a condition that checks if a field IS NULL. +type nullCond struct { + f common3.Field +} + +func (c *nullCond) WriteString(_ common3.CondInterpreter, sb common3.Builder) { + sb.WriteSerializables(c.f).WriteString(" IS NULL") +} + +func isNull(f common3.Field) cond.Condition { + return &nullCond{f: f} +} + // ListRedeemedTokens returns issued tokens that were spent by a Redeem action. -// It JOINs with the transactions table to identify tokens whose spent_by txID -// has action_type = Redeem (2). If tokenType is non-empty, only tokens of that type are included. +// A redeemed token is identified as: issuer=true, is_deleted=true, spent_by is set, +// and the spending tx_id has NO output tokens in the tokens table (because Parse() +// skips redeem outputs with empty owner). This avoids relying on the transactions +// table, which may not have entries on the issuer node. func (db *TokenStore) ListRedeemedTokens(ctx context.Context, tokenType token.Type, issuerRaw tdriver.Identity, from, to *time.Time, sortBy tdriver.SortField, sortDirection tdriver.SortDirection) (*token.IssuedTokens, error) { tokTable := q.Table(db.table.Tokens) - txTable := q.Table(db.table.Transactions) + // Alias the same tokens table to check for output tokens of the spending tx. + t2 := q.AliasedTable(db.table.Tokens, "t2") conds := []cond.Condition{ - cond.Eq("issuer", true), - cond.Eq("is_deleted", true), - cond.Eq("action_type", int(driver.Redeem)), + cond.CmpVal(tokTable.Field("issuer"), "=", true), + cond.CmpVal(tokTable.Field("is_deleted"), "=", true), + isNull(t2.Field("tx_id")), } if len(tokenType) != 0 { conds = append(conds, cond.CmpVal(tokTable.Field("token_type"), "=", tokenType)) } if len(issuerRaw) != 0 { - conds = append(conds, cond.Eq("issuer_raw", issuerRaw)) + conds = append(conds, cond.CmpVal(tokTable.Field("issuer_raw"), "=", issuerRaw)) } if from != nil { conds = append(conds, cond.CmpVal(tokTable.Field("stored_at"), ">=", from.UTC())) @@ -663,7 +679,7 @@ func (db *TokenStore) ListRedeemedTokens(ctx context.Context, tokenType token.Ty tokTable.Field("quantity"), tokTable.Field("issuer_raw"), tokTable.Field("is_deleted"), tokTable.Field("spent_by"), ). - From(tokTable.Join(txTable, cond.Cmp(tokTable.Field("spent_by"), "=", txTable.Field("tx_id")))). + From(tokTable.JoinAs(common3.Left, t2, cond.Cmp(t2.Field("tx_id"), "=", tokTable.Field("spent_by")))). Where(cond.And(conds...)) var query string var args []any @@ -695,20 +711,21 @@ func (db *TokenStore) ListRedeemedTokens(ctx context.Context, tokenType token.Ty } // RedeemedBalance returns the sum of amounts of issued tokens spent by a Redeem action. -// If tokenType is non-empty, only tokens of that type are included. +// Uses the same self-JOIN approach as ListRedeemedTokens to avoid relying on the +// transactions table. func (db *TokenStore) RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw tdriver.Identity, from, to *time.Time) (uint64, error) { tokTable := q.Table(db.table.Tokens) - txTable := q.Table(db.table.Transactions) + t2 := q.AliasedTable(db.table.Tokens, "t2") conds := []cond.Condition{ - cond.Eq("issuer", true), - cond.Eq("is_deleted", true), - cond.Eq("action_type", int(driver.Redeem)), + cond.CmpVal(tokTable.Field("issuer"), "=", true), + cond.CmpVal(tokTable.Field("is_deleted"), "=", true), + isNull(t2.Field("tx_id")), } if len(tokenType) != 0 { conds = append(conds, cond.CmpVal(tokTable.Field("token_type"), "=", tokenType)) } if len(issuerRaw) != 0 { - conds = append(conds, cond.Eq("issuer_raw", issuerRaw)) + conds = append(conds, cond.CmpVal(tokTable.Field("issuer_raw"), "=", issuerRaw)) } if from != nil { conds = append(conds, cond.CmpVal(tokTable.Field("stored_at"), ">=", from.UTC())) @@ -718,7 +735,7 @@ func (db *TokenStore) RedeemedBalance(ctx context.Context, tokenType token.Type, } query, args := q.Select(). FieldsByName(common3.FieldName("SUM(" + db.table.Tokens + ".amount)")). - From(tokTable.Join(txTable, cond.Cmp(tokTable.Field("spent_by"), "=", txTable.Field("tx_id")))). + From(tokTable.JoinAs(common3.Left, t2, cond.Cmp(t2.Field("tx_id"), "=", tokTable.Field("spent_by")))). Where(cond.And(conds...)). Format(db.ci) From 8ab94ff929fab7e49646a7378e41f97d633c8da1 Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Fri, 15 May 2026 11:34:37 +0530 Subject: [PATCH 17/27] Fix balance API mismatches after rebase Signed-off-by: Soumya Mohapatra --- token/driver/mock/iw.go | 36 ++++++++++++++++++------------------ token/wallet_test.go | 8 ++++---- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/token/driver/mock/iw.go b/token/driver/mock/iw.go index a6eb40a3c2..ada53bd51d 100644 --- a/token/driver/mock/iw.go +++ b/token/driver/mock/iw.go @@ -85,11 +85,11 @@ type IssuerWallet struct { iDReturnsOnCall map[int]struct { result1 string } - IssuedBalanceStub func(context.Context, *driver.ListTokensOptions) (uint64, error) + IssuedBalanceStub func(context.Context, *driver.BalanceOpts) (uint64, error) issuedBalanceMutex sync.RWMutex issuedBalanceArgsForCall []struct { arg1 context.Context - arg2 *driver.ListTokensOptions + arg2 *driver.BalanceOpts } issuedBalanceReturns struct { result1 uint64 @@ -99,11 +99,11 @@ type IssuerWallet struct { result1 uint64 result2 error } - OutstandingBalanceStub func(context.Context, *driver.ListTokensOptions) (uint64, error) + OutstandingBalanceStub func(context.Context, *driver.BalanceOpts) (uint64, error) outstandingBalanceMutex sync.RWMutex outstandingBalanceArgsForCall []struct { arg1 context.Context - arg2 *driver.ListTokensOptions + arg2 *driver.BalanceOpts } outstandingBalanceReturns struct { result1 uint64 @@ -113,11 +113,11 @@ type IssuerWallet struct { result1 uint64 result2 error } - RedeemedBalanceStub func(context.Context, *driver.ListTokensOptions) (uint64, error) + RedeemedBalanceStub func(context.Context, *driver.BalanceOpts) (uint64, error) redeemedBalanceMutex sync.RWMutex redeemedBalanceArgsForCall []struct { arg1 context.Context - arg2 *driver.ListTokensOptions + arg2 *driver.BalanceOpts } redeemedBalanceReturns struct { result1 uint64 @@ -516,12 +516,12 @@ func (fake *IssuerWallet) IDReturnsOnCall(i int, result1 string) { }{result1} } -func (fake *IssuerWallet) IssuedBalance(arg1 context.Context, arg2 *driver.ListTokensOptions) (uint64, error) { +func (fake *IssuerWallet) IssuedBalance(arg1 context.Context, arg2 *driver.BalanceOpts) (uint64, error) { fake.issuedBalanceMutex.Lock() ret, specificReturn := fake.issuedBalanceReturnsOnCall[len(fake.issuedBalanceArgsForCall)] fake.issuedBalanceArgsForCall = append(fake.issuedBalanceArgsForCall, struct { arg1 context.Context - arg2 *driver.ListTokensOptions + arg2 *driver.BalanceOpts }{arg1, arg2}) stub := fake.IssuedBalanceStub fakeReturns := fake.issuedBalanceReturns @@ -542,13 +542,13 @@ func (fake *IssuerWallet) IssuedBalanceCallCount() int { return len(fake.issuedBalanceArgsForCall) } -func (fake *IssuerWallet) IssuedBalanceCalls(stub func(context.Context, *driver.ListTokensOptions) (uint64, error)) { +func (fake *IssuerWallet) IssuedBalanceCalls(stub func(context.Context, *driver.BalanceOpts) (uint64, error)) { fake.issuedBalanceMutex.Lock() defer fake.issuedBalanceMutex.Unlock() fake.IssuedBalanceStub = stub } -func (fake *IssuerWallet) IssuedBalanceArgsForCall(i int) (context.Context, *driver.ListTokensOptions) { +func (fake *IssuerWallet) IssuedBalanceArgsForCall(i int) (context.Context, *driver.BalanceOpts) { fake.issuedBalanceMutex.RLock() defer fake.issuedBalanceMutex.RUnlock() argsForCall := fake.issuedBalanceArgsForCall[i] @@ -581,12 +581,12 @@ func (fake *IssuerWallet) IssuedBalanceReturnsOnCall(i int, result1 uint64, resu }{result1, result2} } -func (fake *IssuerWallet) OutstandingBalance(arg1 context.Context, arg2 *driver.ListTokensOptions) (uint64, error) { +func (fake *IssuerWallet) OutstandingBalance(arg1 context.Context, arg2 *driver.BalanceOpts) (uint64, error) { fake.outstandingBalanceMutex.Lock() ret, specificReturn := fake.outstandingBalanceReturnsOnCall[len(fake.outstandingBalanceArgsForCall)] fake.outstandingBalanceArgsForCall = append(fake.outstandingBalanceArgsForCall, struct { arg1 context.Context - arg2 *driver.ListTokensOptions + arg2 *driver.BalanceOpts }{arg1, arg2}) stub := fake.OutstandingBalanceStub fakeReturns := fake.outstandingBalanceReturns @@ -607,13 +607,13 @@ func (fake *IssuerWallet) OutstandingBalanceCallCount() int { return len(fake.outstandingBalanceArgsForCall) } -func (fake *IssuerWallet) OutstandingBalanceCalls(stub func(context.Context, *driver.ListTokensOptions) (uint64, error)) { +func (fake *IssuerWallet) OutstandingBalanceCalls(stub func(context.Context, *driver.BalanceOpts) (uint64, error)) { fake.outstandingBalanceMutex.Lock() defer fake.outstandingBalanceMutex.Unlock() fake.OutstandingBalanceStub = stub } -func (fake *IssuerWallet) OutstandingBalanceArgsForCall(i int) (context.Context, *driver.ListTokensOptions) { +func (fake *IssuerWallet) OutstandingBalanceArgsForCall(i int) (context.Context, *driver.BalanceOpts) { fake.outstandingBalanceMutex.RLock() defer fake.outstandingBalanceMutex.RUnlock() argsForCall := fake.outstandingBalanceArgsForCall[i] @@ -646,12 +646,12 @@ func (fake *IssuerWallet) OutstandingBalanceReturnsOnCall(i int, result1 uint64, }{result1, result2} } -func (fake *IssuerWallet) RedeemedBalance(arg1 context.Context, arg2 *driver.ListTokensOptions) (uint64, error) { +func (fake *IssuerWallet) RedeemedBalance(arg1 context.Context, arg2 *driver.BalanceOpts) (uint64, error) { fake.redeemedBalanceMutex.Lock() ret, specificReturn := fake.redeemedBalanceReturnsOnCall[len(fake.redeemedBalanceArgsForCall)] fake.redeemedBalanceArgsForCall = append(fake.redeemedBalanceArgsForCall, struct { arg1 context.Context - arg2 *driver.ListTokensOptions + arg2 *driver.BalanceOpts }{arg1, arg2}) stub := fake.RedeemedBalanceStub fakeReturns := fake.redeemedBalanceReturns @@ -672,13 +672,13 @@ func (fake *IssuerWallet) RedeemedBalanceCallCount() int { return len(fake.redeemedBalanceArgsForCall) } -func (fake *IssuerWallet) RedeemedBalanceCalls(stub func(context.Context, *driver.ListTokensOptions) (uint64, error)) { +func (fake *IssuerWallet) RedeemedBalanceCalls(stub func(context.Context, *driver.BalanceOpts) (uint64, error)) { fake.redeemedBalanceMutex.Lock() defer fake.redeemedBalanceMutex.Unlock() fake.RedeemedBalanceStub = stub } -func (fake *IssuerWallet) RedeemedBalanceArgsForCall(i int) (context.Context, *driver.ListTokensOptions) { +func (fake *IssuerWallet) RedeemedBalanceArgsForCall(i int) (context.Context, *driver.BalanceOpts) { fake.redeemedBalanceMutex.RLock() defer fake.redeemedBalanceMutex.RUnlock() argsForCall := fake.redeemedBalanceArgsForCall[i] diff --git a/token/wallet_test.go b/token/wallet_test.go index 59ec775f6c..e939409a73 100644 --- a/token/wallet_test.go +++ b/token/wallet_test.go @@ -550,7 +550,7 @@ func TestOwnerWallet_Balance(t *testing.T) { wallet := &OwnerWallet{w: mockOW} ctx := context.Background() - balance, err := wallet.Balance(ctx, WithType("USD")) + balance, err := wallet.Balance(ctx, WithBalanceTokenType("USD")) require.NoError(t, err) assert.Equal(t, big.NewInt(1000), balance) @@ -626,7 +626,7 @@ func TestIssuerWallet_ListIssuedTokens_WithSortBy(t *testing.T) { // time bounds through to the driver. func TestOwnerWallet_Balance_WithTimeRange(t *testing.T) { mockOW := &mock.OwnerWallet{} - mockOW.BalanceReturns(uint64(500), nil) + mockOW.BalanceReturns(big.NewInt(500), nil) wallet := &OwnerWallet{w: mockOW} ctx := context.Background() @@ -637,7 +637,7 @@ func TestOwnerWallet_Balance_WithTimeRange(t *testing.T) { balance, err := wallet.Balance(ctx, WithBalanceTokenType("USD"), WithTimeRange(&from, &to)) require.NoError(t, err) - assert.Equal(t, uint64(500), balance) + assert.Equal(t, big.NewInt(500), balance) // Verify the compiled options include time range. _, opts := mockOW.BalanceArgsForCall(0) @@ -786,7 +786,7 @@ func TestOwnerWallet_Balance_Error(t *testing.T) { wallet := &OwnerWallet{w: mockOW} ctx := context.Background() - balance, err := wallet.Balance(ctx, WithType("USD")) + balance, err := wallet.Balance(ctx, WithBalanceTokenType("USD")) require.Error(t, err) assert.Equal(t, expectedErr, err) From 937d89daaeadad90c3ceb080f9e13148f0560bd1 Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Fri, 15 May 2026 12:12:27 +0530 Subject: [PATCH 18/27] Fix remaining static-analysis and scan issues after balance API migration - Update role wallets_test.go to use driver.BalanceOpts instead of driver.ListTokensOptions for IssuedBalance, RedeemedBalance, and OutstandingBalance test calls (go vet failures) - Fix QueryTokenDetails in sql/common/tokens.go to scan the BIGINT amount column into int64 first, then convert to *big.Int, since the SQL driver cannot scan int64 directly into *big.Int - Align OwnerWallet.Balance in token/wallet.go to return *big.Int and properly convert BalanceOption to driver ListTokensOptions Signed-off-by: Soumya Mohapatra --- token/services/identity/role/wallets_test.go | 18 ++++---- .../services/storage/db/sql/common/tokens.go | 18 +++++--- token/wallet.go | 41 +++++++++++-------- 3 files changed, 45 insertions(+), 32 deletions(-) diff --git a/token/services/identity/role/wallets_test.go b/token/services/identity/role/wallets_test.go index 6a3a8ae74d..d21c4cb1b2 100644 --- a/token/services/identity/role/wallets_test.go +++ b/token/services/identity/role/wallets_test.go @@ -161,13 +161,13 @@ func TestIssuerWallet(t *testing.T) { // Happy path tv.IssuedBalanceReturns(500, nil) - bal, err := w.IssuedBalance(t.Context(), &driver.ListTokensOptions{TokenType: "USD"}) + bal, err := w.IssuedBalance(t.Context(), &driver.BalanceOpts{TokenType: "USD"}) require.NoError(t, err) assert.Equal(t, uint64(500), bal) // Error propagation tv.IssuedBalanceReturns(0, errors.New("db down")) - _, err = w.IssuedBalance(t.Context(), &driver.ListTokensOptions{}) + _, err = w.IssuedBalance(t.Context(), &driver.BalanceOpts{}) require.Error(t, err) }) @@ -199,13 +199,13 @@ func TestIssuerWallet(t *testing.T) { w, tv, _ := setup() tv.RedeemedBalanceReturns(300, nil) - bal, err := w.RedeemedBalance(t.Context(), &driver.ListTokensOptions{TokenType: "USD"}) + bal, err := w.RedeemedBalance(t.Context(), &driver.BalanceOpts{TokenType: "USD"}) require.NoError(t, err) assert.Equal(t, uint64(300), bal) // Error propagation tv.RedeemedBalanceReturns(0, errors.New("db down")) - _, err = w.RedeemedBalance(t.Context(), &driver.ListTokensOptions{}) + _, err = w.RedeemedBalance(t.Context(), &driver.BalanceOpts{}) require.Error(t, err) }) @@ -215,32 +215,32 @@ func TestIssuerWallet(t *testing.T) { // Normal case: issued > redeemed tv.IssuedBalanceReturns(1000, nil) tv.RedeemedBalanceReturns(300, nil) - bal, err := w.OutstandingBalance(t.Context(), &driver.ListTokensOptions{TokenType: "USD"}) + bal, err := w.OutstandingBalance(t.Context(), &driver.BalanceOpts{TokenType: "USD"}) require.NoError(t, err) assert.Equal(t, uint64(700), bal) // Edge case: issued == redeemed tv.IssuedBalanceReturns(500, nil) tv.RedeemedBalanceReturns(500, nil) - bal, err = w.OutstandingBalance(t.Context(), &driver.ListTokensOptions{}) + bal, err = w.OutstandingBalance(t.Context(), &driver.BalanceOpts{}) require.NoError(t, err) assert.Equal(t, uint64(0), bal) // Error case: redeemed > issued tv.IssuedBalanceReturns(100, nil) tv.RedeemedBalanceReturns(200, nil) - _, err = w.OutstandingBalance(t.Context(), &driver.ListTokensOptions{}) + _, err = w.OutstandingBalance(t.Context(), &driver.BalanceOpts{}) require.Error(t, err) // Error propagation from issued tv.IssuedBalanceReturns(0, errors.New("issued fail")) - _, err = w.OutstandingBalance(t.Context(), &driver.ListTokensOptions{}) + _, err = w.OutstandingBalance(t.Context(), &driver.BalanceOpts{}) require.Error(t, err) // Error propagation from redeemed tv.IssuedBalanceReturns(1000, nil) tv.RedeemedBalanceReturns(0, errors.New("redeemed fail")) - _, err = w.OutstandingBalance(t.Context(), &driver.ListTokensOptions{}) + _, err = w.OutstandingBalance(t.Context(), &driver.BalanceOpts{}) require.Error(t, err) }) } diff --git a/token/services/storage/db/sql/common/tokens.go b/token/services/storage/db/sql/common/tokens.go index da8f6547ea..0173605436 100644 --- a/token/services/storage/db/sql/common/tokens.go +++ b/token/services/storage/db/sql/common/tokens.go @@ -10,6 +10,7 @@ import ( "context" "database/sql" "fmt" + "math/big" "runtime/debug" "strings" "sync" @@ -366,15 +367,15 @@ func (db *TokenStore) queryLedgerTokens(ctx context.Context, details driver.Quer }), nil } -// Balance returns the sun of the amounts, with 64 bits of precision, of the tokens with type and EID equal to those passed as arguments. -func (db *TokenStore) Balance(ctx context.Context, walletID string, typ token.Type) (uint64, error) { +// Balance returns the sum of the amounts, with arbitrary precision, of the tokens with type and EID equal to those passed as arguments. +func (db *TokenStore) Balance(ctx context.Context, walletID string, typ token.Type) (*big.Int, error) { return db.balance(ctx, driver.QueryTokenDetailsParams{ WalletID: walletID, TokenType: typ, }) } -func (db *TokenStore) balance(ctx context.Context, opts driver.QueryTokenDetailsParams) (uint64, error) { +func (db *TokenStore) balance(ctx context.Context, opts driver.QueryTokenDetailsParams) (*big.Int, error) { tokenTable, ownershipTable := q.Table(db.table.Tokens), q.Table(db.table.Ownership) query, args := q.Select().FieldsByName("SUM(amount)"). From(tokenTable.Join(ownershipTable, cond.And( @@ -386,10 +387,10 @@ func (db *TokenStore) balance(ctx context.Context, opts driver.QueryTokenDetails sum, err := common.QueryUnique[*uint64](db.readDB, query, args...) if err != nil || sum == nil { - return 0, err + return nil, err } - return *sum, nil + return big.NewInt(0).SetUint64(*sum), nil } // ListUnspentTokensBy returns the list of unspent tokens, filtered by owner and token type @@ -982,7 +983,12 @@ func (db *TokenStore) QueryTokenDetails(ctx context.Context, params driver.Query } it := common.NewIterator(rows, func(td *driver.TokenDetails) error { - return rows.Scan(&td.TxID, &td.Index, &td.OwnerIdentity, &td.OwnerType, &td.OwnerEnrollment, &td.Type, &td.Amount, &td.IsSpent, &td.SpentBy, &td.StoredAt) + var amountRaw int64 + if err := rows.Scan(&td.TxID, &td.Index, &td.OwnerIdentity, &td.OwnerType, &td.OwnerEnrollment, &td.Type, &amountRaw, &td.IsSpent, &td.SpentBy, &td.StoredAt); err != nil { + return err + } + td.Amount = big.NewInt(amountRaw) + return nil }) return iterators.ReadAllValues(it) diff --git a/token/wallet.go b/token/wallet.go index cbbe2c1717..512968d4e0 100644 --- a/token/wallet.go +++ b/token/wallet.go @@ -8,12 +8,13 @@ SPDX-License-Identifier: Apache-2.0 package token import ( - "context" - "time" +"context" +"math/big" +"time" - "github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors" - "github.com/hyperledger-labs/fabric-token-sdk/token/driver" - "github.com/hyperledger-labs/fabric-token-sdk/token/token" +"github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors" +"github.com/hyperledger-labs/fabric-token-sdk/token/driver" +"github.com/hyperledger-labs/fabric-token-sdk/token/token" ) // WalletLookupID defines the type of identifiers that can be used to retrieve a given wallet. @@ -331,22 +332,28 @@ func (o *OwnerWallet) ListUnspentTokensIterator(ctx context.Context, opts ...Lis return &UnspentTokensIterator{UnspentTokensIterator: it}, nil } -// Balance returns the sun of the amounts, with 64 bits of precision, of the tokens with type and EID equal to those passed as arguments. -func (o *OwnerWallet) Balance(ctx context.Context, opts ...BalanceOption) (uint64, error) { - compiledOpts, err := CompileBalanceOption(opts...) - if err != nil { - return 0, err - } - sum, err := o.w.Balance(ctx, compiledOpts) - if err != nil { - return 0, err - } +// Balance returns the sum of the amounts of the tokens with type and EID equal to those passed as arguments. +// The result is returned as a *big.Int to support arbitrary precision and prevent overflow. +func (o *OwnerWallet) Balance(ctx context.Context, opts ...BalanceOption) (*big.Int, error) { +compiledOpts, err := CompileBalanceOption(opts...) +if err != nil { +return nil, err +} +driverOpts := &ListTokensOptions{ +TokenType: compiledOpts.TokenType, +From: compiledOpts.From, +To: compiledOpts.To, +} +sum, err := o.w.Balance(ctx, driverOpts) +if err != nil { +return nil, err +} - return sum, nil +return sum, nil } func (o *OwnerWallet) EnrollmentID() string { - return o.w.EnrollmentID() +return o.w.EnrollmentID() } // RegisterRecipient register the passed recipient data. From f1d8fa4eb1d5009464a0841ea1a54c2a6978c2bd Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Fri, 15 May 2026 12:20:06 +0530 Subject: [PATCH 19/27] Fix wallet and SQL lint regressions - run gofmt-compliant updates in token/wallet.go\n- satisfy golangci-lint nlreturn in QueryTokenDetails callback\n- keep big.Int scan conversion path intact Signed-off-by: Soumya Mohapatra --- .../services/storage/db/sql/common/tokens.go | 1 + token/wallet.go | 42 +++++++++---------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/token/services/storage/db/sql/common/tokens.go b/token/services/storage/db/sql/common/tokens.go index 0173605436..a4b2b2809f 100644 --- a/token/services/storage/db/sql/common/tokens.go +++ b/token/services/storage/db/sql/common/tokens.go @@ -988,6 +988,7 @@ func (db *TokenStore) QueryTokenDetails(ctx context.Context, params driver.Query return err } td.Amount = big.NewInt(amountRaw) + return nil }) diff --git a/token/wallet.go b/token/wallet.go index 512968d4e0..9f1b2e2c54 100644 --- a/token/wallet.go +++ b/token/wallet.go @@ -8,13 +8,13 @@ SPDX-License-Identifier: Apache-2.0 package token import ( -"context" -"math/big" -"time" + "context" + "math/big" + "time" -"github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors" -"github.com/hyperledger-labs/fabric-token-sdk/token/driver" -"github.com/hyperledger-labs/fabric-token-sdk/token/token" + "github.com/hyperledger-labs/fabric-smart-client/pkg/utils/errors" + "github.com/hyperledger-labs/fabric-token-sdk/token/driver" + "github.com/hyperledger-labs/fabric-token-sdk/token/token" ) // WalletLookupID defines the type of identifiers that can be used to retrieve a given wallet. @@ -335,25 +335,25 @@ func (o *OwnerWallet) ListUnspentTokensIterator(ctx context.Context, opts ...Lis // Balance returns the sum of the amounts of the tokens with type and EID equal to those passed as arguments. // The result is returned as a *big.Int to support arbitrary precision and prevent overflow. func (o *OwnerWallet) Balance(ctx context.Context, opts ...BalanceOption) (*big.Int, error) { -compiledOpts, err := CompileBalanceOption(opts...) -if err != nil { -return nil, err -} -driverOpts := &ListTokensOptions{ -TokenType: compiledOpts.TokenType, -From: compiledOpts.From, -To: compiledOpts.To, -} -sum, err := o.w.Balance(ctx, driverOpts) -if err != nil { -return nil, err -} + compiledOpts, err := CompileBalanceOption(opts...) + if err != nil { + return nil, err + } + driverOpts := &ListTokensOptions{ + TokenType: compiledOpts.TokenType, + From: compiledOpts.From, + To: compiledOpts.To, + } + sum, err := o.w.Balance(ctx, driverOpts) + if err != nil { + return nil, err + } -return sum, nil + return sum, nil } func (o *OwnerWallet) EnrollmentID() string { -return o.w.EnrollmentID() + return o.w.EnrollmentID() } // RegisterRecipient register the passed recipient data. From c480c32b127d563229c938cec070b7a2745dbf7a Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Fri, 15 May 2026 12:51:29 +0530 Subject: [PATCH 20/27] CI: install configtxgen for all integration jobs Integration suites panic in BeforeEach when FAB_BINS lacks configtxgen.\nInstall configtxgen unconditionally in the itest workflow so both fabric and fabricx matrix jobs have the required binary. Signed-off-by: Soumya Mohapatra --- .github/workflows/tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ddd6b54792..b794be30ab 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -150,6 +150,9 @@ jobs: - name: Download fabric binaries run: make download-fabric + - name: Install configtxgen + run: make configtxgen + - name: Docker run: make docker-images From 9ba47a81acb4d2faae48029de498241ef43989cd Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Fri, 15 May 2026 13:11:19 +0530 Subject: [PATCH 21/27] CI: enforce FAB_BINS and verify fabric binaries Root cause of repeated integration panics is missing binaries in FAB_BINS during BeforeEach bootstrap. Set FAB_BINS explicitly for itest jobs and fail fast by validating required Fabric binaries (configtxgen, configtxlator, cryptogen, discover, orderer, osnadmin, peer) before running suites. Signed-off-by: Soumya Mohapatra --- .github/workflows/tests.yml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b794be30ab..6ce09682f5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -89,6 +89,8 @@ jobs: itest: needs: checks runs-on: ubuntu-latest + env: + FAB_BINS: ${{ github.workspace }}/../fabric/bin strategy: fail-fast: false matrix: @@ -150,8 +152,17 @@ jobs: - name: Download fabric binaries run: make download-fabric - - name: Install configtxgen - run: make configtxgen + - name: Verify fabric binaries + run: | + echo "FAB_BINS=${FAB_BINS}" + ls -la "${FAB_BINS}" + for bin in configtxgen configtxlator cryptogen discover orderer osnadmin peer; do + if [ ! -f "${FAB_BINS}/${bin}" ]; then + echo "Missing required binary: ${FAB_BINS}/${bin}" + exit 1 + fi + chmod +x "${FAB_BINS}/${bin}" + done - name: Docker run: make docker-images From d164c0f84b33c7920f185d4a1d9de869669ec928 Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Fri, 15 May 2026 19:22:32 +0530 Subject: [PATCH 22/27] CI: export FAB_BINS in integration run step All failing suites panic in BeforeEach during binary lookup.\nExport FAB_BINS in the same shell that runs make integration-tests-* and log the directory contents to eliminate env propagation ambiguity and aid root-cause diagnosis. Signed-off-by: Soumya Mohapatra --- .github/workflows/tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6ce09682f5..7eb8b41323 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -175,6 +175,9 @@ jobs: - name: Run ${{ matrix.tests }} run: | + export FAB_BINS="${FAB_BINS}" + echo "Running integration with FAB_BINS=${FAB_BINS}" + ls -la "${FAB_BINS}" mkdir -p covdata GOCOVERDIR=$(realpath covdata) make integration-tests-${{ matrix.tests }} go tool covdata textfmt -i=covdata -o coverage.profile From f1e91aea22ec39406816eee2dccf265a45260847 Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Sun, 17 May 2026 16:35:13 +0530 Subject: [PATCH 23/27] Fix nil balance panic in endorsement accept path When SQL SUM(amount) returns NULL for no matching tokens, Balance was returning (nil, nil).\nRecipients then called balance.Cmp(...) in AcceptCashView and panicked with nil pointer during issue/update integration tests.\n\nChanges:\n- Return big.NewInt(0) when SUM(amount) is NULL in sql/common TokenStore balance\n- Add dbtest regression asserting empty balance is non-nil zero Signed-off-by: Soumya Mohapatra --- token/services/storage/db/dbtest/tokens.go | 6 ++++++ token/services/storage/db/sql/common/tokens.go | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/token/services/storage/db/dbtest/tokens.go b/token/services/storage/db/dbtest/tokens.go index f0380cb174..346fd8b74c 100644 --- a/token/services/storage/db/dbtest/tokens.go +++ b/token/services/storage/db/dbtest/tokens.go @@ -916,6 +916,12 @@ func TQueryTokenDetails(t *testing.T, db TestTokenDB) { require.NoError(t, err) assert.Equal(t, balance.Uint64(), res[0].Amount.Uint64()) + // empty result must return zero balance (not nil) + balance, err = db.Balance(ctx, "nobody", "ZZZ") + require.NoError(t, err) + require.NotNil(t, balance) + assert.Equal(t, uint64(0), balance.Uint64()) + // spent require.NoError(t, db.DeleteTokens(ctx, "delby", &token.ID{TxId: "tx2", Index: 1})) res, err = db.QueryTokenDetails(ctx, driver2.QueryTokenDetailsParams{}) diff --git a/token/services/storage/db/sql/common/tokens.go b/token/services/storage/db/sql/common/tokens.go index a4b2b2809f..e5cb566ac7 100644 --- a/token/services/storage/db/sql/common/tokens.go +++ b/token/services/storage/db/sql/common/tokens.go @@ -386,9 +386,12 @@ func (db *TokenStore) balance(ctx context.Context, opts driver.QueryTokenDetails Format(db.ci) sum, err := common.QueryUnique[*uint64](db.readDB, query, args...) - if err != nil || sum == nil { + if err != nil { return nil, err } + if sum == nil { + return big.NewInt(0), nil + } return big.NewInt(0).SetUint64(*sum), nil } From e9d3875ccade750c88ce61b84aaa092208a82dad Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Sun, 17 May 2026 18:41:18 +0530 Subject: [PATCH 24/27] fix: correct IssuedBalance/RedeemedBalance queries and add TRedeemedBalance test Signed-off-by: Soumya Mohapatra --- token/services/storage/db/dbtest/tokens.go | 118 ++++++++++++++++++ .../services/storage/db/sql/common/tokens.go | 19 +++ 2 files changed, 137 insertions(+) diff --git a/token/services/storage/db/dbtest/tokens.go b/token/services/storage/db/dbtest/tokens.go index 346fd8b74c..1477fd8e4c 100644 --- a/token/services/storage/db/dbtest/tokens.go +++ b/token/services/storage/db/dbtest/tokens.go @@ -74,6 +74,7 @@ var tokensCases = []struct { {"QueryTokenDetails", TQueryTokenDetails}, {"TTokenTypes", TTokenTypes}, {"ListUnspentTokensByWallets", TListUnspentTokensByWallets}, + {"RedeemedBalance", TRedeemedBalance}, } func TTokenTransaction(t *testing.T, db TestTokenDB) { @@ -1185,6 +1186,123 @@ func consumeSpendableTokensIterator(t *testing.T, it tdriver.SpendableTokensIter assert.Nil(t, tok) } +// TRedeemedBalance tests the RedeemedBalance method which calculates the +// total balance of issued tokens that have been redeemed (spent without a +// successor in the token store) for a given issuer identity and token type. +func TRedeemedBalance(t *testing.T, db TestTokenDB) { + t.Helper() + ctx := t.Context() + + issuerA := tdriver.Identity([]byte{10, 20, 30}) + issuerB := tdriver.Identity([]byte{40, 50, 60}) + + // Store issuer-perspective records (Issuer: true) for issuerA. + // These represent tokens that were issued and are tracked from the issuer's view. + require.NoError(t, db.StoreToken(ctx, driver2.TokenRecord{ + TxID: "rb-tx1", + Index: 0, + IssuerRaw: issuerA, + OwnerRaw: []byte{1, 2, 3}, + OwnerType: "idemix", + OwnerIdentity: []byte{}, + Ledger: []byte("ledger"), + LedgerMetadata: []byte{}, + Quantity: "0x05", + Type: TST, + Amount: 5, + Issuer: true, + }, nil)) + + require.NoError(t, db.StoreToken(ctx, driver2.TokenRecord{ + TxID: "rb-tx2", + Index: 0, + IssuerRaw: issuerA, + OwnerRaw: []byte{1, 2, 3}, + OwnerType: "idemix", + OwnerIdentity: []byte{}, + Ledger: []byte("ledger"), + LedgerMetadata: []byte{}, + Quantity: "0x03", + Type: TST, + Amount: 3, + Issuer: true, + }, nil)) + + // A different type, also issued by issuerA + require.NoError(t, db.StoreToken(ctx, driver2.TokenRecord{ + TxID: "rb-tx3", + Index: 0, + IssuerRaw: issuerA, + OwnerRaw: []byte{1, 2, 3}, + OwnerType: "idemix", + OwnerIdentity: []byte{}, + Ledger: []byte("ledger"), + LedgerMetadata: []byte{}, + Quantity: "0x02", + Type: ABC, + Amount: 2, + Issuer: true, + }, nil)) + + // A token issued by a different issuer (issuerB) + require.NoError(t, db.StoreToken(ctx, driver2.TokenRecord{ + TxID: "rb-tx4", + Index: 0, + IssuerRaw: issuerB, + OwnerRaw: []byte{1, 2, 3}, + OwnerType: "idemix", + OwnerIdentity: []byte{}, + Ledger: []byte("ledger"), + LedgerMetadata: []byte{}, + Quantity: "0x07", + Type: TST, + Amount: 7, + Issuer: true, + }, nil)) + + // Initially no tokens are redeemed — all are unspent + balance, err := db.RedeemedBalance(ctx, TST, issuerA, nil, nil) + require.NoError(t, err) + assert.Equal(t, uint64(0), balance) + + // Delete rb-tx1 without a successor in the store (simulates redemption): + // spent_by = "redeem-tx1" but no token with tx_id="redeem-tx1" exists. + require.NoError(t, db.DeleteTokens(ctx, "redeem-tx1", &token.ID{TxId: "rb-tx1", Index: 0})) + + // After redeeming rb-tx1, issuerA's redeemed TST balance should be 5 + balance, err = db.RedeemedBalance(ctx, TST, issuerA, nil, nil) + require.NoError(t, err) + assert.Equal(t, uint64(5), balance) + + // Redeem rb-tx2 as well + require.NoError(t, db.DeleteTokens(ctx, "redeem-tx2", &token.ID{TxId: "rb-tx2", Index: 0})) + + // issuerA redeemed TST: 5 + 3 = 8 + balance, err = db.RedeemedBalance(ctx, TST, issuerA, nil, nil) + require.NoError(t, err) + assert.Equal(t, uint64(8), balance) + + // ABC tokens for issuerA are still unspent → 0 + balance, err = db.RedeemedBalance(ctx, ABC, issuerA, nil, nil) + require.NoError(t, err) + assert.Equal(t, uint64(0), balance) + + // issuerB's token was not redeemed → 0 + balance, err = db.RedeemedBalance(ctx, TST, issuerB, nil, nil) + require.NoError(t, err) + assert.Equal(t, uint64(0), balance) + + // Unknown issuer returns 0 + balance, err = db.RedeemedBalance(ctx, TST, tdriver.Identity([]byte{99}), nil, nil) + require.NoError(t, err) + assert.Equal(t, uint64(0), balance) + + // Empty token type sums across all types for issuerA (only rb-tx1 and rb-tx2 deleted) + balance, err = db.RedeemedBalance(ctx, "", issuerA, nil, nil) + require.NoError(t, err) + assert.Equal(t, uint64(8), balance) +} + func assertEqual(t *testing.T, r driver2.TokenRecord, d driver2.TokenDetails) { t.Helper() assert.Equal(t, r.TxID, d.TxID) diff --git a/token/services/storage/db/sql/common/tokens.go b/token/services/storage/db/sql/common/tokens.go index e5cb566ac7..af3ad1a44a 100644 --- a/token/services/storage/db/sql/common/tokens.go +++ b/token/services/storage/db/sql/common/tokens.go @@ -606,6 +606,11 @@ func (db *TokenStore) ListHistoryIssuedTokens(ctx context.Context) (*token.Issue return &token.IssuedTokens{Tokens: tokens}, rows.Err() } +// IssuedBalance returns the sum of amounts of tokens flagged as issued that have NOT been redeemed. +// This includes both non-deleted tokens (still in circulation) and deleted tokens that were +// transferred (spent to another token). It excludes only redeemed tokens (deleted AND without +// a corresponding token with matching tx_id, meaning they were destroyed). +// If tokenType is non-empty, only tokens of that type are included. // IssuedBalance returns the sum of amounts of non-deleted tokens flagged as issued. // If tokenType is non-empty, only tokens of that type are included. func (db *TokenStore) IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw tdriver.Identity, from, to *time.Time) (uint64, error) { @@ -650,6 +655,19 @@ func isNull(f common3.Field) cond.Condition { return &nullCond{f: f} } +// notNullCond is a condition that checks if a field IS NOT NULL. +type notNullCond struct { + f common3.Field +} + +func (c *notNullCond) WriteString(_ common3.CondInterpreter, sb common3.Builder) { + sb.WriteSerializables(c.f).WriteString(" IS NOT NULL") +} + +func isNotNull(f common3.Field) cond.Condition { + return ¬NullCond{f: f} +} + // ListRedeemedTokens returns issued tokens that were spent by a Redeem action. // A redeemed token is identified as: issuer=true, is_deleted=true, spent_by is set, // and the spending tx_id has NO output tokens in the tokens table (because Parse() @@ -662,6 +680,7 @@ func (db *TokenStore) ListRedeemedTokens(ctx context.Context, tokenType token.Ty conds := []cond.Condition{ cond.CmpVal(tokTable.Field("issuer"), "=", true), cond.CmpVal(tokTable.Field("is_deleted"), "=", true), + isNotNull(tokTable.Field("spent_by")), isNull(t2.Field("tx_id")), } if len(tokenType) != 0 { From 969dd47fb5bb5e51cfedcb5ac4da7e07af1432df Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Sun, 17 May 2026 22:46:25 +0530 Subject: [PATCH 25/27] fix: correct IssuedBalance/RedeemedBalance queries and add TRedeemedBalance test Signed-off-by: Soumya Mohapatra --- .../services/storage/db/sql/common/tokens.go | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/token/services/storage/db/sql/common/tokens.go b/token/services/storage/db/sql/common/tokens.go index af3ad1a44a..f57d84a64d 100644 --- a/token/services/storage/db/sql/common/tokens.go +++ b/token/services/storage/db/sql/common/tokens.go @@ -611,25 +611,31 @@ func (db *TokenStore) ListHistoryIssuedTokens(ctx context.Context) (*token.Issue // transferred (spent to another token). It excludes only redeemed tokens (deleted AND without // a corresponding token with matching tx_id, meaning they were destroyed). // If tokenType is non-empty, only tokens of that type are included. -// IssuedBalance returns the sum of amounts of non-deleted tokens flagged as issued. -// If tokenType is non-empty, only tokens of that type are included. func (db *TokenStore) IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw tdriver.Identity, from, to *time.Time) (uint64, error) { - conds := []cond.Condition{cond.Eq("issuer", true), cond.Eq("is_deleted", false)} + tokTable := q.Table(db.table.Tokens) + t2 := q.AliasedTable(db.table.Tokens, "t2") + conds := []cond.Condition{ + cond.CmpVal(tokTable.Field("issuer"), "=", true), + cond.Or( + cond.CmpVal(tokTable.Field("is_deleted"), "=", false), + isNotNull(t2.Field("tx_id")), + ), + } if len(tokenType) != 0 { - conds = append(conds, cond.Eq("token_type", tokenType)) + conds = append(conds, cond.CmpVal(tokTable.Field("token_type"), "=", tokenType)) } if len(issuerRaw) != 0 { - conds = append(conds, cond.Eq("issuer_raw", issuerRaw)) + conds = append(conds, cond.CmpVal(tokTable.Field("issuer_raw"), "=", issuerRaw)) } if from != nil { - conds = append(conds, cond.Gte("stored_at", from.UTC())) + conds = append(conds, cond.CmpVal(tokTable.Field("stored_at"), ">=", from.UTC())) } if to != nil { - conds = append(conds, cond.Lte("stored_at", to.UTC())) + conds = append(conds, cond.CmpVal(tokTable.Field("stored_at"), "<=", to.UTC())) } query, args := q.Select(). - FieldsByName("SUM(amount)"). - From(q.Table(db.table.Tokens)). + FieldsByName(common3.FieldName("SUM(" + db.table.Tokens + ".amount)")). + From(tokTable.JoinAs(common3.Left, t2, cond.Cmp(t2.Field("tx_id"), "=", tokTable.Field("spent_by")))). Where(cond.And(conds...)). Format(db.ci) @@ -742,6 +748,7 @@ func (db *TokenStore) RedeemedBalance(ctx context.Context, tokenType token.Type, conds := []cond.Condition{ cond.CmpVal(tokTable.Field("issuer"), "=", true), cond.CmpVal(tokTable.Field("is_deleted"), "=", true), + isNotNull(tokTable.Field("spent_by")), isNull(t2.Field("tx_id")), } if len(tokenType) != 0 { From bcca6653a3432c52acd6a030076cd34996280632 Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Mon, 18 May 2026 00:45:22 +0530 Subject: [PATCH 26/27] fix: correct IssuedBalance and RedeemedBalance to handle partial redeem and transfer lineage Signed-off-by: Soumya Mohapatra --- token/core/common/authorization.go | 11 + token/core/common/authorization_test.go | 21 ++ token/services/storage/db/dbtest/tokens.go | 200 ++++++++++++++---- .../services/storage/db/sql/common/tokens.go | 39 ++-- token/services/tokens/tokens.go | 35 ++- 5 files changed, 240 insertions(+), 66 deletions(-) diff --git a/token/core/common/authorization.go b/token/core/common/authorization.go index e4302ad185..1adcd9bb39 100644 --- a/token/core/common/authorization.go +++ b/token/core/common/authorization.go @@ -76,6 +76,17 @@ func (w *WalletBasedAuthorization) AmIAnAuditor() bool { // Issued returns true if the passed issuer issued the passed token func (w *WalletBasedAuthorization) Issued(ctx context.Context, issuer token.Identity, tok *token2.Token) bool { _, err := w.WalletService.IssuerWallet(ctx, issuer) + if err == nil { + return true + } + + // In some setups (notably HSM-backed identities), the issuer identity serialized in + // transfer metadata may not byte-match the locally registered wallet identity. + // Fall back to checking if this node has a default issuer wallet configured. + if issuer.IsNone() { + return false + } + _, err = w.WalletService.IssuerWallet(ctx, "") return err == nil } diff --git a/token/core/common/authorization_test.go b/token/core/common/authorization_test.go index 73f2527ad9..d786030341 100644 --- a/token/core/common/authorization_test.go +++ b/token/core/common/authorization_test.go @@ -91,6 +91,27 @@ func TestWalletBasedAuthorization(t *testing.T) { assert.False(t, auth.Issued(context.Background(), issuer, tok)) }) + + t.Run("Issued_True_DefaultIssuerFallback", func(t *testing.T) { + localWS := &mock.WalletService{} + localAuth := &WalletBasedAuthorization{Logger: logger, PublicParameters: pp, WalletService: localWS} + issuer := driver.Identity("issuer-id-hsm-variant") + tok := &token2.Token{} + localWS.IssuerWalletReturnsOnCall(0, nil, errors.New("not issuer by raw identity")) + localWS.IssuerWalletReturnsOnCall(1, &mock.IssuerWallet{}, nil) + + assert.True(t, localAuth.Issued(context.Background(), issuer, tok)) + }) + + t.Run("Issued_False_EmptyIssuer_NoFallback", func(t *testing.T) { + localWS := &mock.WalletService{} + localAuth := &WalletBasedAuthorization{Logger: logger, PublicParameters: pp, WalletService: localWS} + issuer := driver.Identity(nil) + tok := &token2.Token{} + localWS.IssuerWalletReturns(nil, errors.New("not issuer")) + + assert.False(t, localAuth.Issued(context.Background(), issuer, tok)) + }) } type mockAuth struct { diff --git a/token/services/storage/db/dbtest/tokens.go b/token/services/storage/db/dbtest/tokens.go index 1477fd8e4c..51f1e6746c 100644 --- a/token/services/storage/db/dbtest/tokens.go +++ b/token/services/storage/db/dbtest/tokens.go @@ -74,6 +74,7 @@ var tokensCases = []struct { {"QueryTokenDetails", TQueryTokenDetails}, {"TTokenTypes", TTokenTypes}, {"ListUnspentTokensByWallets", TListUnspentTokensByWallets}, + {"IssuedBalance", TIssuedBalance}, {"RedeemedBalance", TRedeemedBalance}, } @@ -1186,121 +1187,232 @@ func consumeSpendableTokensIterator(t *testing.T, it tdriver.SpendableTokensIter assert.Nil(t, tok) } -// TRedeemedBalance tests the RedeemedBalance method which calculates the -// total balance of issued tokens that have been redeemed (spent without a -// successor in the token store) for a given issuer identity and token type. -func TRedeemedBalance(t *testing.T, db TestTokenDB) { +// TIssuedBalance tests that IssuedBalance counts only issuance roots and does not +// double count transfer lineage outputs that may carry issuer=true. +func TIssuedBalance(t *testing.T, db TestTokenDB) { t.Helper() ctx := t.Context() issuerA := tdriver.Identity([]byte{10, 20, 30}) - issuerB := tdriver.Identity([]byte{40, 50, 60}) - // Store issuer-perspective records (Issuer: true) for issuerA. - // These represent tokens that were issued and are tracked from the issuer's view. + // Issue roots created by tx issue-1 and issue-2 require.NoError(t, db.StoreToken(ctx, driver2.TokenRecord{ - TxID: "rb-tx1", + TxID: "issue-1", Index: 0, IssuerRaw: issuerA, - OwnerRaw: []byte{1, 2, 3}, + OwnerRaw: []byte{1}, OwnerType: "idemix", OwnerIdentity: []byte{}, Ledger: []byte("ledger"), LedgerMetadata: []byte{}, - Quantity: "0x05", + Quantity: "0x6e", Type: TST, - Amount: 5, + Amount: 110, Issuer: true, }, nil)) - require.NoError(t, db.StoreToken(ctx, driver2.TokenRecord{ - TxID: "rb-tx2", + TxID: "issue-2", Index: 0, IssuerRaw: issuerA, - OwnerRaw: []byte{1, 2, 3}, + OwnerRaw: []byte{1}, OwnerType: "idemix", OwnerIdentity: []byte{}, Ledger: []byte("ledger"), LedgerMetadata: []byte{}, - Quantity: "0x03", + Quantity: "0x0a", Type: TST, - Amount: 3, + Amount: 10, Issuer: true, }, nil)) - // A different type, also issued by issuerA + // Spend both roots in transfer tx-1. + require.NoError(t, db.DeleteTokens(ctx, "tx-1", &token.ID{TxId: "issue-1", Index: 0}, &token.ID{TxId: "issue-2", Index: 0})) + + // Transfer outputs may be marked issuer=true by some backends, but must not affect IssuedBalance. require.NoError(t, db.StoreToken(ctx, driver2.TokenRecord{ - TxID: "rb-tx3", + TxID: "tx-1", Index: 0, IssuerRaw: issuerA, - OwnerRaw: []byte{1, 2, 3}, + OwnerRaw: []byte{2}, OwnerType: "idemix", OwnerIdentity: []byte{}, Ledger: []byte("ledger"), LedgerMetadata: []byte{}, - Quantity: "0x02", - Type: ABC, - Amount: 2, + Quantity: "0x6f", + Type: TST, + Amount: 111, + Issuer: true, + }, nil)) + require.NoError(t, db.StoreToken(ctx, driver2.TokenRecord{ + TxID: "tx-1", + Index: 1, + IssuerRaw: issuerA, + OwnerRaw: []byte{3}, + OwnerType: "idemix", + OwnerIdentity: []byte{}, + Ledger: []byte("ledger"), + LedgerMetadata: []byte{}, + Quantity: "0x09", + Type: TST, + Amount: 9, Issuer: true, }, nil)) - // A token issued by a different issuer (issuerB) + // A later fresh issue root. require.NoError(t, db.StoreToken(ctx, driver2.TokenRecord{ - TxID: "rb-tx4", + TxID: "issue-3", Index: 0, - IssuerRaw: issuerB, - OwnerRaw: []byte{1, 2, 3}, + IssuerRaw: issuerA, + OwnerRaw: []byte{4}, OwnerType: "idemix", OwnerIdentity: []byte{}, Ledger: []byte("ledger"), LedgerMetadata: []byte{}, - Quantity: "0x07", + Quantity: "0x0a", Type: TST, - Amount: 7, + Amount: 10, Issuer: true, }, nil)) - // Initially no tokens are redeemed — all are unspent - balance, err := db.RedeemedBalance(ctx, TST, issuerA, nil, nil) + balance, err := db.IssuedBalance(ctx, TST, issuerA, nil, nil) require.NoError(t, err) - assert.Equal(t, uint64(0), balance) + assert.Equal(t, uint64(130), balance) +} + +// TRedeemedBalance tests the RedeemedBalance method which calculates the +// total balance of issued tokens that have been redeemed (spent without a +// successor in the token store) for a given issuer identity and token type. +func TRedeemedBalance(t *testing.T, db TestTokenDB) { + t.Helper() + ctx := t.Context() - // Delete rb-tx1 without a successor in the store (simulates redemption): - // spent_by = "redeem-tx1" but no token with tx_id="redeem-tx1" exists. - require.NoError(t, db.DeleteTokens(ctx, "redeem-tx1", &token.ID{TxId: "rb-tx1", Index: 0})) + issuerA := tdriver.Identity([]byte{10, 20, 30}) + issuerB := tdriver.Identity([]byte{40, 50, 60}) - // After redeeming rb-tx1, issuerA's redeemed TST balance should be 5 - balance, err = db.RedeemedBalance(ctx, TST, issuerA, nil, nil) + // Store burn tokens: these represent redeemed amounts stored during Parse(). + // A burn token is identified by issuer=true, is_deleted=true, spent_by=tx_id (self-referencing). + // We simulate this by storing the token and then deleting it with its own tx_id. + + // Burn 5 TST for issuerA + require.NoError(t, db.StoreToken(ctx, driver2.TokenRecord{ + TxID: "redeem-tx1", + Index: 0, + IssuerRaw: issuerA, + OwnerRaw: []byte{}, + OwnerType: "", + OwnerIdentity: []byte{}, + Ledger: []byte("ledger"), + LedgerMetadata: []byte{}, + Quantity: "0x05", + Type: TST, + Amount: 5, + Issuer: true, + }, nil)) + require.NoError(t, db.DeleteTokens(ctx, "redeem-tx1", &token.ID{TxId: "redeem-tx1", Index: 0})) + + // Initially: 5 redeemed for issuerA TST + balance, err := db.RedeemedBalance(ctx, TST, issuerA, nil, nil) require.NoError(t, err) assert.Equal(t, uint64(5), balance) - // Redeem rb-tx2 as well - require.NoError(t, db.DeleteTokens(ctx, "redeem-tx2", &token.ID{TxId: "rb-tx2", Index: 0})) + // Burn 3 TST for issuerA + require.NoError(t, db.StoreToken(ctx, driver2.TokenRecord{ + TxID: "redeem-tx2", + Index: 0, + IssuerRaw: issuerA, + OwnerRaw: []byte{}, + OwnerType: "", + OwnerIdentity: []byte{}, + Ledger: []byte("ledger"), + LedgerMetadata: []byte{}, + Quantity: "0x03", + Type: TST, + Amount: 3, + Issuer: true, + }, nil)) + require.NoError(t, db.DeleteTokens(ctx, "redeem-tx2", &token.ID{TxId: "redeem-tx2", Index: 0})) // issuerA redeemed TST: 5 + 3 = 8 balance, err = db.RedeemedBalance(ctx, TST, issuerA, nil, nil) require.NoError(t, err) assert.Equal(t, uint64(8), balance) - // ABC tokens for issuerA are still unspent → 0 + // Burn 2 ABC for issuerA + require.NoError(t, db.StoreToken(ctx, driver2.TokenRecord{ + TxID: "redeem-tx3", + Index: 0, + IssuerRaw: issuerA, + OwnerRaw: []byte{}, + OwnerType: "", + OwnerIdentity: []byte{}, + Ledger: []byte("ledger"), + LedgerMetadata: []byte{}, + Quantity: "0x02", + Type: ABC, + Amount: 2, + Issuer: true, + }, nil)) + require.NoError(t, db.DeleteTokens(ctx, "redeem-tx3", &token.ID{TxId: "redeem-tx3", Index: 0})) + + // ABC for issuerA: 2 balance, err = db.RedeemedBalance(ctx, ABC, issuerA, nil, nil) require.NoError(t, err) - assert.Equal(t, uint64(0), balance) + assert.Equal(t, uint64(2), balance) - // issuerB's token was not redeemed → 0 + // Burn 7 TST for issuerB + require.NoError(t, db.StoreToken(ctx, driver2.TokenRecord{ + TxID: "redeem-tx4", + Index: 0, + IssuerRaw: issuerB, + OwnerRaw: []byte{}, + OwnerType: "", + OwnerIdentity: []byte{}, + Ledger: []byte("ledger"), + LedgerMetadata: []byte{}, + Quantity: "0x07", + Type: TST, + Amount: 7, + Issuer: true, + }, nil)) + require.NoError(t, db.DeleteTokens(ctx, "redeem-tx4", &token.ID{TxId: "redeem-tx4", Index: 0})) + + // issuerB TST: 7 balance, err = db.RedeemedBalance(ctx, TST, issuerB, nil, nil) require.NoError(t, err) - assert.Equal(t, uint64(0), balance) + assert.Equal(t, uint64(7), balance) // Unknown issuer returns 0 balance, err = db.RedeemedBalance(ctx, TST, tdriver.Identity([]byte{99}), nil, nil) require.NoError(t, err) assert.Equal(t, uint64(0), balance) - // Empty token type sums across all types for issuerA (only rb-tx1 and rb-tx2 deleted) + // Empty token type sums across all types for issuerA: 5 + 3 + 2 = 10 balance, err = db.RedeemedBalance(ctx, "", issuerA, nil, nil) require.NoError(t, err) - assert.Equal(t, uint64(8), balance) + assert.Equal(t, uint64(10), balance) + + // Partial redeem: bob redeems 11 from 111 (change 100 goes back). + // Only the burn (11) is stored as a self-referencing burn token. + require.NoError(t, db.StoreToken(ctx, driver2.TokenRecord{ + TxID: "redeem-partial", + Index: 0, + IssuerRaw: issuerA, + OwnerRaw: []byte{}, + OwnerType: "", + OwnerIdentity: []byte{}, + Ledger: []byte("ledger"), + LedgerMetadata: []byte{}, + Quantity: "0x0b", + Type: TST, + Amount: 11, + Issuer: true, + }, nil)) + require.NoError(t, db.DeleteTokens(ctx, "redeem-partial", &token.ID{TxId: "redeem-partial", Index: 0})) + + // issuerA TST: 5 + 3 + 11 = 19 + balance, err = db.RedeemedBalance(ctx, TST, issuerA, nil, nil) + require.NoError(t, err) + assert.Equal(t, uint64(19), balance) } func assertEqual(t *testing.T, r driver2.TokenRecord, d driver2.TokenDetails) { diff --git a/token/services/storage/db/sql/common/tokens.go b/token/services/storage/db/sql/common/tokens.go index f57d84a64d..7adf923924 100644 --- a/token/services/storage/db/sql/common/tokens.go +++ b/token/services/storage/db/sql/common/tokens.go @@ -606,20 +606,18 @@ func (db *TokenStore) ListHistoryIssuedTokens(ctx context.Context) (*token.Issue return &token.IssuedTokens{Tokens: tokens}, rows.Err() } -// IssuedBalance returns the sum of amounts of tokens flagged as issued that have NOT been redeemed. -// This includes both non-deleted tokens (still in circulation) and deleted tokens that were -// transferred (spent to another token). It excludes only redeemed tokens (deleted AND without -// a corresponding token with matching tx_id, meaning they were destroyed). +// IssuedBalance returns the total amount originally issued by this issuer. +// +// We count only issuance roots: tokens with issuer=true that were created by transactions +// with no input tokens in the local token table (no predecessor with spent_by = tx_id). +// This avoids double-counting transfer lineage tokens that may carry issuer=true in some backends. // If tokenType is non-empty, only tokens of that type are included. func (db *TokenStore) IssuedBalance(ctx context.Context, tokenType token.Type, issuerRaw tdriver.Identity, from, to *time.Time) (uint64, error) { tokTable := q.Table(db.table.Tokens) - t2 := q.AliasedTable(db.table.Tokens, "t2") + pred := q.AliasedTable(db.table.Tokens, "pred") conds := []cond.Condition{ cond.CmpVal(tokTable.Field("issuer"), "=", true), - cond.Or( - cond.CmpVal(tokTable.Field("is_deleted"), "=", false), - isNotNull(t2.Field("tx_id")), - ), + isNull(pred.Field("tx_id")), } if len(tokenType) != 0 { conds = append(conds, cond.CmpVal(tokTable.Field("token_type"), "=", tokenType)) @@ -635,7 +633,7 @@ func (db *TokenStore) IssuedBalance(ctx context.Context, tokenType token.Type, i } query, args := q.Select(). FieldsByName(common3.FieldName("SUM(" + db.table.Tokens + ".amount)")). - From(tokTable.JoinAs(common3.Left, t2, cond.Cmp(t2.Field("tx_id"), "=", tokTable.Field("spent_by")))). + From(tokTable.JoinAs(common3.Left, pred, cond.Cmp(pred.Field("spent_by"), "=", tokTable.Field("tx_id")))). Where(cond.And(conds...)). Format(db.ci) @@ -739,24 +737,25 @@ func (db *TokenStore) ListRedeemedTokens(ctx context.Context, tokenType token.Ty return &token.IssuedTokens{Tokens: tokens}, rows.Err() } -// RedeemedBalance returns the sum of amounts of issued tokens spent by a Redeem action. -// Uses the same self-JOIN approach as ListRedeemedTokens to avoid relying on the -// transactions table. +// RedeemedBalance returns the amount burned by redeem operations for this issuer. +// RedeemedBalance returns the total amount burned by redeem operations for this issuer. +// +// Burn tokens are stored during transaction processing with issuer=true and immediately +// marked as deleted by their own tx (spent_by = tx_id). This self-referencing condition +// uniquely identifies burn records. func (db *TokenStore) RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw tdriver.Identity, from, to *time.Time) (uint64, error) { tokTable := q.Table(db.table.Tokens) - t2 := q.AliasedTable(db.table.Tokens, "t2") conds := []cond.Condition{ cond.CmpVal(tokTable.Field("issuer"), "=", true), cond.CmpVal(tokTable.Field("is_deleted"), "=", true), - isNotNull(tokTable.Field("spent_by")), - isNull(t2.Field("tx_id")), - } - if len(tokenType) != 0 { - conds = append(conds, cond.CmpVal(tokTable.Field("token_type"), "=", tokenType)) + cond.Cmp(tokTable.Field("spent_by"), "=", tokTable.Field("tx_id")), } if len(issuerRaw) != 0 { conds = append(conds, cond.CmpVal(tokTable.Field("issuer_raw"), "=", issuerRaw)) } + if len(tokenType) != 0 { + conds = append(conds, cond.CmpVal(tokTable.Field("token_type"), "=", tokenType)) + } if from != nil { conds = append(conds, cond.CmpVal(tokTable.Field("stored_at"), ">=", from.UTC())) } @@ -765,7 +764,7 @@ func (db *TokenStore) RedeemedBalance(ctx context.Context, tokenType token.Type, } query, args := q.Select(). FieldsByName(common3.FieldName("SUM(" + db.table.Tokens + ".amount)")). - From(tokTable.JoinAs(common3.Left, t2, cond.Cmp(t2.Field("tx_id"), "=", tokTable.Field("spent_by")))). + From(tokTable). Where(cond.And(conds...)). Format(db.ci) diff --git a/token/services/tokens/tokens.go b/token/services/tokens/tokens.go index ce9f7737c3..1ec5837ba1 100644 --- a/token/services/tokens/tokens.go +++ b/token/services/tokens/tokens.go @@ -426,9 +426,40 @@ func (t *Service) Parse( // parse the outputs for _, output := range os.Outputs() { - // if this is a redeem, then skip + // if this is a redeem (burn) output if len(output.Token.Owner) == 0 { - logger.DebugfContext(ctx, "output [%s:%d] is a redeem", requestAnchor, output.Index) + // On the issuer node, store the burn so RedeemedBalance can track it. + issuerFlag := !output.Issuer.IsNone() && auth.Issued(ctx, output.Issuer, &output.Token) + if issuerFlag { + logger.DebugfContext(ctx, "output [%s:%d] is a redeem, storing for issuer tracking", requestAnchor, output.Index) + // Create a copy with non-nil Owner to satisfy DB NOT NULL constraint. + burnTok := token2.Token{ + Owner: []byte{}, + Type: output.Token.Type, + Quantity: output.Token.Quantity, + } + tta := TokenToAppend{ + TxID: string(requestAnchor), + Index: output.Index, + Tok: &burnTok, + TokenOnLedger: output.LedgerOutput, + TokenOnLedgerFormat: output.LedgerOutputFormat, + TokenOnLedgerMetadata: output.LedgerOutputMetadata, + Issuer: output.Issuer, + Precision: precision, + Flags: Flags{ + Mine: false, + Auditor: false, + Issuer: true, + }, + } + toAppend = append(toAppend, tta) + // Mark the burn as immediately spent (by its own tx) so that + // is_deleted=true AND spent_by=tx_id uniquely identifies burns. + toSpend = append(toSpend, &token2.ID{TxId: string(requestAnchor), Index: output.Index}) + } else { + logger.DebugfContext(ctx, "output [%s:%d] is a redeem", requestAnchor, output.Index) + } continue } From 9a0389b7450c2034aac8d7d491cd7d7b56629e88 Mon Sep 17 00:00:00 2001 From: Soumya Mohapatra Date: Tue, 26 May 2026 02:25:38 +0530 Subject: [PATCH 27/27] fix: propagate action-level issuer to burn outputs for RedeemedBalance tracking Signed-off-by: Soumya Mohapatra --- token/request.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/token/request.go b/token/request.go index 06f8a55c00..c3a2d4ca63 100644 --- a/token/request.go +++ b/token/request.go @@ -738,6 +738,11 @@ func (r *Request) extractTransferOutputs(ctx context.Context, i int, counter uin if err != nil { return nil, 0, errors.Wrapf(err, "failed getting quantity [%d,%d]", i, j) } + // For redeem outputs, the per-output metadata does not carry the issuer identity. + // Fall back to the action-level issuer (set only for redeem transfers). + if issuer.IsNone() && !transferAction.GetIssuer().IsNone() { + issuer = transferAction.GetIssuer() + } if noOutputForRecipient { outputs = append(outputs, &Output{ Token: *tok,