Skip to content

Commit 8da2b44

Browse files
author
Soumya Mohapatra
committed
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 <mohapatras@microsoft.com>
1 parent d5242aa commit 8da2b44

1 file changed

Lines changed: 32 additions & 15 deletions

File tree

  • token/services/storage/db/sql/common

token/services/storage/db/sql/common/tokens.go

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -632,22 +632,38 @@ func (db *TokenStore) IssuedBalance(ctx context.Context, tokenType token.Type, i
632632
return *sum, nil
633633
}
634634

635+
// nullCond is a condition that checks if a field IS NULL.
636+
type nullCond struct {
637+
f common3.Field
638+
}
639+
640+
func (c *nullCond) WriteString(_ common3.CondInterpreter, sb common3.Builder) {
641+
sb.WriteSerializables(c.f).WriteString(" IS NULL")
642+
}
643+
644+
func isNull(f common3.Field) cond.Condition {
645+
return &nullCond{f: f}
646+
}
647+
635648
// ListRedeemedTokens returns issued tokens that were spent by a Redeem action.
636-
// It JOINs with the transactions table to identify tokens whose spent_by txID
637-
// has action_type = Redeem (2). If tokenType is non-empty, only tokens of that type are included.
649+
// A redeemed token is identified as: issuer=true, is_deleted=true, spent_by is set,
650+
// and the spending tx_id has NO output tokens in the tokens table (because Parse()
651+
// skips redeem outputs with empty owner). This avoids relying on the transactions
652+
// table, which may not have entries on the issuer node.
638653
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) {
639654
tokTable := q.Table(db.table.Tokens)
640-
txTable := q.Table(db.table.Transactions)
655+
// Alias the same tokens table to check for output tokens of the spending tx.
656+
t2 := q.AliasedTable(db.table.Tokens, "t2")
641657
conds := []cond.Condition{
642-
cond.Eq("issuer", true),
643-
cond.Eq("is_deleted", true),
644-
cond.Eq("action_type", int(driver.Redeem)),
658+
cond.CmpVal(tokTable.Field("issuer"), "=", true),
659+
cond.CmpVal(tokTable.Field("is_deleted"), "=", true),
660+
isNull(t2.Field("tx_id")),
645661
}
646662
if len(tokenType) != 0 {
647663
conds = append(conds, cond.CmpVal(tokTable.Field("token_type"), "=", tokenType))
648664
}
649665
if len(issuerRaw) != 0 {
650-
conds = append(conds, cond.Eq("issuer_raw", issuerRaw))
666+
conds = append(conds, cond.CmpVal(tokTable.Field("issuer_raw"), "=", issuerRaw))
651667
}
652668
if from != nil {
653669
conds = append(conds, cond.CmpVal(tokTable.Field("stored_at"), ">=", from.UTC()))
@@ -662,7 +678,7 @@ func (db *TokenStore) ListRedeemedTokens(ctx context.Context, tokenType token.Ty
662678
tokTable.Field("quantity"), tokTable.Field("issuer_raw"),
663679
tokTable.Field("is_deleted"), tokTable.Field("spent_by"),
664680
).
665-
From(tokTable.Join(txTable, cond.Cmp(tokTable.Field("spent_by"), "=", txTable.Field("tx_id")))).
681+
From(tokTable.JoinAs(common3.Left, t2, cond.Cmp(t2.Field("tx_id"), "=", tokTable.Field("spent_by")))).
666682
Where(cond.And(conds...))
667683
var query string
668684
var args []any
@@ -694,20 +710,21 @@ func (db *TokenStore) ListRedeemedTokens(ctx context.Context, tokenType token.Ty
694710
}
695711

696712
// RedeemedBalance returns the sum of amounts of issued tokens spent by a Redeem action.
697-
// If tokenType is non-empty, only tokens of that type are included.
713+
// Uses the same self-JOIN approach as ListRedeemedTokens to avoid relying on the
714+
// transactions table.
698715
func (db *TokenStore) RedeemedBalance(ctx context.Context, tokenType token.Type, issuerRaw tdriver.Identity, from, to *time.Time) (uint64, error) {
699716
tokTable := q.Table(db.table.Tokens)
700-
txTable := q.Table(db.table.Transactions)
717+
t2 := q.AliasedTable(db.table.Tokens, "t2")
701718
conds := []cond.Condition{
702-
cond.Eq("issuer", true),
703-
cond.Eq("is_deleted", true),
704-
cond.Eq("action_type", int(driver.Redeem)),
719+
cond.CmpVal(tokTable.Field("issuer"), "=", true),
720+
cond.CmpVal(tokTable.Field("is_deleted"), "=", true),
721+
isNull(t2.Field("tx_id")),
705722
}
706723
if len(tokenType) != 0 {
707724
conds = append(conds, cond.CmpVal(tokTable.Field("token_type"), "=", tokenType))
708725
}
709726
if len(issuerRaw) != 0 {
710-
conds = append(conds, cond.Eq("issuer_raw", issuerRaw))
727+
conds = append(conds, cond.CmpVal(tokTable.Field("issuer_raw"), "=", issuerRaw))
711728
}
712729
if from != nil {
713730
conds = append(conds, cond.CmpVal(tokTable.Field("stored_at"), ">=", from.UTC()))
@@ -717,7 +734,7 @@ func (db *TokenStore) RedeemedBalance(ctx context.Context, tokenType token.Type,
717734
}
718735
query, args := q.Select().
719736
FieldsByName(common3.FieldName("SUM(" + db.table.Tokens + ".amount)")).
720-
From(tokTable.Join(txTable, cond.Cmp(tokTable.Field("spent_by"), "=", txTable.Field("tx_id")))).
737+
From(tokTable.JoinAs(common3.Left, t2, cond.Cmp(t2.Field("tx_id"), "=", tokTable.Field("spent_by")))).
721738
Where(cond.And(conds...)).
722739
Format(db.ci)
723740

0 commit comments

Comments
 (0)