Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,9 +424,13 @@ func (api *api) GetApp(dbApp *db.App) *App {
}

// renewsIn := ""
budgetUsage := uint64(0)
maxAmount := uint64(paySpecificPermission.MaxAmountSat)
budgetUsage = queries.GetBudgetUsageSat(api.db, &paySpecificPermission)
budgetUsage, err := queries.GetBudgetUsageSat(api.db, &paySpecificPermission)
if err != nil {
logger.Logger.WithError(err).WithFields(logrus.Fields{
"app_id": dbApp.ID,
}).Error("Failed to get budget usage for app")
}

var metadata Metadata
if dbApp.Metadata != nil {
Expand Down Expand Up @@ -465,7 +469,14 @@ func (api *api) GetApp(dbApp *db.App) *App {
}

if dbApp.Isolated {
response.Balance = queries.GetIsolatedBalance(api.db, dbApp.ID)
balance, err := queries.GetIsolatedBalance(api.db, dbApp.ID)
if err != nil {
logger.Logger.WithError(err).WithFields(logrus.Fields{
"app_id": dbApp.ID,
}).Error("Failed to get isolated app balance")
} else {
response.Balance = balance
}
}

return &response
Expand Down Expand Up @@ -571,7 +582,14 @@ func (api *api) ListApps(limit uint64, offset uint64, filters ListAppsFilters, o
}

if dbApp.Isolated {
apiApp.Balance = queries.GetIsolatedBalance(api.db, dbApp.ID)
balance, err := queries.GetIsolatedBalance(api.db, dbApp.ID)
if err != nil {
logger.Logger.WithError(err).WithFields(logrus.Fields{
"app_id": dbApp.ID,
}).Error("Failed to get isolated app balance")
return nil, err
}
apiApp.Balance = balance
}

for _, appPermission := range permissionsMap[dbApp.ID] {
Expand All @@ -580,7 +598,14 @@ func (api *api) ListApps(limit uint64, offset uint64, filters ListAppsFilters, o
if appPermission.Scope == constants.PAY_INVOICE_SCOPE {
apiApp.BudgetRenewal = appPermission.BudgetRenewal
apiApp.MaxAmountSat = uint64(appPermission.MaxAmountSat)
apiApp.BudgetUsage = queries.GetBudgetUsageSat(api.db, &appPermission)
budgetUsage, err := queries.GetBudgetUsageSat(api.db, &appPermission)
if err != nil {
logger.Logger.WithError(err).WithFields(logrus.Fields{
"app_id": dbApp.ID,
}).Error("Failed to get budget usage for app")
return nil, err
}
apiApp.BudgetUsage = budgetUsage
}
}

Expand Down
11 changes: 7 additions & 4 deletions db/queries/get_budget_usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@ import (
"gorm.io/gorm"
)

func GetBudgetUsageSat(tx *gorm.DB, appPermission *db.AppPermission) uint64 {
func GetBudgetUsageSat(tx *gorm.DB, appPermission *db.AppPermission) (uint64, error) {
var result struct {
Sum uint64
}
tx.
err := tx.
Table("transactions").
Select("SUM(amount_msat + fee_msat + fee_reserve_msat) as sum").
Where("app_id = ? AND type = ? AND (state = ? OR state = ?) AND created_at > ?", appPermission.AppId, constants.TRANSACTION_TYPE_OUTGOING, constants.TRANSACTION_STATE_SETTLED, constants.TRANSACTION_STATE_PENDING, getStartOfBudget(appPermission.BudgetRenewal)).Scan(&result)
return result.Sum / 1000
Where("app_id = ? AND type = ? AND (state = ? OR state = ?) AND created_at > ?", appPermission.AppId, constants.TRANSACTION_TYPE_OUTGOING, constants.TRANSACTION_STATE_SETTLED, constants.TRANSACTION_STATE_PENDING, getStartOfBudget(appPermission.BudgetRenewal)).Scan(&result).Error
if err != nil {
return 0, err
}
return result.Sum / 1000, nil
}

func getStartOfBudget(budget_type string) time.Time {
Expand Down
18 changes: 12 additions & 6 deletions db/queries/get_isolated_balance.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,29 @@ import (
"gorm.io/gorm"
)

func GetIsolatedBalance(tx *gorm.DB, appId uint) int64 {
func GetIsolatedBalance(tx *gorm.DB, appId uint) (int64, error) {
var received struct {
Sum int64
}
tx.
err := tx.
Table("transactions").
Select("SUM(amount_msat) as sum").
Where("app_id = ? AND type = ? AND state = ?", appId, constants.TRANSACTION_TYPE_INCOMING, constants.TRANSACTION_STATE_SETTLED).Scan(&received)
Where("app_id = ? AND type = ? AND state = ?", appId, constants.TRANSACTION_TYPE_INCOMING, constants.TRANSACTION_STATE_SETTLED).Scan(&received).Error
if err != nil {
return 0, err
}

var spent struct {
Sum int64
}

tx.
err = tx.
Table("transactions").
Select("SUM(amount_msat + fee_msat + fee_reserve_msat) as sum").
Where("app_id = ? AND type = ? AND (state = ? OR state = ?)", appId, constants.TRANSACTION_TYPE_OUTGOING, constants.TRANSACTION_STATE_SETTLED, constants.TRANSACTION_STATE_PENDING).Scan(&spent)
Where("app_id = ? AND type = ? AND (state = ? OR state = ?)", appId, constants.TRANSACTION_TYPE_OUTGOING, constants.TRANSACTION_STATE_SETTLED, constants.TRANSACTION_STATE_PENDING).Scan(&spent).Error
if err != nil {
return 0, err
}

return received.Sum - spent.Sum
return received.Sum - spent.Sum, nil
}
6 changes: 4 additions & 2 deletions db/queries/get_isolated_balance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ func TestGetIsolatedBalance_PendingNoOverflow(t *testing.T) {
}
svc.DB.Save(&tx)

balance := GetIsolatedBalance(svc.DB, app.ID)
balance, err := GetIsolatedBalance(svc.DB, app.ID)
require.NoError(t, err)
assert.Equal(t, int64(-11000), balance)
}

Expand Down Expand Up @@ -65,6 +66,7 @@ func TestGetIsolatedBalance_SettledNoOverflow(t *testing.T) {
}
svc.DB.Save(&tx)

balance := GetIsolatedBalance(svc.DB, app.ID)
balance, err := GetIsolatedBalance(svc.DB, app.ID)
require.NoError(t, err)
assert.Equal(t, int64(-1000), balance)
}
13 changes: 12 additions & 1 deletion nip47/controllers/get_balance_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,18 @@ func (controller *nip47Controller) HandleGetBalanceEvent(ctx context.Context, ni

balance := int64(0)
if app.Isolated {
balance = queries.GetIsolatedBalance(controller.db, app.ID)
var err error
balance, err = queries.GetIsolatedBalance(controller.db, app.ID)
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"request_event_id": requestEventId,
}).WithError(err).Error("Failed to fetch isolated balance")
publishResponse(&models.Response{
ResultType: nip47Request.Method,
Error: mapNip47Error(err),
}, nostr.Tags{})
return
}
} else {
balances, err := controller.lnClient.GetBalances(ctx, true)
balance = balances.Lightning.TotalSpendable
Expand Down
13 changes: 12 additions & 1 deletion nip47/controllers/get_budget_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,18 @@ func (controller *nip47Controller) HandleGetBudgetEvent(ctx context.Context, nip
return
}

usedBudget := queries.GetBudgetUsageSat(controller.db, &appPermission)
usedBudget, err := queries.GetBudgetUsageSat(controller.db, &appPermission)
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"request_event_id": requestEventId,
}).WithError(err).Error("Failed to fetch budget usage")
publishResponse(&models.Response{
ResultType: nip47Request.Method,
Error: mapNip47Error(err),
}, nostr.Tags{})
return
}

responsePayload := &getBudgetResponse{
TotalBudget: uint64(maxAmount * 1000),
UsedBudget: usedBudget * 1000,
Expand Down
12 changes: 9 additions & 3 deletions transactions/keysend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,9 @@ func TestSendKeysend_IsolatedAppToNoApp(t *testing.T) {
result := svc.DB.Find(&transactions)
assert.Equal(t, int64(3), result.RowsAffected)
// expect balance to be decreased
assert.Equal(t, int64(10000), queries.GetIsolatedBalance(svc.DB, app.ID))
balance, err := queries.GetIsolatedBalance(svc.DB, app.ID)
assert.NoError(t, err)
assert.Equal(t, int64(10000), balance)
}

func TestSendKeysend_IsolatedAppToIsolatedApp(t *testing.T) {
Expand Down Expand Up @@ -489,10 +491,14 @@ func TestSendKeysend_IsolatedAppToIsolatedApp(t *testing.T) {
result := svc.DB.Find(&transactions)
assert.Equal(t, int64(3), result.RowsAffected)
// expect balance to be decreased
assert.Equal(t, int64(10000), queries.GetIsolatedBalance(svc.DB, app.ID))
balance, err := queries.GetIsolatedBalance(svc.DB, app.ID)
assert.NoError(t, err)
assert.Equal(t, int64(10000), balance)

// expect app2 to receive the payment
assert.Equal(t, int64(123000), queries.GetIsolatedBalance(svc.DB, app2.ID))
balance, err = queries.GetIsolatedBalance(svc.DB, app2.ID)
assert.NoError(t, err)
assert.Equal(t, int64(123000), balance)

// check notifications
assert.Equal(t, 2, len(mockEventConsumer.GetConsumedEvents()))
Expand Down
24 changes: 18 additions & 6 deletions transactions/self_payments_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ func TestSendPaymentSync_SelfPayment_NoAppToIsolatedApp(t *testing.T) {
result := svc.DB.Find(&transactions)
assert.Equal(t, int64(2), result.RowsAffected)
// expect balance to be increased
assert.Equal(t, int64(123000), queries.GetIsolatedBalance(svc.DB, app.ID))
balance, err := queries.GetIsolatedBalance(svc.DB, app.ID)
assert.NoError(t, err)
assert.Equal(t, int64(123000), balance)
}

func TestSendPaymentSync_SelfPayment_NoAppToApp(t *testing.T) {
Expand Down Expand Up @@ -230,7 +232,9 @@ func TestSendPaymentSync_SelfPayment_IsolatedAppToNoApp(t *testing.T) {
result := svc.DB.Find(&transactions)
assert.Equal(t, int64(3), result.RowsAffected)
// expect balance to be decreased
assert.Equal(t, int64(0), queries.GetIsolatedBalance(svc.DB, app.ID))
balance, err := queries.GetIsolatedBalance(svc.DB, app.ID)
assert.NoError(t, err)
assert.Equal(t, int64(0), balance)
}

func TestSendPaymentSync_SelfPayment_IsolatedAppToApp(t *testing.T) {
Expand Down Expand Up @@ -306,7 +310,9 @@ func TestSendPaymentSync_SelfPayment_IsolatedAppToApp(t *testing.T) {
result := svc.DB.Find(&transactions)
assert.Equal(t, int64(3), result.RowsAffected)
// expect balance to be decreased
assert.Equal(t, int64(0), queries.GetIsolatedBalance(svc.DB, app.ID))
balance, err := queries.GetIsolatedBalance(svc.DB, app.ID)
assert.NoError(t, err)
assert.Equal(t, int64(0), balance)
}

func TestSendPaymentSync_SelfPayment_IsolatedAppToIsolatedApp(t *testing.T) {
Expand Down Expand Up @@ -388,7 +394,9 @@ func TestSendPaymentSync_SelfPayment_IsolatedAppToIsolatedApp(t *testing.T) {
result := svc.DB.Find(&transactions)
assert.Equal(t, int64(3), result.RowsAffected)
// expect balance to be decreased
assert.Equal(t, int64(0), queries.GetIsolatedBalance(svc.DB, app.ID))
balance, err := queries.GetIsolatedBalance(svc.DB, app.ID)
assert.NoError(t, err)
assert.Equal(t, int64(0), balance)

// check notifications
assert.Equal(t, 2, len(mockEventConsumer.GetConsumedEvents()))
Expand Down Expand Up @@ -481,7 +489,9 @@ func TestSendPaymentSync_SelfPayment_IsolatedAppToSelf(t *testing.T) {
assert.Equal(t, int64(3), result.RowsAffected)

// expect balance to be unchanged
assert.Equal(t, int64(123000), queries.GetIsolatedBalance(svc.DB, app.ID))
balance, err := queries.GetIsolatedBalance(svc.DB, app.ID)
assert.NoError(t, err)
assert.Equal(t, int64(123000), balance)
}

func TestSendPaymentSync_SelfPayment_IsolatedAppToApp_AmountProvidedIgnoredOnNonZeroAmountInvoice(t *testing.T) {
Expand Down Expand Up @@ -560,5 +570,7 @@ func TestSendPaymentSync_SelfPayment_IsolatedAppToApp_AmountProvidedIgnoredOnNon
result := svc.DB.Find(&transactions)
assert.Equal(t, int64(3), result.RowsAffected)
// expect balance to be decreased
assert.Equal(t, int64(0), queries.GetIsolatedBalance(svc.DB, app.ID))
balance, err := queries.GetIsolatedBalance(svc.DB, app.ID)
assert.NoError(t, err)
assert.Equal(t, int64(0), balance)
}
16 changes: 13 additions & 3 deletions transactions/transactions_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -1054,7 +1054,10 @@ func (svc *transactionsService) validateCanPay(tx *gorm.DB, appId *uint, amount
}

if app.Isolated {
balance := queries.GetIsolatedBalance(tx, appPermission.AppId)
balance, err := queries.GetIsolatedBalance(tx, appPermission.AppId)
if err != nil {
return errors.New("failed to calculate isolated balance for app")
}

if int64(amountWithFeeReserve) > balance {
logger.Logger.WithFields(logrus.Fields{
Expand All @@ -1081,7 +1084,10 @@ func (svc *transactionsService) validateCanPay(tx *gorm.DB, appId *uint, amount
}

if appPermission.MaxAmountSat > 0 {
budgetUsageSat := queries.GetBudgetUsageSat(tx, &appPermission)
budgetUsageSat, err := queries.GetBudgetUsageSat(tx, &appPermission)
if err != nil {
return errors.New("failed to calculate budget usage for app")
}
if int(amountWithFeeReserve/1000) > appPermission.MaxAmountSat-int(budgetUsageSat) {
message := NewQuotaExceededError().Error()
if description != "" {
Expand Down Expand Up @@ -1422,7 +1428,11 @@ func (svc *transactionsService) checkBudgetUsage(dbTransaction *db.Transaction,
return
}

budgetUsage := queries.GetBudgetUsageSat(gormTransaction, &appPermission)
budgetUsage, err := queries.GetBudgetUsageSat(gormTransaction, &appPermission)
if err != nil {
logger.Logger.WithField("app_id", dbTransaction.AppId).WithError(err).Error("failed to get budget usage")
return
}
warningUsage := uint64(math.Floor(float64(appPermission.MaxAmountSat) * 0.8))
if budgetUsage >= warningUsage && budgetUsage-dbTransaction.AmountMsat/1000 < warningUsage {
svc.eventPublisher.Publish(&events.Event{
Expand Down