Skip to content

Commit

Permalink
feat: validate keysend payment does not exceed app internal balance
Browse files Browse the repository at this point in the history
  • Loading branch information
rolznz committed Jul 11, 2024
1 parent f49caf1 commit aefd4cf
Showing 1 changed file with 64 additions and 44 deletions.
108 changes: 64 additions & 44 deletions transactions/transactions_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,41 +122,11 @@ func (svc *transactionsService) SendPaymentSync(ctx context.Context, payReq stri
var dbTransaction *db.Transaction

err = svc.db.Transaction(func(tx *gorm.DB) error {
// ensure balance for isolated apps
if appId != nil {
var appPermission db.AppPermission
tx.Find(&appPermission, &db.AppPermission{
AppId: *appId,
})

if appPermission.BalanceType == "isolated" {
var received struct {
Sum uint64
}
tx.
Table("transactions").
Select("SUM(amount) as sum").
Where("app_id = ? AND type = ? AND state = ?", appPermission.AppId, TRANSACTION_TYPE_INCOMING, TRANSACTION_STATE_SETTLED).Scan(&received)

var spent struct {
Sum uint64
}
tx.
Table("transactions").
Select("SUM(amount + fee) as sum").
Where("app_id = ? AND type = ? AND (state = ? OR state = ?)", appPermission.AppId, TRANSACTION_TYPE_OUTGOING, TRANSACTION_STATE_SETTLED, TRANSACTION_STATE_PENDING).Scan(&spent)

// TODO: ensure fee reserve for external payment
balance := received.Sum - spent.Sum
if balance < uint64(paymentRequest.MSatoshi) {
// TODO: add a proper error type so INSUFFICIENT_BALANCE is returned
return errors.New("Insufficient balance")
}
}
err := svc.validateCanPay(tx, appId, uint64(paymentRequest.MSatoshi))
if err != nil {
return err
}

// TODO: ensure budget is not exceeded

var expiresAt *time.Time
if paymentRequest.Expiry > 0 {
expiresAtValue := time.Now().Add(time.Duration(paymentRequest.Expiry) * time.Second)
Expand Down Expand Up @@ -239,24 +209,35 @@ func (svc *transactionsService) SendPaymentSync(ctx context.Context, payReq stri
}

func (svc *transactionsService) SendKeysend(ctx context.Context, amount uint64, destination string, customRecords []lnclient.TLVRecord, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error) {
// TODO: add same transaction as SendPayment to ensure balance and budget are not exceeded

metadata := map[string]interface{}{}

metadata["destination"] = destination
metadata["tlv_records"] = customRecords
metadataBytes, err := json.Marshal(metadata)

// NOTE: transaction is created without payment hash :scream:
dbTransaction := &db.Transaction{
AppId: appId,
RequestEventId: requestEventId,
Type: TRANSACTION_TYPE_OUTGOING,
State: TRANSACTION_STATE_PENDING,
Amount: amount,
Metadata: string(metadataBytes),
}
err = svc.db.Create(dbTransaction).Error
var dbTransaction *db.Transaction

err = svc.db.Transaction(func(tx *gorm.DB) error {
err := svc.validateCanPay(tx, appId, amount)
if err != nil {
return err
}

// NOTE: transaction is created without payment hash :scream:
dbTransaction = &db.Transaction{
AppId: appId,
RequestEventId: requestEventId,
Type: TRANSACTION_TYPE_OUTGOING,
State: TRANSACTION_STATE_PENDING,
Amount: amount,
Metadata: string(metadataBytes),
}
err = tx.Create(dbTransaction).Error

return err
})

if err != nil {
logger.Logger.WithFields(logrus.Fields{
"destination": destination,
Expand Down Expand Up @@ -640,3 +621,42 @@ func (svc *transactionsService) interceptSelfPayment(paymentHash string) (*lncli
Fee: &fee,
}, nil
}

func (svc *transactionsService) validateCanPay(tx *gorm.DB, appId *uint, amount uint64) error {
// ensure balance for isolated apps
if appId != nil {
var appPermission db.AppPermission
tx.Find(&appPermission, &db.AppPermission{
AppId: *appId,
})

if appPermission.BalanceType == "isolated" {
var received struct {
Sum uint64
}
tx.
Table("transactions").
Select("SUM(amount) as sum").
Where("app_id = ? AND type = ? AND state = ?", appPermission.AppId, TRANSACTION_TYPE_INCOMING, TRANSACTION_STATE_SETTLED).Scan(&received)

var spent struct {
Sum uint64
}
tx.
Table("transactions").
Select("SUM(amount + fee) as sum").
Where("app_id = ? AND type = ? AND (state = ? OR state = ?)", appPermission.AppId, TRANSACTION_TYPE_OUTGOING, TRANSACTION_STATE_SETTLED, TRANSACTION_STATE_PENDING).Scan(&spent)

// TODO: ensure fee reserve for external payment
balance := received.Sum - spent.Sum
if balance < amount {
// TODO: add a proper error type so INSUFFICIENT_BALANCE is returned
return errors.New("Insufficient balance")
}
}
// TODO: ensure budget is not exceeded
// TODO: ensure fee reserve for external payment
}

return nil
}

0 comments on commit aefd4cf

Please sign in to comment.