Skip to content

Commit

Permalink
Merge branch 'master' into fix/zapstore
Browse files Browse the repository at this point in the history
  • Loading branch information
reneaaron authored Dec 23, 2024
2 parents 7219d19 + ad2d8dd commit 4903f4f
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 25 deletions.
44 changes: 36 additions & 8 deletions alby/alby_oauth_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"math"
"net/http"
"regexp"
"slices"
"strconv"
"strings"
"sync"
Expand Down Expand Up @@ -645,11 +646,15 @@ func (svc *albyOAuthService) ConsumeEvent(ctx context.Context, event *events.Eve
return
}

// TODO: we should have a whitelist rather than a blacklist, so new events are not automatically sent

// TODO: rename this config option to be specific to the alby API
if !svc.cfg.GetEnv().LogEvents {
logger.Logger.WithField("event", event).Debug("Skipped sending to alby events API")
logger.Logger.WithField("event", event).Debug("Skipped sending to alby events API (alby event logging disabled)")
return
}

// ensure we do not send unintended events to Alby API
if !slices.Contains(getEventWhitelist(), event.Event) {
logger.Logger.WithField("event", event).Debug("Skipped sending non-whitelisted event to alby events API")
return
}

Expand All @@ -660,11 +665,6 @@ func (svc *albyOAuthService) ConsumeEvent(ctx context.Context, event *events.Eve
return
}

if strings.HasPrefix(event.Event, "nwc_lnclient_") {
// don't consume internal LNClient events
return
}

if event.Event == "nwc_payment_received" {
type paymentReceivedEventProperties struct {
PaymentHash string `json:"payment_hash"`
Expand Down Expand Up @@ -1322,3 +1322,31 @@ func (svc *albyOAuthService) deleteAlbyAccountApps() {
logger.Logger.WithError(err).Error("Failed to delete Alby Account apps")
}
}

// whitelist of events that can be sent to the alby API
// (e.g. to enable encrypted static channel backups and sending email notifications)
func getEventWhitelist() []string {
return []string{
"nwc_backup_channels",
"nwc_payment_received",
"nwc_payment_sent",
"nwc_payment_failed",
"nwc_app_created",
"nwc_app_updated",
"nwc_app_deleted",
"nwc_unlocked",
"nwc_node_sync_failed",
"nwc_outgoing_liquidity_required",
"nwc_incoming_liquidity_required",
"nwc_budget_warning",
"nwc_channel_ready",
"nwc_channel_closed",
"nwc_permission_denied",
"nwc_started",
"nwc_stopped",
"nwc_node_started",
"nwc_node_start_failed",
"nwc_node_stop_failed",
"nwc_node_stopped",
}
}
22 changes: 16 additions & 6 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,13 @@ func (api *api) UpdateApp(userApp *db.App, updateAppRequest *UpdateAppRequest) e
return err
}
}
api.svc.GetEventPublisher().Publish(&events.Event{
Event: "nwc_app_updated",
Properties: map[string]interface{}{
"name": name,
"id": userApp.ID,
},
})

// commit transaction
return nil
Expand All @@ -252,13 +259,13 @@ func (api *api) GetApp(dbApp *db.App) *App {
for _, appPerm := range appPermissions {
expiresAt = appPerm.ExpiresAt
if appPerm.Scope == constants.PAY_INVOICE_SCOPE {
//find the pay_invoice-specific permissions
// find the pay_invoice-specific permissions
paySpecificPermission = appPerm
}
requestMethods = append(requestMethods, appPerm.Scope)
}

//renewsIn := ""
// renewsIn := ""
budgetUsage := uint64(0)
maxAmount := uint64(paySpecificPermission.MaxAmountSat)
budgetUsage = queries.GetBudgetUsageSat(api.db, &paySpecificPermission)
Expand Down Expand Up @@ -975,10 +982,13 @@ func (api *api) GetLogOutput(ctx context.Context, logType string, getLogRequest
}
} else if logType == LogTypeApp {
logFileName := logger.GetLogFilePath()

logData, err = utils.ReadFileTail(logFileName, getLogRequest.MaxLen)
if err != nil {
return nil, err
if logFileName == "" {
logData = []byte("file log is disabled")
} else {
logData, err = utils.ReadFileTail(logFileName, getLogRequest.MaxLen)
if err != nil {
return nil, err
}
}
} else {
return nil, fmt.Errorf("invalid log type: '%s'", logType)
Expand Down
1 change: 1 addition & 0 deletions config/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type AppConfig struct {
DatabaseUri string `envconfig:"DATABASE_URI" default:"nwc.db"`
JWTSecret string `envconfig:"JWT_SECRET"`
LogLevel string `envconfig:"LOG_LEVEL" default:"4"`
LogToFile bool `envconfig:"LOG_TO_FILE" default:"true"`
LDKNetwork string `envconfig:"LDK_NETWORK" default:"bitcoin"`
LDKEsploraServer string `envconfig:"LDK_ESPLORA_SERVER" default:"https://electrs.getalbypro.com"` // TODO: remove LDK prefix
LDKGossipSource string `envconfig:"LDK_GOSSIP_SOURCE"`
Expand Down
4 changes: 2 additions & 2 deletions nip47/controllers/pay_invoice_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const nip47PayInvoice0AmountJson = `
{
"method": "pay_invoice",
"params": {
"invoice": "lnbc1pn428a6pp5njn604kl6x7eruycwzpwapwe97uer8et084kru4r0hsql9ttpmusdp82pshjgr5dusyymrfde4jq4mpd3kx2apq24ek2uscqzpuxqr8pqsp50nvadnrqlvghx44ftl89gjvx9lvrfpy5sypt2zahmpehvex4lp7q9p4gqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqysgq395pa893uux2agv8jlqxsppyyx5e4rfdjn9dhynlzxhuq3sgwnv48akdqxut7s4fqrre8adnt8kmfmjaexdcevajqlxvmtjpp823cmspud7udk",
"invoice": "` + tests.Mock0AmountInvoice + `",
"amount": 1234
}
}
Expand Down Expand Up @@ -144,7 +144,7 @@ func TestHandlePayInvoiceEvent_0Amount(t *testing.T) {
assert.Equal(t, "123preimage", publishedResponse.Result.(payResponse).Preimage)

transactionType := constants.TRANSACTION_TYPE_OUTGOING
transaction, err := transactionsSvc.LookupTransaction(ctx, "9ca7a7d6dfd1bd91f0987082ee85d92fb9919f2b79eb61f2a37de00f956b0ef9", &transactionType, svc.LNClient, &app.ID)
transaction, err := transactionsSvc.LookupTransaction(ctx, tests.Mock0AmountPaymentHash, &transactionType, svc.LNClient, &app.ID)
assert.NoError(t, err)
// from the request amount
assert.Equal(t, uint64(1234), transaction.AmountMsat)
Expand Down
31 changes: 27 additions & 4 deletions nip47/publish_nip47_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import (
"strconv"
"strings"

"github.com/getAlby/hub/db"
"github.com/getAlby/hub/lnclient"
"github.com/getAlby/hub/logger"
"github.com/getAlby/hub/nip47/models"
nostrmodels "github.com/getAlby/hub/nostr/models"
"github.com/nbd-wtf/go-nostr"
"github.com/sirupsen/logrus"
)

func (svc *nip47Service) GetNip47Info(ctx context.Context, relay *nostr.Relay, appWalletPubKey string) (*nostr.Event, error) {
Expand All @@ -32,18 +35,38 @@ func (svc *nip47Service) GetNip47Info(ctx context.Context, relay *nostr.Relay, a
}

func (svc *nip47Service) PublishNip47Info(ctx context.Context, relay nostrmodels.Relay, appWalletPubKey string, appWalletPrivKey string, lnClient lnclient.LNClient) (*nostr.Event, error) {
// TODO: should the capabilities be based on the app permissions? (for non-legacy apps)
capabilities := lnClient.GetSupportedNIP47Methods()
if len(lnClient.GetSupportedNIP47NotificationTypes()) > 0 {
var capabilities []string
var permitsNotifications bool
tags := nostr.Tags{[]string{}}
if svc.keys.GetNostrPublicKey() == appWalletPubKey {
// legacy app, so return lnClient.GetSupportedNIP47Methods()
capabilities = lnClient.GetSupportedNIP47Methods()
permitsNotifications = true
} else {
app := db.App{}
err := svc.db.First(&app, &db.App{
WalletPubkey: &appWalletPubKey,
}).Error
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"walletPubKey": appWalletPubKey,
}).WithError(err).Error("Failed to find app for wallet pubkey")
return nil, err
}
capabilities = svc.permissionsService.GetPermittedMethods(&app, lnClient)
permitsNotifications = svc.permissionsService.PermitsNotifications(&app)
}
if permitsNotifications && len(lnClient.GetSupportedNIP47NotificationTypes()) > 0 {
capabilities = append(capabilities, "notifications")
tags = append(tags, []string{"notifications", strings.Join(lnClient.GetSupportedNIP47NotificationTypes(), " ")})
}

ev := &nostr.Event{}
ev.Kind = models.INFO_EVENT_KIND
ev.Content = strings.Join(capabilities, " ")
ev.CreatedAt = nostr.Now()
ev.PubKey = appWalletPubKey
ev.Tags = nostr.Tags{[]string{"notifications", strings.Join(lnClient.GetSupportedNIP47NotificationTypes(), " ")}}
ev.Tags = tags
err := ev.Sign(appWalletPrivKey)
if err != nil {
return nil, err
Expand Down
9 changes: 5 additions & 4 deletions service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package service

import (
"context"

"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -63,9 +62,11 @@ func NewService(ctx context.Context) (*service, error) {
// make sure workdir exists
os.MkdirAll(appConfig.Workdir, os.ModePerm)

err = logger.AddFileLogger(appConfig.Workdir)
if err != nil {
return nil, err
if appConfig.LogToFile {
err = logger.AddFileLogger(appConfig.Workdir)
if err != nil {
return nil, err
}
}

err = finishRestoreNode(appConfig.Workdir)
Expand Down
8 changes: 8 additions & 0 deletions service/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func (svc *service) startNostr(ctx context.Context) error {
var relay *nostr.Relay
waitToReconnectSeconds := 0
var createAppEventListener events.EventSubscriber
var updateAppEventListener events.EventSubscriber
for i := 0; ; i++ {
// wait for a delay if any before retrying
contextCancelled := false
Expand Down Expand Up @@ -94,6 +95,13 @@ func (svc *service) startNostr(ctx context.Context) error {
createAppEventListener = &createAppConsumer{svc: svc, relay: relay}
svc.eventPublisher.RegisterSubscriber(createAppEventListener)

// register a subscriber for events of "nwc_app_updated" which handles re-publishing of nip47 event info
if updateAppEventListener != nil {
svc.eventPublisher.RemoveSubscriber(updateAppEventListener)
}
updateAppEventListener = &updateAppConsumer{svc: svc, relay: relay}
svc.eventPublisher.RegisterSubscriber(updateAppEventListener)

// start each app wallet subscription which have a child derived wallet key
svc.startAllExistingAppsWalletSubscriptions(ctx, relay)

Expand Down
53 changes: 53 additions & 0 deletions service/update_app_consumer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package service

import (
"context"

"github.com/getAlby/hub/events"
"github.com/getAlby/hub/logger"
"github.com/nbd-wtf/go-nostr"
)

type updateAppConsumer struct {
events.EventSubscriber
svc *service
relay *nostr.Relay
}

// When a app is updated, re-publish the nip47 info event
func (s *updateAppConsumer) ConsumeEvent(ctx context.Context, event *events.Event, globalProperties map[string]interface{}) {
if event.Event != "nwc_app_updated" {
return
}

properties, ok := event.Properties.(map[string]interface{})
if !ok {
logger.Logger.WithField("event", event).Error("Failed to cast event.Properties to map")
return
}
id, ok := properties["id"].(uint)
if !ok {
logger.Logger.WithField("event", event).Error("Failed to get app id")
return
}
walletPrivKey, err := s.svc.keys.GetAppWalletKey(id)
if err != nil {
logger.Logger.WithError(err).Error("Failed to calculate app wallet priv key")
return
}
walletPubKey, err := nostr.GetPublicKey(walletPrivKey)
if err != nil {
logger.Logger.WithError(err).Error("Failed to calculate app wallet pub key")
return
}

if s.svc.keys.GetNostrPublicKey() != walletPubKey {
// only need to re-publish the nip47 event info if it is not a legacy wallet

_, err = s.svc.GetNip47Service().PublishNip47Info(ctx, s.relay, walletPubKey, walletPrivKey, s.svc.lnClient)
if err != nil {
logger.Logger.WithError(err).Error("Could not re-publish NIP47 info")
}

}
}
3 changes: 2 additions & 1 deletion tests/mock_ln_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ const MockPaymentHash500 = "be8ad5d0b82071d538dcd160e3a3af444bd890de68388a4d771b
const MockInvoice = "lntbs1230n1pnkqautdqyw3jsnp4q09a0z84kg4a2m38zjllw43h953fx5zvqe8qxfgw694ymkq26u8zcpp5yvnh6hsnlnj4xnuh2trzlnunx732dv8ta2wjr75pdfxf6p2vlyassp5hyeg97a3ft5u769kjwsn7p0e85h79pzz8kladmnqhpcypz2uawjs9qyysgqcqpcxq8zals8sq9yeg2pa9eywkgj50cyzxd5elatujuc0c0wh6j9nat5mn34pgk8u9ufpgs99tw9ldlfk42cqlkr48au3lmuh09269prg4qkggh4a8cyqpfl0y6j"
const MockPaymentHash = "23277d5e13fce5534f9752c62fcf9337a2a6b0ebea9d21fa816a4c9d054cf93b" // for the above invoice

const Mock0AmountInvoice = "lnbc1pn428a6pp5njn604kl6x7eruycwzpwapwe97uer8et084kru4r0hsql9ttpmusdp82pshjgr5dusyymrfde4jq4mpd3kx2apq24ek2uscqzpuxqr8pqsp50nvadnrqlvghx44ftl89gjvx9lvrfpy5sypt2zahmpehvex4lp7q9p4gqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqysgq395pa893uux2agv8jlqxsppyyx5e4rfdjn9dhynlzxhuq3sgwnv48akdqxut7s4fqrre8adnt8kmfmjaexdcevajqlxvmtjpp823cmspud7udk"
const Mock0AmountInvoice = "lntbs1pnkjfgudqjd3hkueeqv4u8q6tj0ynp4qws83mqzuqptu5kfvxeles7qmyhsj6u2s6zyuft26mcr4tdmcupuupp533y9nwnsaktr9zlvyxmv97ta23faerygh3t9xvsfwytsr28lgggssp5mku3023z3kdxlpx6vrwtfxvvrxpffrquy6veex4ndk7rxhdtslhq9qyysgqcqpcxqxfvltyqva6y7k89jwtcljx399jl6wsq4lkq29vnm3rj4jxmapc6vcs358sx8mtpgh93rdc6ccqpxwwfga59zrla5m55zwzck2y2rsrxumu852sqkvpcm7"
const Mock0AmountPaymentHash = "8c4859ba70ed96328bec21b6c2f97d5453dc8c88bc56533209711701a8ff4211"

var MockNodeInfo = lnclient.NodeInfo{
Alias: "bob",
Expand Down
13 changes: 13 additions & 0 deletions transactions/isolated_app_payments_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,16 @@ func TestSendPaymentSync_IsolatedApp_BalanceSufficient_FailedPayment(t *testing.
assert.Equal(t, app.ID, *transaction.AppId)
assert.Equal(t, dbRequestEvent.ID, *transaction.RequestEventId)
}

func TestCalculateFeeReserve(t *testing.T) {
defer tests.RemoveTestService()
svc, err := tests.CreateTestService()
require.NoError(t, err)
transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)

assert.Equal(t, uint64(10_000), transactionsService.calculateFeeReserveMsat(0))
assert.Equal(t, uint64(10_000), transactionsService.calculateFeeReserveMsat(10_000))
assert.Equal(t, uint64(10_000), transactionsService.calculateFeeReserveMsat(100_000))
assert.Equal(t, uint64(10_000), transactionsService.calculateFeeReserveMsat(1000_000))
assert.Equal(t, uint64(20_000), transactionsService.calculateFeeReserveMsat(2000_000))
}

0 comments on commit 4903f4f

Please sign in to comment.