Skip to content

Commit

Permalink
mint - handle pending proofs
Browse files Browse the repository at this point in the history
  • Loading branch information
elnosh committed Sep 30, 2024
1 parent 00ed11a commit c5727cc
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 3 deletions.
3 changes: 2 additions & 1 deletion cashu/cashu.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,8 @@ var (
MintingDisabled = Error{Detail: "minting is disabled", Code: MintingDisabledErrCode}
MintAmountExceededErr = Error{Detail: "max amount for minting exceeded", Code: AmountLimitExceeded}
OutputsOverQuoteAmountErr = Error{Detail: "sum of the output amounts is greater than quote amount", Code: StandardErrCode}
ProofAlreadyUsedErr = Error{Detail: "proofs already used", Code: ProofAlreadyUsedErrCode}
ProofAlreadyUsedErr = Error{Detail: "proof already used", Code: ProofAlreadyUsedErrCode}
ProofPendingErr = Error{Detail: "proof is pending", Code: ProofAlreadyUsedErrCode}
InvalidProofErr = Error{Detail: "invalid proof", Code: InvalidProofErrCode}
NoProofsProvided = Error{Detail: "no proofs provided", Code: InvalidProofErrCode}
DuplicateProofs = Error{Detail: "duplicate proofs", Code: InvalidProofErrCode}
Expand Down
28 changes: 26 additions & 2 deletions mint/mint.go
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,13 @@ func (m *Mint) MeltTokens(method, quoteId string, proofs cashu.Proofs) (storage.
return storage.MeltQuote{}, nut11.SigAllOnlySwap
}

// set proofs as pending before trying to make payment
err = m.db.AddPendingProofs(proofs, meltQuote.Id)
if err != nil {
msg := fmt.Sprintf("error setting proofs as pending in db: %v", err)
return storage.MeltQuote{}, cashu.BuildCashuError(msg, cashu.DBErrCode)
}

// before asking backend to send payment, check if quotes can be settled
// internally (i.e mint and melt quotes exist with the same invoice)
mintQuote, err := m.db.GetMintQuoteByPaymentHash(meltQuote.PaymentHash)
Expand All @@ -566,7 +573,6 @@ func (m *Mint) MeltTokens(method, quoteId string, proofs cashu.Proofs) (storage.
}

// if payment succeeded, mark melt quote as paid
// and invalidate proofs
meltQuote.State = nut05.Paid
meltQuote.Preimage = preimage
err = m.db.UpdateMeltQuote(meltQuote.Id, meltQuote.Preimage, meltQuote.State)
Expand All @@ -576,6 +582,13 @@ func (m *Mint) MeltTokens(method, quoteId string, proofs cashu.Proofs) (storage.
}
}

// if quote is paid, unset pending proofs and mark them as
// used by adding them to db
err = m.db.RemovePendingProofs(Ys)
if err != nil {
msg := fmt.Sprintf("error removing proofs from pending: %v", err)
return storage.MeltQuote{}, cashu.BuildCashuError(msg, cashu.DBErrCode)
}
err = m.db.SaveProofs(proofs)
if err != nil {
msg := fmt.Sprintf("error invalidating proofs. Could not save proofs to db: %v", err)
Expand Down Expand Up @@ -668,7 +681,18 @@ func (m *Mint) verifyProofs(proofs cashu.Proofs, Ys []string) error {
return cashu.NoProofsProvided
}

// check if proofs were alredy used
// check if proofs are either pending or already spent
pendingProofs, err := m.db.GetPendingProofs(Ys)
if err != nil {
if !errors.Is(err, sql.ErrNoRows) {
msg := fmt.Sprintf("could not get pending proofs from db: %v", err)
return cashu.BuildCashuError(msg, cashu.DBErrCode)
}
}
if len(pendingProofs) != 0 {
return cashu.ProofPendingErr
}

usedProofs, err := m.db.GetProofsUsed(Ys)
if err != nil {
if !errors.Is(err, sql.ErrNoRows) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

DROP TABLE IF EXISTS pending_proofs;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE TABLE IF NOT EXISTS pending_proofs (
y TEXT PRIMARY KEY,
amount INTEGER NOT NULL,
keyset_id TEXT NOT NULL,
secret TEXT NOT NULL UNIQUE,
c TEXT NOT NULL,
melt_quote_id TEXT NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_pending_proofs_y ON pending_proofs(y);
121 changes: 121 additions & 0 deletions mint/storage/sqlite/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,127 @@ func (sqlite *SQLiteDB) GetProofsUsed(Ys []string) ([]storage.DBProof, error) {
return proofs, nil
}

func (sqlite *SQLiteDB) AddPendingProofs(proofs cashu.Proofs, quoteId string) error {
tx, err := sqlite.db.Begin()
if err != nil {
return err
}

stmt, err := tx.Prepare("INSERT INTO pending_proofs (y, amount, keyset_id, secret, c, melt_quote_id) VALUES (?, ?, ?, ?, ?, ?)")
if err != nil {
return err
}
defer stmt.Close()

for _, proof := range proofs {
Y, err := crypto.HashToCurve([]byte(proof.Secret))
if err != nil {
return err
}
Yhex := hex.EncodeToString(Y.SerializeCompressed())

if _, err := stmt.Exec(Yhex, proof.Amount, proof.Id, proof.Secret, proof.C, quoteId); err != nil {
tx.Rollback()
return err
}
}

if err := tx.Commit(); err != nil {
return err
}

return nil
}

func (sqlite *SQLiteDB) GetPendingProofs(Ys []string) ([]storage.DBProof, error) {
proofs := []storage.DBProof{}
query := `SELECT * FROM pending_proofs WHERE y in (?` + strings.Repeat(",?", len(Ys)-1) + `)`

args := make([]any, len(Ys))
for i, y := range Ys {
args[i] = y
}

rows, err := sqlite.db.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()

for rows.Next() {
var proof storage.DBProof
err := rows.Scan(
&proof.Y,
&proof.Amount,
&proof.Id,
&proof.Secret,
&proof.C,
)
if err != nil {
return nil, err
}

proofs = append(proofs, proof)
}

return proofs, nil
}

func (sqlite *SQLiteDB) GetPendingProofsByQuote(quoteId string) ([]storage.DBProof, error) {
proofs := []storage.DBProof{}
query := `SELECT * FROM pending_proofs WHERE melt_quote_id = ?`

rows, err := sqlite.db.Query(query, quoteId)
if err != nil {
return nil, err
}
defer rows.Close()

for rows.Next() {
var proof storage.DBProof
err := rows.Scan(
&proof.Y,
&proof.Amount,
&proof.Id,
&proof.Secret,
&proof.C,
)
if err != nil {
return nil, err
}

proofs = append(proofs, proof)
}

return proofs, nil
}

func (sqlite *SQLiteDB) RemovePendingProofs(Ys []string) error {
tx, err := sqlite.db.Begin()
if err != nil {
return err
}

stmt, err := tx.Prepare("DELETE FROM pending_proofs WHERE y = ?")
if err != nil {
return err
}
defer stmt.Close()

for _, y := range Ys {
if _, err := stmt.Exec(y); err != nil {
tx.Rollback()
return err
}
}

if err := tx.Commit(); err != nil {
return err
}

return nil
}

func (sqlite *SQLiteDB) SaveMintQuote(mintQuote storage.MintQuote) error {
_, err := sqlite.db.Exec(
`INSERT INTO mint_quotes (id, payment_request, payment_hash, amount, state, expiry)
Expand Down
4 changes: 4 additions & 0 deletions mint/storage/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ type MintDB interface {

SaveProofs(cashu.Proofs) error
GetProofsUsed(Ys []string) ([]DBProof, error)
AddPendingProofs(proofs cashu.Proofs, quoteId string) error
GetPendingProofs(Ys []string) ([]DBProof, error)
GetPendingProofsByQuote(quoteId string) ([]DBProof, error)
RemovePendingProofs(Ys []string) error

SaveMintQuote(MintQuote) error
GetMintQuote(string) (MintQuote, error)
Expand Down

0 comments on commit c5727cc

Please sign in to comment.