From 3fed36432eefd431699ec87364b2ef273a21e9bc Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Wed, 6 Nov 2024 14:57:35 +0700 Subject: [PATCH] feat: enable VSS for new hubs with alby subscription (wip) --- .env.example | 2 ++ README.md | 5 +++-- alby/alby_oauth_service.go | 38 ++++++++++++++++++++++++++++++++++++++ alby/models.go | 26 +++++++++++++++++--------- config/models.go | 1 + lnclient/ldk/ldk.go | 15 +++++++++++++-- service/start.go | 38 +++++++++++++++++++++++++++++++++++--- 7 files changed, 109 insertions(+), 16 deletions(-) diff --git a/.env.example b/.env.example index 03c8a36c..97f61696 100644 --- a/.env.example +++ b/.env.example @@ -35,3 +35,5 @@ FRONTEND_URL=http://localhost:5173 #LND_CERT_FILE=/home/YOUR_USERNAME/.polar/networks/1/volumes/lnd/alice/tls.cert #LND_ADDRESS=127.0.0.1:10001 #LND_MACAROON_FILE=/home/YOUR_USERNAME/.polar/networks/1/volumes/lnd/alice/data/chain/bitcoin/regtest/admin.macaroon + +#LDK_VSS_URL="http://localhost:8090/vss" \ No newline at end of file diff --git a/README.md b/README.md index 6b1c8f09..56fd39ab 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Go to the [Deploy it yourself](#deploy-it-yourself) section below. ## Supported Backends -By default Alby Hub uses the embedded LDK based lightning node. Optionally it can be configured to use an external node: +By default Alby Hub uses the embedded LDK based lightning node. Optionally it can be configured to use an external node: - LND - Phoenixd @@ -41,6 +41,7 @@ By default Alby Hub uses the embedded LDK based lightning node. Optionally it ca - Yarn ### Environment setup + $ cp .env.example .env # edit the config for your needs (Read further down for all the available env options) $ vim .env @@ -163,6 +164,7 @@ _To configure via env, the following parameters must be provided:_ ### LDK Backend parameters - `LDK_ESPLORA_SERVER`: By default the optimized Alby esplora is used. You can configure your own esplora server (note: the public blockstream one is slow and can cause onchain syncing and issues with opening channels) +- `LDK_VSS_URL`: Use VSS (encrypted remote storage) rather than local sqlite store for lightning and bitcoin data. Currently this feature only works for brand new Alby Hub instances that are connected to Alby Accounts with an active subscription plan. #### LDK Network Configuration @@ -362,7 +364,6 @@ Go to the [Quick start script](https://github.com/getAlby/hub/tree/master/script Go to the [Quick start script](https://github.com/getAlby/hub/blob/master/scripts/pi-aarch64) which you can run as a service. - #### Quick start (Raspberry PI Zero) Go to the [Quick start script](https://github.com/getAlby/hub/tree/master/scripts/pi-arm) which you can run as a service. diff --git a/alby/alby_oauth_service.go b/alby/alby_oauth_service.go index f9ebdbda..21719709 100644 --- a/alby/alby_oauth_service.go +++ b/alby/alby_oauth_service.go @@ -281,6 +281,44 @@ func (svc *albyOAuthService) GetInfo(ctx context.Context) (*AlbyInfo, error) { }, nil } +func (svc *albyOAuthService) GetVssToken(ctx context.Context) (string, error) { + token, err := svc.fetchUserToken(ctx) + if err != nil { + logger.Logger.WithError(err).Error("Failed to fetch user token") + return "", err + } + + client := svc.oauthConf.Client(ctx, token) + + req, err := http.NewRequest("GET", fmt.Sprintf("%s/internal/users/vss", albyOAuthAPIURL), nil) + if err != nil { + logger.Logger.WithError(err).Error("Error creating request for vss endpoint") + return "", err + } + + setDefaultRequestHeaders(req) + + res, err := client.Do(req) + if err != nil { + logger.Logger.WithError(err).Error("Failed to fetch vss endpoint") + return "", err + } + + type vssTokenResponse struct { + Token string `json:"token"` + } + + vssResponse := &vssTokenResponse{} + err = json.NewDecoder(res.Body).Decode(vssResponse) + if err != nil { + logger.Logger.WithError(err).Error("Failed to decode API response") + return "", err + } + + logger.Logger.WithFields(logrus.Fields{"vssTokenResponse": vssResponse}).Info("Alby vss response") + return vssResponse.Token, nil +} + func (svc *albyOAuthService) GetMe(ctx context.Context) (*AlbyMe, error) { token, err := svc.fetchUserToken(ctx) if err != nil { diff --git a/alby/models.go b/alby/models.go index 9c4a1cfc..0fb1e394 100644 --- a/alby/models.go +++ b/alby/models.go @@ -23,6 +23,7 @@ type AlbyOAuthService interface { DrainSharedWallet(ctx context.Context, lnClient lnclient.LNClient) error UnlinkAccount(ctx context.Context) error RequestAutoChannel(ctx context.Context, lnClient lnclient.LNClient, isPublic bool) (*AutoChannelResponse, error) + GetVssToken(ctx context.Context) (string, error) } type AlbyBalanceResponse struct { @@ -61,16 +62,23 @@ type AlbyInfo struct { type AlbyMeHub struct { Name string `json:"name"` } + +type AlbyMeSubscription struct { + // PlanCode string `json:"plan_code"` + Buzz bool `json:"buzz"` +} + type AlbyMe struct { - Identifier string `json:"identifier"` - NPub string `json:"nostr_pubkey"` - LightningAddress string `json:"lightning_address"` - Email string `json:"email"` - Name string `json:"name"` - Avatar string `json:"avatar"` - KeysendPubkey string `json:"keysend_pubkey"` - SharedNode bool `json:"shared_node"` - Hub AlbyMeHub `json:"hub"` + Identifier string `json:"identifier"` + NPub string `json:"nostr_pubkey"` + LightningAddress string `json:"lightning_address"` + Email string `json:"email"` + Name string `json:"name"` + Avatar string `json:"avatar"` + KeysendPubkey string `json:"keysend_pubkey"` + SharedNode bool `json:"shared_node"` + Hub AlbyMeHub `json:"hub"` + Subscription AlbyMeSubscription `json:"subscription"` } type AlbyBalance struct { diff --git a/config/models.go b/config/models.go index 0491494b..eb54ba0a 100644 --- a/config/models.go +++ b/config/models.go @@ -28,6 +28,7 @@ type AppConfig struct { LDKEsploraServer string `envconfig:"LDK_ESPLORA_SERVER" default:"https://electrs.getalbypro.com"` // TODO: remove LDK prefix LDKGossipSource string `envconfig:"LDK_GOSSIP_SOURCE"` LDKLogLevel string `envconfig:"LDK_LOG_LEVEL" default:"3"` + LDKVssUrl string `envconfig:"LDK_VSS_URL"` MempoolApi string `envconfig:"MEMPOOL_API" default:"https://mempool.space/api"` AlbyClientId string `envconfig:"ALBY_OAUTH_CLIENT_ID" default:"J2PbXS1yOf"` AlbyClientSecret string `envconfig:"ALBY_OAUTH_CLIENT_SECRET" default:"rABK2n16IWjLTZ9M1uKU"` diff --git a/lnclient/ldk/ldk.go b/lnclient/ldk/ldk.go index bd60f908..8a785c40 100644 --- a/lnclient/ldk/ldk.go +++ b/lnclient/ldk/ldk.go @@ -49,7 +49,8 @@ type LDKService struct { const resetRouterKey = "ResetRouter" -func NewLDKService(ctx context.Context, cfg config.Config, eventPublisher events.EventPublisher, mnemonic, workDir string, network string, staticChannelsBackup *events.StaticChannelsBackupEvent, restoredFromSeed bool) (result lnclient.LNClient, err error) { +// TODO: remove staticChannelsBackup *events.StaticChannelsBackupEvent, restoredFromSeed bool (we have a dedicated SCB recovery tool) +func NewLDKService(ctx context.Context, cfg config.Config, eventPublisher events.EventPublisher, mnemonic, workDir string, network string, staticChannelsBackup *events.StaticChannelsBackupEvent, restoredFromSeed bool, vssToken string) (result lnclient.LNClient, err error) { if mnemonic == "" || workDir == "" { return nil, errors.New("one or more required LDK configuration are missing") } @@ -126,7 +127,17 @@ func NewLDKService(ctx context.Context, cfg config.Config, eventPublisher events builder.RestoreEncodedChannelMonitors(getEncodedChannelMonitorsFromStaticChannelsBackup(staticChannelsBackup)) } - node, err := builder.Build() + logger.Logger.WithFields(logrus.Fields{ + "vss_token": vssToken, + }).Info("Creating node") + var node *ldk_node.Node + if vssToken != "" { + node, err = builder.BuildWithVssStoreAndFixedHeaders(cfg.GetEnv().LDKVssUrl, "albyhub", map[string]string{ + "Authorization": fmt.Sprintf("Bearer %s", vssToken), + }) + } else { + node, err = builder.Build() + } if err != nil { logger.Logger.Errorf("Failed to create LDK node: %v", err) diff --git a/service/start.go b/service/start.go index a8353cb8..1ac78498 100644 --- a/service/start.go +++ b/service/start.go @@ -180,10 +180,42 @@ func (svc *service) launchLNBackend(ctx context.Context, encryptionKey string) e LNDMacaroonHex, _ := svc.cfg.Get("LNDMacaroonHex", encryptionKey) lnClient, err = lnd.NewLNDService(ctx, svc.eventPublisher, LNDAddress, LNDCertHex, LNDMacaroonHex) case config.LDKBackendType: - Mnemonic, _ := svc.cfg.Get("Mnemonic", encryptionKey) - LDKWorkdir := path.Join(svc.cfg.GetEnv().Workdir, "ldk") + mnemonic, _ := svc.cfg.Get("Mnemonic", encryptionKey) + ldkWorkdir := path.Join(svc.cfg.GetEnv().Workdir, "ldk") + + nodeLastStartTime, _ := svc.cfg.Get("NodeLastStartTime", "") + + // for brand new nodes, consider enabling VSS + if nodeLastStartTime == "" { + albyUserIdentifier, err := svc.albyOAuthSvc.GetUserIdentifier() + if err != nil { + logger.Logger.WithError(err).Error("Failed to fetch alby user identifier") + return err + } + if albyUserIdentifier != "" { + me, err := svc.albyOAuthSvc.GetMe(ctx) + if err != nil { + logger.Logger.WithError(err).Error("Failed to fetch alby user") + return err + } + // only activate VSS for Alby paid subscribers + if me.Subscription.Buzz { + svc.cfg.SetUpdate("LdkVssEnabled", "true", "") + } + } + } + + vssToken := "" + vssEnabled, _ := svc.cfg.Get("LdkVssEnabled", "") + if vssEnabled == "true" { + vssToken, err = svc.albyOAuthSvc.GetVssToken(ctx) + if err != nil { + logger.Logger.WithError(err).Error("Failed to fetch VSS JWT token") + return err + } + } - lnClient, err = ldk.NewLDKService(ctx, svc.cfg, svc.eventPublisher, Mnemonic, LDKWorkdir, svc.cfg.GetEnv().LDKNetwork, nil, false) + lnClient, err = ldk.NewLDKService(ctx, svc.cfg, svc.eventPublisher, mnemonic, ldkWorkdir, svc.cfg.GetEnv().LDKNetwork, nil, false, vssToken) case config.GreenlightBackendType: Mnemonic, _ := svc.cfg.Get("Mnemonic", encryptionKey) GreenlightInviteCode, _ := svc.cfg.Get("GreenlightInviteCode", encryptionKey)