feat: support multiple relays (#1802)

* feat: support multiple relays (WIP)

* fix: multiple NWC url construction for multiple relays

* fix: startup

* chore: update go-nostr to fix pool relay reconnect

* fix: split nip-47 queue info publish event for each relay url

* fix: add relayUrls to dispatched nwc success events

* fix: ensure newly created app info publishing is not blocked by sleep from failed publishes

* feat: multiple relays improvements

- update app deleted and updated consumers to handle multiple relays
- handle multiple relays in healthcheck
- display relay online status in about page
- renaming and improved comments

* chore: simplify error messages

* fix: unnecessary db error log when checking if app has notification permission

* fix: tests

* chore: remove old comment

* fix: app wallet subscription context usage
This commit is contained in:
Roland 2025-11-06 18:01:09 +07:00 committed by GitHub
parent 944bb11c28
commit 377a3169c6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 487 additions and 403 deletions

View file

@ -7,16 +7,34 @@ import (
"github.com/nbd-wtf/go-nostr"
)
type mockRelay struct {
type mockSimplePool struct {
PublishedEvents []*nostr.Event
}
func NewMockRelay() *mockRelay {
return &mockRelay{}
func NewMockSimplePool() *mockSimplePool {
return &mockSimplePool{}
}
func (relay *mockRelay) Publish(ctx context.Context, event nostr.Event) error {
func (relay *mockSimplePool) PublishMany(ctx context.Context, relayUrls []string, event nostr.Event) chan nostr.PublishResult {
logger.Logger.WithField("event", event).Info("Mock Publishing event")
relay.PublishedEvents = append(relay.PublishedEvents, &event)
channel := make(chan nostr.PublishResult)
go func() {
channel <- nostr.PublishResult{
RelayURL: "wss://fakerelay.com/v1",
}
close(channel)
}()
return channel
}
func (relay *mockSimplePool) QuerySingle(
ctx context.Context,
urls []string,
filter nostr.Filter,
opts ...nostr.SubscriptionOption,
) *nostr.RelayEvent {
logger.Logger.Error("Mock pool QuerySingle is not supported yet")
return nil
}

View file

@ -404,46 +404,48 @@ func (_c *MockConfig_GetNetwork_Call) RunAndReturn(run func() string) *MockConfi
return _c
}
// GetRelayUrl provides a mock function for the type MockConfig
func (_mock *MockConfig) GetRelayUrl() string {
// GetRelayUrls provides a mock function for the type MockConfig
func (_mock *MockConfig) GetRelayUrls() []string {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for GetRelayUrl")
panic("no return value specified for GetRelayUrls")
}
var r0 string
if returnFunc, ok := ret.Get(0).(func() string); ok {
var r0 []string
if returnFunc, ok := ret.Get(0).(func() []string); ok {
r0 = returnFunc()
} else {
r0 = ret.Get(0).(string)
if ret.Get(0) != nil {
r0 = ret.Get(0).([]string)
}
}
return r0
}
// MockConfig_GetRelayUrl_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRelayUrl'
type MockConfig_GetRelayUrl_Call struct {
// MockConfig_GetRelayUrls_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRelayUrls'
type MockConfig_GetRelayUrls_Call struct {
*mock.Call
}
// GetRelayUrl is a helper method to define mock.On call
func (_e *MockConfig_Expecter) GetRelayUrl() *MockConfig_GetRelayUrl_Call {
return &MockConfig_GetRelayUrl_Call{Call: _e.mock.On("GetRelayUrl")}
// GetRelayUrls is a helper method to define mock.On call
func (_e *MockConfig_Expecter) GetRelayUrls() *MockConfig_GetRelayUrls_Call {
return &MockConfig_GetRelayUrls_Call{Call: _e.mock.On("GetRelayUrls")}
}
func (_c *MockConfig_GetRelayUrl_Call) Run(run func()) *MockConfig_GetRelayUrl_Call {
func (_c *MockConfig_GetRelayUrls_Call) Run(run func()) *MockConfig_GetRelayUrls_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockConfig_GetRelayUrl_Call) Return(s string) *MockConfig_GetRelayUrl_Call {
_c.Call.Return(s)
func (_c *MockConfig_GetRelayUrls_Call) Return(strings []string) *MockConfig_GetRelayUrls_Call {
_c.Call.Return(strings)
return _c
}
func (_c *MockConfig_GetRelayUrl_Call) RunAndReturn(run func() string) *MockConfig_GetRelayUrl_Call {
func (_c *MockConfig_GetRelayUrls_Call) RunAndReturn(run func() []string) *MockConfig_GetRelayUrls_Call {
_c.Call.Return(run)
return _c
}

View file

@ -9,6 +9,7 @@ import (
"github.com/getAlby/hub/config"
"github.com/getAlby/hub/events"
"github.com/getAlby/hub/lnclient"
"github.com/getAlby/hub/service"
"github.com/getAlby/hub/service/keys"
"github.com/getAlby/hub/swaps"
"github.com/getAlby/hub/transactions"
@ -365,6 +366,52 @@ func (_c *MockService_GetLNClient_Call) RunAndReturn(run func() lnclient.LNClien
return _c
}
// GetRelayStatuses provides a mock function for the type MockService
func (_mock *MockService) GetRelayStatuses() []service.RelayStatus {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for GetRelayStatuses")
}
var r0 []service.RelayStatus
if returnFunc, ok := ret.Get(0).(func() []service.RelayStatus); ok {
r0 = returnFunc()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]service.RelayStatus)
}
}
return r0
}
// MockService_GetRelayStatuses_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRelayStatuses'
type MockService_GetRelayStatuses_Call struct {
*mock.Call
}
// GetRelayStatuses is a helper method to define mock.On call
func (_e *MockService_Expecter) GetRelayStatuses() *MockService_GetRelayStatuses_Call {
return &MockService_GetRelayStatuses_Call{Call: _e.mock.On("GetRelayStatuses")}
}
func (_c *MockService_GetRelayStatuses_Call) Run(run func()) *MockService_GetRelayStatuses_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockService_GetRelayStatuses_Call) Return(relayStatuss []service.RelayStatus) *MockService_GetRelayStatuses_Call {
_c.Call.Return(relayStatuss)
return _c
}
func (_c *MockService_GetRelayStatuses_Call) RunAndReturn(run func() []service.RelayStatus) *MockService_GetRelayStatuses_Call {
_c.Call.Return(run)
return _c
}
// GetStartupState provides a mock function for the type MockService
func (_mock *MockService) GetStartupState() string {
ret := _mock.Called()
@ -501,50 +548,6 @@ func (_c *MockService_GetTransactionsService_Call) RunAndReturn(run func() trans
return _c
}
// IsRelayReady provides a mock function for the type MockService
func (_mock *MockService) IsRelayReady() bool {
ret := _mock.Called()
if len(ret) == 0 {
panic("no return value specified for IsRelayReady")
}
var r0 bool
if returnFunc, ok := ret.Get(0).(func() bool); ok {
r0 = returnFunc()
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// MockService_IsRelayReady_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IsRelayReady'
type MockService_IsRelayReady_Call struct {
*mock.Call
}
// IsRelayReady is a helper method to define mock.On call
func (_e *MockService_Expecter) IsRelayReady() *MockService_IsRelayReady_Call {
return &MockService_IsRelayReady_Call{Call: _e.mock.On("IsRelayReady")}
}
func (_c *MockService_IsRelayReady_Call) Run(run func()) *MockService_IsRelayReady_Call {
_c.Call.Run(func(args mock.Arguments) {
run()
})
return _c
}
func (_c *MockService_IsRelayReady_Call) Return(b bool) *MockService_IsRelayReady_Call {
_c.Call.Return(b)
return _c
}
func (_c *MockService_IsRelayReady_Call) RunAndReturn(run func() bool) *MockService_IsRelayReady_Call {
_c.Call.Return(run)
return _c
}
// Shutdown provides a mock function for the type MockService
func (_mock *MockService) Shutdown() {
_mock.Called()