Skip to content

Commit

Permalink
mint: issued and redeemed ecash for db
Browse files Browse the repository at this point in the history
  • Loading branch information
elnosh committed Feb 19, 2025
1 parent e0bb4e3 commit 9fb4143
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 16 deletions.
35 changes: 33 additions & 2 deletions mint/mint.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ func (m *Mint) RequestMintQuote(mintQuoteRequest nut04.PostMintQuoteBolt11Reques
}
}
if m.limits.MaxBalance > 0 {
balance, err := m.db.GetBalance()
balance, err := m.TotalBalance()
if err != nil {
errmsg := fmt.Sprintf("could not get mint balance from db: %v", err)
return storage.MintQuote{}, cashu.BuildCashuError(errmsg, cashu.DBErrCode)
Expand Down Expand Up @@ -1499,6 +1499,37 @@ func (m *Mint) GetActiveKeyset() crypto.MintKeyset {
return keyset
}

func (m *Mint) EcashIssued() (map[string]uint64, error) {
return m.db.GetEcashIssued()
}

func (m *Mint) EcashRedeemed() (map[string]uint64, error) {
return m.db.GetEcashRedeemed()
}

func (m *Mint) TotalBalance() (uint64, error) {
ecashIssued, err := m.db.GetEcashIssued()
if err != nil {
return 0, err
}
var totalIssued uint64
for _, issuedForKeyset := range ecashIssued {
totalIssued += issuedForKeyset
}

ecashRedeemed, err := m.db.GetEcashRedeemed()
if err != nil {
return 0, err
}

var totalRedeemed uint64
for _, redeemedForKeyset := range ecashRedeemed {
totalRedeemed += redeemedForKeyset
}

return totalIssued - totalRedeemed, nil
}

func (m *Mint) SetMintInfo(mintInfo MintInfo) {
nuts := nut06.Nuts{
Nut04: nut06.NutSetting{
Expand Down Expand Up @@ -1582,7 +1613,7 @@ func (m Mint) RetrieveMintInfo() (nut06.MintInfo, error) {
}

mintingDisabled := false
mintBalance, err := m.db.GetBalance()
mintBalance, err := m.TotalBalance()
if err != nil {
errmsg := fmt.Sprintf("error getting mint balance: %v", err)
return nut06.MintInfo{}, cashu.BuildCashuError(errmsg, cashu.DBErrCode)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DROP VIEW total_issued;
DROP VIEW total_redeemed;
19 changes: 19 additions & 0 deletions mint/storage/sqlite/migrations/000010_keyset_balance_view.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- drop previous balance views
DROP VIEW minted_ecash;
DROP VIEW melted_ecash;
DROP VIEW balance;

-- create new balance views by keyset
CREATE VIEW IF NOT EXISTS total_issued AS
SELECT keyset_id, COALESCE(amount, 0) AS balance FROM (
SELECT keyset_id, SUM(amount) AS amount
FROM blind_signatures
GROUP BY keyset_id
);

CREATE VIEW IF NOT EXISTS total_redeemed AS
SELECT keyset_id, COALESCE(amount, 0) AS balance FROM (
SELECT keyset_id, SUM(amount) AS amount
FROM proofs
GROUP BY keyset_id
);
52 changes: 42 additions & 10 deletions mint/storage/sqlite/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,16 +104,6 @@ func (sqlite *SQLiteDB) Close() error {
return sqlite.db.Close()
}

func (sqlite *SQLiteDB) GetBalance() (uint64, error) {
var balance uint64
row := sqlite.db.QueryRow("SELECT balance FROM balance")
err := row.Scan(&balance)
if err != nil {
return 0, err
}
return balance, nil
}

func (sqlite *SQLiteDB) SaveSeed(seed []byte) error {
hexSeed := hex.EncodeToString(seed)

Expand Down Expand Up @@ -725,3 +715,45 @@ func (sqlite *SQLiteDB) GetBlindSignatures(B_s []string) (cashu.BlindedSignature

return signatures, nil
}

func (sqlite *SQLiteDB) GetEcashIssued() (map[string]uint64, error) {
ecashIssued := make(map[string]uint64)

rows, err := sqlite.db.Query("SELECT * FROM total_issued")
if err != nil {
return nil, err
}
defer rows.Close()

for rows.Next() {
var keysetId string
var amount uint64
if err := rows.Scan(&keysetId, &amount); err != nil {
return nil, err
}
ecashIssued[keysetId] = amount
}

return ecashIssued, nil
}

func (sqlite *SQLiteDB) GetEcashRedeemed() (map[string]uint64, error) {
ecashRedeemed := make(map[string]uint64)

rows, err := sqlite.db.Query("SELECT * FROM total_redeemed")
if err != nil {
return nil, err
}
defer rows.Close()

for rows.Next() {
var keysetId string
var amount uint64
if err := rows.Scan(&keysetId, &amount); err != nil {
return nil, err
}
ecashRedeemed[keysetId] = amount
}

return ecashRedeemed, nil
}
88 changes: 86 additions & 2 deletions mint/storage/sqlite/sqlite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,88 @@ func TestBlindSignatures(t *testing.T) {

}

func TestBalanceViews(t *testing.T) {
dbpath := "./balanceviewsdb"
if err := os.MkdirAll(dbpath, 0750); err != nil {
t.Fatalf("could not create directory test db: %v", err)
}

db, err := InitSQLite(dbpath)
if err != nil {
t.Fatalf("unexpected error creating sqlite db: %v", err)
}
defer os.RemoveAll(dbpath)

count := 210
B_s := generateRandomB_s(count)
blindSignatures := generateBlindSignatures(count)
if err := db.SaveBlindSignatures(B_s, blindSignatures); err != nil {
t.Fatalf("unexpected error saving blind signatures: %v", err)
}
keysetId := blindSignatures[0].Id

// 2nd batch of blind signatures
B_s = generateRandomB_s(count)
blindSignatures2 := generateBlindSignatures(count)
if err := db.SaveBlindSignatures(B_s, blindSignatures2); err != nil {
t.Fatalf("unexpected error saving blind signatures: %v", err)
}
keysetId2 := blindSignatures[0].Id

proofs := generateRandomProofs(21)
if err := db.SaveProofs(proofs); err != nil {
t.Fatalf("unexpected error saving proofs: %v", err)
}
proofKeysetId := proofs[0].Id

// 2nd batch of proofs
proofs2 := generateRandomProofs(21)
if err := db.SaveProofs(proofs2); err != nil {
t.Fatalf("unexpected error saving proofs: %v", err)
}
proofKeysetId2 := proofs[0].Id

totalIssued := blindSignatures.Amount() + blindSignatures2.Amount()
ecashIssued, err := db.GetEcashIssued()
if err != nil {
t.Fatalf("unexpected error getting issued ecash: %v", err)
}
if len(ecashIssued) != 2 {
t.Fatalf("expected map of length 2 but got '%v'", len(ecashIssued))
}
// check map hash both keyset ids
if _, ok := ecashIssued[keysetId]; !ok {
t.Fatalf("expected ecash issued with keyset id '%v' but it was not present", keysetId)
}
if _, ok := ecashIssued[keysetId2]; !ok {
t.Fatalf("expected ecash issued with keyset id '%v' but it was not present", keysetId2)
}
issuedFromDB := ecashIssued[keysetId] + ecashIssued[keysetId2]
if totalIssued != issuedFromDB {
t.Fatalf("expected total issued of '%v' but got '%v'", totalIssued, issuedFromDB)
}

totalRedeemed := proofs.Amount() + proofs2.Amount()
ecashRedeemed, err := db.GetEcashRedeemed()
if err != nil {
t.Fatalf("unexpected error getting redeemed ecash: %v", err)
}
if len(ecashRedeemed) != 2 {
t.Fatalf("expected map of length 2 but got '%v'", len(ecashRedeemed))
}
// check map hash both keyset ids
if _, ok := ecashRedeemed[proofKeysetId]; !ok {
t.Fatalf("expected ecash redeemed with keyset id '%v' but it was not present", proofKeysetId)
}
if _, ok := ecashRedeemed[proofKeysetId2]; !ok {
t.Fatalf("expected ecash redeemed with keyset id '%v' but it was not present", proofKeysetId2)
}
redeemedFromDB := ecashRedeemed[proofKeysetId] + ecashRedeemed[proofKeysetId2]
if totalRedeemed != redeemedFromDB {
t.Fatalf("expected total redeemed of '%v' but got '%v'", totalRedeemed, redeemedFromDB)
}
}

func generateRandomString(length int) string {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
b := make([]byte, length)
Expand All @@ -377,11 +459,12 @@ func generateRandomString(length int) string {

func generateRandomProofs(num int) cashu.Proofs {
proofs := make(cashu.Proofs, num)
keysetId := generateRandomString(32)

for i := 0; i < num; i++ {
proof := cashu.Proof{
Amount: 21,
Id: generateRandomString(32),
Id: keysetId,
Secret: generateRandomString(64),
C: generateRandomString(64),
}
Expand Down Expand Up @@ -456,10 +539,11 @@ func generateRandomB_s(num int) []string {

func generateBlindSignatures(num int) cashu.BlindedSignatures {
blindSigs := make(cashu.BlindedSignatures, num)
keysetId := generateRandomString(32)
for i := 0; i < num; i++ {
sig := cashu.BlindedSignature{
C_: generateRandomString(33),
Id: generateRandomString(32),
Id: keysetId,
Amount: 21,
DLEQ: &cashu.DLEQProof{
E: generateRandomString(33),
Expand Down
6 changes: 4 additions & 2 deletions mint/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import (
)

type MintDB interface {
GetBalance() (uint64, error)

SaveSeed([]byte) error
GetSeed() ([]byte, error)

Expand Down Expand Up @@ -39,6 +37,10 @@ type MintDB interface {
GetBlindSignature(B_ string) (cashu.BlindedSignature, error)
GetBlindSignatures(B_s []string) (cashu.BlindedSignatures, error)

// these return a map of keyset id and amount
GetEcashIssued() (map[string]uint64, error)
GetEcashRedeemed() (map[string]uint64, error)

Close() error
}

Expand Down

0 comments on commit 9fb4143

Please sign in to comment.