Skip to content

Commit

Permalink
Merge branch 'feat/dynamic-budgets' into task-lnd-notifs
Browse files Browse the repository at this point in the history
  • Loading branch information
im-adithya committed Jul 19, 2024
2 parents 6176eca + 30c1799 commit 4ff30fe
Show file tree
Hide file tree
Showing 17 changed files with 1,373 additions and 54 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ frontend/src/utils/openLink.ts
glalby

*.db-shm
*.db-wal
*.db-wal
*.db-journal
2 changes: 1 addition & 1 deletion api/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (api *api) LookupInvoice(ctx context.Context, paymentHash string) (*LookupI
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
}
transaction, err := api.svc.GetTransactionsService().LookupTransaction(ctx, paymentHash, api.svc.GetLNClient(), nil)
transaction, err := api.svc.GetTransactionsService().LookupTransaction(ctx, paymentHash, nil, api.svc.GetLNClient(), nil)
if err != nil {
return nil, err
}
Expand Down
3 changes: 2 additions & 1 deletion db/migrations/202407012100_transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ CREATE TABLE transactions(
updated_at datetime,
expires_at datetime,
settled_at datetime,
metadata text
metadata text,
self_payment boolean
);
DROP TABLE payments;
Expand Down
1 change: 1 addition & 0 deletions db/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ type Transaction struct {
UpdatedAt time.Time
SettledAt *time.Time
Metadata string
SelfPayment bool
}

type DBService interface {
Expand Down
2 changes: 1 addition & 1 deletion nip47/controllers/lookup_invoice_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (controller *nip47Controller) HandleLookupInvoiceEvent(ctx context.Context,
paymentHash = paymentRequest.PaymentHash
}

dbTransaction, err := controller.transactionsService.LookupTransaction(ctx, paymentHash, controller.lnClient, &appId)
dbTransaction, err := controller.transactionsService.LookupTransaction(ctx, paymentHash, nil, controller.lnClient, &appId)
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"request_event_id": requestEventId,
Expand Down
6 changes: 4 additions & 2 deletions nip47/notifications/nip47_notifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ func (notifier *Nip47Notifier) ConsumeEvent(ctx context.Context, event *events.E
return
}

transaction, err := notifier.transactionsService.LookupTransaction(ctx, lnClientTransaction.PaymentHash, notifier.lnClient, nil)
transactionType := constants.TRANSACTION_TYPE_INCOMING
transaction, err := notifier.transactionsService.LookupTransaction(ctx, lnClientTransaction.PaymentHash, &transactionType, notifier.lnClient, nil)
if err != nil {
logger.Logger.
WithField("paymentHash", lnClientTransaction.PaymentHash).
Expand All @@ -79,7 +80,8 @@ func (notifier *Nip47Notifier) ConsumeEvent(ctx context.Context, event *events.E
return
}

transaction, err := notifier.transactionsService.LookupTransaction(ctx, paymentSentEventProperties.PaymentHash, notifier.lnClient, nil)
transactionType := constants.TRANSACTION_TYPE_OUTGOING
transaction, err := notifier.transactionsService.LookupTransaction(ctx, paymentSentEventProperties.PaymentHash, &transactionType, notifier.lnClient, nil)
if err != nil {
logger.Logger.
WithField("paymentHash", paymentSentEventProperties.PaymentHash).
Expand Down
8 changes: 4 additions & 4 deletions nip47/notifications/nip47_notifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func TestSendNotification_PaymentReceived(t *testing.T) {
feesPaid := uint64(tests.MockLNClientTransaction.FeesPaid)
settledAt := time.Unix(*tests.MockLNClientTransaction.SettledAt, 0)
err = svc.DB.Create(&db.Transaction{
Type: tests.MockLNClientTransaction.Type,
Type: constants.TRANSACTION_TYPE_INCOMING,
PaymentRequest: tests.MockLNClientTransaction.Invoice,
Description: tests.MockLNClientTransaction.Description,
DescriptionHash: tests.MockLNClientTransaction.DescriptionHash,
Expand Down Expand Up @@ -101,7 +101,7 @@ func TestSendNotification_PaymentReceived(t *testing.T) {
assert.Equal(t, PAYMENT_RECEIVED_NOTIFICATION, unmarshalledResponse.NotificationType)

transaction := (unmarshalledResponse.Notification.(*PaymentReceivedNotification))
assert.Equal(t, tests.MockLNClientTransaction.Type, transaction.Type)
assert.Equal(t, constants.TRANSACTION_TYPE_INCOMING, transaction.Type)
assert.Equal(t, tests.MockLNClientTransaction.Invoice, transaction.Invoice)
assert.Equal(t, tests.MockLNClientTransaction.Description, transaction.Description)
assert.Equal(t, tests.MockLNClientTransaction.DescriptionHash, transaction.DescriptionHash)
Expand Down Expand Up @@ -132,7 +132,7 @@ func TestSendNotification_PaymentSent(t *testing.T) {
feesPaid := uint64(tests.MockLNClientTransaction.FeesPaid)
settledAt := time.Unix(*tests.MockLNClientTransaction.SettledAt, 0)
err = svc.DB.Create(&db.Transaction{
Type: tests.MockLNClientTransaction.Type,
Type: constants.TRANSACTION_TYPE_OUTGOING,
PaymentRequest: tests.MockLNClientTransaction.Invoice,
Description: tests.MockLNClientTransaction.Description,
DescriptionHash: tests.MockLNClientTransaction.DescriptionHash,
Expand Down Expand Up @@ -182,7 +182,7 @@ func TestSendNotification_PaymentSent(t *testing.T) {
assert.Equal(t, PAYMENT_SENT_NOTIFICATION, unmarshalledResponse.NotificationType)

transaction := (unmarshalledResponse.Notification.(*PaymentReceivedNotification))
assert.Equal(t, tests.MockLNClientTransaction.Type, transaction.Type)
assert.Equal(t, constants.TRANSACTION_TYPE_OUTGOING, transaction.Type)
assert.Equal(t, tests.MockLNClientTransaction.Invoice, transaction.Invoice)
assert.Equal(t, tests.MockLNClientTransaction.Description, transaction.Description)
assert.Equal(t, tests.MockLNClientTransaction.DescriptionHash, transaction.DescriptionHash)
Expand Down
9 changes: 7 additions & 2 deletions tests/mock_ln_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ var MockLNClientTransactions = []lnclient.Transaction{
Description: "mock invoice 1",
DescriptionHash: "hash1",
Preimage: "preimage1",
PaymentHash: "payment_hash_1",
PaymentHash: MockPaymentHash,
Amount: 1000,
FeesPaid: 50,
SettledAt: &MockTimeUnix,
Expand All @@ -48,7 +48,7 @@ var MockLNClientTransactions = []lnclient.Transaction{
Description: "mock invoice 2",
DescriptionHash: "hash2",
Preimage: "preimage2",
PaymentHash: "payment_hash_2",
PaymentHash: MockPaymentHash,
Amount: 2000,
FeesPaid: 75,
SettledAt: &MockTimeUnix,
Expand All @@ -59,6 +59,7 @@ var MockLNClientTransaction = &MockLNClientTransactions[0]
type MockLn struct {
PayInvoiceResponses []*lnclient.PayInvoiceResponse
PayInvoiceErrors []error
Pubkey string
}

func NewMockLn() (*MockLn, error) {
Expand Down Expand Up @@ -178,5 +179,9 @@ func (mln *MockLn) GetSupportedNIP47NotificationTypes() []string {
return []string{"payment_received", "payment_sent"}
}
func (mln *MockLn) GetPubkey() string {
if mln.Pubkey != "" {
return mln.Pubkey
}

return "123pubkey"
}
4 changes: 3 additions & 1 deletion tests/test_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package tests

import (
"os"
"strconv"

"github.com/getAlby/hub/config"
"github.com/getAlby/hub/db"
"github.com/getAlby/hub/events"
"github.com/getAlby/hub/lnclient"
"github.com/getAlby/hub/logger"
"github.com/getAlby/hub/service/keys"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
)

Expand All @@ -25,7 +27,7 @@ func CreateTestService() (svc *TestService, err error) {
return nil, err
}

logger.Init("")
logger.Init(strconv.Itoa(int(logrus.DebugLevel)))

appConfig := &config.AppConfig{
Workdir: ".test",
Expand Down
219 changes: 219 additions & 0 deletions transactions/app_payments_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
package transactions

import (
"context"
"testing"
"time"

"github.com/getAlby/hub/constants"
"github.com/getAlby/hub/db"
"github.com/getAlby/hub/tests"
"github.com/stretchr/testify/assert"
)

func TestSendPaymentSync_App_NoPermission(t *testing.T) {
ctx := context.TODO()

defer tests.RemoveTestService()
svc, err := tests.CreateTestService()
assert.NoError(t, err)

app, _, err := tests.CreateApp(svc)
assert.NoError(t, err)

dbRequestEvent := &db.RequestEvent{}
err = svc.DB.Create(&dbRequestEvent).Error
assert.NoError(t, err)

transactionsService := NewTransactionsService(svc.DB)
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, svc.LNClient, &app.ID, &dbRequestEvent.ID)

assert.Error(t, err)
assert.Equal(t, "app does not have pay_invoice scope", err.Error())
assert.Nil(t, transaction)
}
func TestSendPaymentSync_App_WithPermission(t *testing.T) {
ctx := context.TODO()

defer tests.RemoveTestService()
svc, err := tests.CreateTestService()
assert.NoError(t, err)

app, _, err := tests.CreateApp(svc)
assert.NoError(t, err)

appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
Scope: constants.PAY_INVOICE_SCOPE,
}
err = svc.DB.Create(appPermission).Error
assert.NoError(t, err)

dbRequestEvent := &db.RequestEvent{}
err = svc.DB.Create(&dbRequestEvent).Error
assert.NoError(t, err)

transactionsService := NewTransactionsService(svc.DB)
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, svc.LNClient, &app.ID, &dbRequestEvent.ID)

assert.NoError(t, err)
assert.Equal(t, uint64(123000), transaction.AmountMsat)
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
assert.Equal(t, "123preimage", *transaction.Preimage)
assert.Equal(t, app.ID, *transaction.AppId)
assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
}

func TestSendPaymentSync_App_BudgetExceeded(t *testing.T) {
ctx := context.TODO()

defer tests.RemoveTestService()
svc, err := tests.CreateTestService()
assert.NoError(t, err)

app, _, err := tests.CreateApp(svc)
assert.NoError(t, err)

appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
Scope: constants.PAY_INVOICE_SCOPE,
MaxAmountSat: 1,
}
err = svc.DB.Create(appPermission).Error
assert.NoError(t, err)

dbRequestEvent := &db.RequestEvent{}
err = svc.DB.Create(&dbRequestEvent).Error
assert.NoError(t, err)

transactionsService := NewTransactionsService(svc.DB)
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, svc.LNClient, &app.ID, &dbRequestEvent.ID)

assert.Error(t, err)
assert.ErrorIs(t, err, NewQuotaExceededError())
assert.Nil(t, transaction)
}

func TestSendPaymentSync_App_BudgetExceeded_SettledPayment(t *testing.T) {
ctx := context.TODO()

defer tests.RemoveTestService()
svc, err := tests.CreateTestService()
assert.NoError(t, err)

app, _, err := tests.CreateApp(svc)
assert.NoError(t, err)

appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
Scope: constants.PAY_INVOICE_SCOPE,
MaxAmountSat: 133, // invoice is 123 sats, but we also calculate fee reserves max of(10 sats or 1%)
}
err = svc.DB.Create(appPermission).Error
assert.NoError(t, err)

// 1 sat payment pushes app over the limit
svc.DB.Create(&db.Transaction{
AppId: &app.ID,
State: constants.TRANSACTION_STATE_SETTLED,
Type: constants.TRANSACTION_TYPE_OUTGOING,
AmountMsat: 1000,
CreatedAt: time.Now(),
})

dbRequestEvent := &db.RequestEvent{}
err = svc.DB.Create(&dbRequestEvent).Error
assert.NoError(t, err)

transactionsService := NewTransactionsService(svc.DB)
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, svc.LNClient, &app.ID, &dbRequestEvent.ID)

assert.Error(t, err)
assert.ErrorIs(t, err, NewQuotaExceededError())
assert.Nil(t, transaction)
}
func TestSendPaymentSync_App_BudgetExceeded_UnsettledPayment(t *testing.T) {
ctx := context.TODO()

defer tests.RemoveTestService()
svc, err := tests.CreateTestService()
assert.NoError(t, err)

app, _, err := tests.CreateApp(svc)
assert.NoError(t, err)

appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
Scope: constants.PAY_INVOICE_SCOPE,
MaxAmountSat: 133, // invoice is 123 sats, but we also calculate fee reserves max of(10 sats or 1%)
}
err = svc.DB.Create(appPermission).Error
assert.NoError(t, err)

// 1 sat payment pushes app over the limit
svc.DB.Create(&db.Transaction{
AppId: &app.ID,
State: constants.TRANSACTION_STATE_PENDING,
Type: constants.TRANSACTION_TYPE_OUTGOING,
AmountMsat: 1000,
CreatedAt: time.Now(),
})

dbRequestEvent := &db.RequestEvent{}
err = svc.DB.Create(&dbRequestEvent).Error
assert.NoError(t, err)

transactionsService := NewTransactionsService(svc.DB)
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, svc.LNClient, &app.ID, &dbRequestEvent.ID)

assert.Error(t, err)
assert.ErrorIs(t, err, NewQuotaExceededError())
assert.Nil(t, transaction)
}

func TestSendPaymentSync_App_BudgetNotExceeded_FailedPayment(t *testing.T) {
ctx := context.TODO()

defer tests.RemoveTestService()
svc, err := tests.CreateTestService()
assert.NoError(t, err)

app, _, err := tests.CreateApp(svc)
assert.NoError(t, err)

appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
Scope: constants.PAY_INVOICE_SCOPE,
MaxAmountSat: 133, // invoice is 123 sats, but we also calculate fee reserves max of(10 sats or 1%)
}
err = svc.DB.Create(appPermission).Error
assert.NoError(t, err)

// 1 sat payment would push app over the limit, but it failed so its not counted
svc.DB.Create(&db.Transaction{
AppId: &app.ID,
State: constants.TRANSACTION_STATE_FAILED,
Type: constants.TRANSACTION_TYPE_OUTGOING,
AmountMsat: 1000,
CreatedAt: time.Now(),
})

dbRequestEvent := &db.RequestEvent{}
err = svc.DB.Create(&dbRequestEvent).Error
assert.NoError(t, err)

transactionsService := NewTransactionsService(svc.DB)
transaction, err := transactionsService.SendPaymentSync(ctx, tests.MockLNClientTransaction.Invoice, svc.LNClient, &app.ID, &dbRequestEvent.ID)

assert.NoError(t, err)
assert.Equal(t, uint64(123000), transaction.AmountMsat)
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, transaction.State)
assert.Equal(t, "123preimage", *transaction.Preimage)
assert.Equal(t, app.ID, *transaction.AppId)
assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
}
Loading

0 comments on commit 4ff30fe

Please sign in to comment.