From bc49cb7d33fd815f5d43f66afbc471ffca6f97f5 Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Thu, 4 Jun 2026 08:29:05 +0200 Subject: [PATCH] fix: stop retrying NIP47 info publish for deleted apps (#2391) The NIP47 info publish queue re-enqueued every failed publish with an incrementing backoff and no terminal condition. When an app connection was deleted, PublishNip47Info fails the `db.First(&app, appId)` lookup with gorm.ErrRecordNotFound on every attempt, so the item was retried forever (observed as a steady stream of "Failed to publish NIP47 info from queue" errors from affected instances). Drop the queue item when the app no longer exists instead of requeuing. All other errors (offline relay, timeouts) still retry with backoff. Co-authored-by: Claude Opus 4.8 (1M context) --- nip47/nip47_service.go | 13 ++++++++++++ nip47/publish_nip47_info_test.go | 36 ++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 nip47/publish_nip47_info_test.go diff --git a/nip47/nip47_service.go b/nip47/nip47_service.go index 25c0b467..50d70d30 100644 --- a/nip47/nip47_service.go +++ b/nip47/nip47_service.go @@ -2,6 +2,7 @@ package nip47 import ( "context" + "errors" "time" "github.com/getAlby/go-nostr" @@ -114,6 +115,18 @@ func (svc *nip47Service) StartNip47InfoPublisher(ctx context.Context, pool *nost case req := <-svc.nip47InfoPublishQueue.Channel(): _, err := svc.PublishNip47Info(ctx, pool, req.AppId, req.AppWalletPubKey, req.AppWalletPrivKey, req.RelayUrl, lnClient) if err != nil { + // the app connection no longer exists (e.g. it was deleted), + // so the info event can never be published - drop the item + // instead of retrying forever + if errors.Is(err, gorm.ErrRecordNotFound) { + logger.Logger.WithError(err).WithFields(logrus.Fields{ + "app_id": req.AppId, + "wallet_pubkey": req.AppWalletPubKey, + "relay_url": req.RelayUrl, + }).Warn("Skipping NIP47 info publish for deleted app") + continue + } + logger.Logger.WithError(err).WithFields(logrus.Fields{ "wallet_pubkey": req.AppWalletPubKey, "relay_url": req.RelayUrl, diff --git a/nip47/publish_nip47_info_test.go b/nip47/publish_nip47_info_test.go new file mode 100644 index 00000000..548ddc7f --- /dev/null +++ b/nip47/publish_nip47_info_test.go @@ -0,0 +1,36 @@ +package nip47 + +import ( + "context" + "testing" + + "github.com/getAlby/go-nostr" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gorm.io/gorm" + + "github.com/getAlby/hub/alby" + "github.com/getAlby/hub/tests" +) + +// When an app connection has been deleted, publishing its NIP47 info must +// surface gorm.ErrRecordNotFound so the publish queue can drop the item +// instead of retrying forever. +func TestPublishNip47Info_AppNotFound(t *testing.T) { + svc, err := tests.CreateTestService(t) + require.NoError(t, err) + defer svc.Remove() + + albyOAuthSvc := alby.NewAlbyOAuthService(svc.DB, svc.Cfg, svc.Keys, svc.EventPublisher) + nip47svc := NewNip47Service(svc.DB, svc.Cfg, svc.Keys, svc.EventPublisher, albyOAuthSvc) + + walletPrivKey := nostr.GeneratePrivateKey() + walletPubKey, err := nostr.GetPublicKey(walletPrivKey) + require.NoError(t, err) + + // app id 9999999 does not exist; the DB lookup fails before the relay pool + // is ever used, so a nil pool/lnClient is fine here. + _, err = nip47svc.PublishNip47Info(context.Background(), nil, 9999999, walletPubKey, walletPrivKey, "wss://relay.example.com", nil) + require.Error(t, err) + assert.ErrorIs(t, err, gorm.ErrRecordNotFound) +}