Skip to content

Commit

Permalink
feat: migrate to VSS (#865)
Browse files Browse the repository at this point in the history
* feat: migrate to VSS (WIP)

* feat: add ui and api endpoint to migrate ldk storage to vss

* chore: update copy of enable vss dialog

* chore: bump ldk-node-go version

* chore: minor spacing in dialog

* chore: error handling
  • Loading branch information
rolznz authored Dec 16, 2024
1 parent 1fd317e commit d51db84
Show file tree
Hide file tree
Showing 11 changed files with 190 additions and 8 deletions.
22 changes: 22 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,28 @@ func (api *api) SendPaymentProbes(ctx context.Context, sendPaymentProbesRequest
return &SendPaymentProbesResponse{Error: errMessage}, nil
}

func (api *api) MigrateNodeStorage(ctx context.Context, to string) error {
if api.svc.GetLNClient() == nil {
return errors.New("LNClient not started")
}
if to != "VSS" {
return fmt.Errorf("Migration type not supported: %s", to)
}

ldkVssEnabled, err := api.cfg.Get("LdkVssEnabled", "")
if err != nil {
return err
}

if ldkVssEnabled == "true" {
return errors.New("VSS already enabled")
}

api.cfg.SetUpdate("LdkVssEnabled", "true", "")
api.cfg.SetUpdate("LdkMigrateStorage", "VSS", "")
return api.Stop()
}

func (api *api) SendSpontaneousPaymentProbes(ctx context.Context, sendSpontaneousPaymentProbesRequest *SendSpontaneousPaymentProbesRequest) (*SendSpontaneousPaymentProbesResponse, error) {
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
Expand Down
5 changes: 5 additions & 0 deletions api/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type API interface {
RequestLSPOrder(ctx context.Context, request *LSPOrderRequest) (*LSPOrderResponse, error)
CreateBackup(unlockPassword string, w io.Writer) error
RestoreBackup(unlockPassword string, r io.Reader) error
MigrateNodeStorage(ctx context.Context, to string) error
GetWalletCapabilities(ctx context.Context) (*WalletCapabilitiesResponse, error)
}

Expand Down Expand Up @@ -348,3 +349,7 @@ type Channel struct {
Status string `json:"status"`
IsOutbound bool `json:"isOutbound"`
}

type MigrateNodeStorageRequest struct {
To string `json:"to"`
}
1 change: 1 addition & 0 deletions frontend/src/hooks/useLinkAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export function useLinkAccount() {
"Your Alby Hub has successfully been linked to your Alby Account",
});
} catch (e) {
console.error(e);
toast({
title: "Your Alby Hub couldn't be linked to your Alby Account",
description: "Did you already link another Alby Hub?",
Expand Down
36 changes: 36 additions & 0 deletions frontend/src/hooks/useMigrateLDKStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from "react";
import { useToast } from "src/components/ui/use-toast";
import { request } from "src/utils/request";

export function useMigrateLDKStorage() {
const [isMigratingStorage, setMigratingStorage] = React.useState(false);
const { toast } = useToast();

const migrateLDKStorage = async (to: "VSS") => {
try {
setMigratingStorage(true);

await request("/api/node/migrate-storage", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
to,
}),
});
toast({
title: "Please unlock your hub",
});
} catch (e) {
console.error(e);
toast({
title: "Could not start hub storage migration: " + e,
variant: "destructive",
});
}
setMigratingStorage(false);
};

return { isMigratingStorage, migrateLDKStorage };
}
73 changes: 69 additions & 4 deletions frontend/src/screens/settings/AlbyAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,39 @@ import { ExitIcon } from "@radix-ui/react-icons";
import { ExternalLinkIcon } from "lucide-react";

import ExternalLink from "src/components/ExternalLink";
import Loading from "src/components/Loading";
import SettingsHeader from "src/components/SettingsHeader";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "src/components/ui/alert-dialog";
import {
Card,
CardDescription,
CardHeader,
CardTitle,
} from "src/components/ui/card";
import { LoadingButton } from "src/components/ui/loading-button";
import { UnlinkAlbyAccount } from "src/components/UnlinkAlbyAccount";
import { useAlbyMe } from "src/hooks/useAlbyMe";
import { useInfo } from "src/hooks/useInfo";
import { useMigrateLDKStorage } from "src/hooks/useMigrateLDKStorage";

export function AlbyAccount() {
const { data: info } = useInfo();
const { data: me } = useAlbyMe();
const { isMigratingStorage, migrateLDKStorage } = useMigrateLDKStorage();
if (!info || !me) {
return <Loading />;
}

return (
<>
<SettingsHeader
Expand Down Expand Up @@ -72,10 +93,54 @@ export function AlbyAccount() {
your recovery phrase alone, without having to close your channels.
</p>
{info && (
<p>
VSS is <b>{info.ldkVssEnabled ? "enabled" : "disabled"}</b>. Migration
to and from VSS will be available shortly.
</p>
<>
{info.ldkVssEnabled && (
<p>
✅ VSS <b>enabled</b>.{" "}
{info.ldkVssEnabled && (
<>Migration from VSS will be available shortly.</>
)}
</p>
)}
{!me.subscription.buzz && (
<p>VSS is only available to Alby users with a paid subscription.</p>
)}
{!info.ldkVssEnabled && me.subscription.buzz && (
<AlertDialog>
<AlertDialogTrigger asChild>
{
<LoadingButton loading={isMigratingStorage}>
Enable VSS
</LoadingButton>
}
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Alby Hub Restart Required</AlertDialogTitle>
<AlertDialogDescription>
<div>
<p>
As part of enabling VSS your hub will be shut down, and
you will need to enter your unlock password to start it
again.
</p>
<p className="mt-2">
Please ensure you have no pending payments or channel
closures before continuing.
</p>
</div>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction onClick={() => migrateLDKStorage("VSS")}>
Confirm
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)}
</>
)}
</>
);
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,9 @@ export type AlbyMe = {
hub: {
name?: string;
};
subscription: {
buzz: boolean;
};
};

export type AlbyBalance = {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/breez/breez-sdk-go v0.5.2
github.com/elnosh/gonuts v0.2.0
github.com/getAlby/glalby-go v0.0.0-20240621192717-95673c864d59
github.com/getAlby/ldk-node-go v0.0.0-20241126182233-197f9bcdd475
github.com/getAlby/ldk-node-go v0.0.0-20241211081207-8911834564db
github.com/go-gormigrate/gormigrate/v2 v2.1.3
github.com/labstack/echo/v4 v4.12.0
github.com/nbd-wtf/go-nostr v0.42.3
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,8 @@ github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwV
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/getAlby/glalby-go v0.0.0-20240621192717-95673c864d59 h1:fSqdXE9uKhLcOOQaLtzN+D8RN3oEcZQkGX5E8PyiKy0=
github.com/getAlby/glalby-go v0.0.0-20240621192717-95673c864d59/go.mod h1:ViyJvjlvv0GCesTJ7mb3fBo4G+/qsujDAFN90xZ7a9U=
github.com/getAlby/ldk-node-go v0.0.0-20241126182233-197f9bcdd475 h1:rsOFBWZE94kgbv7lWI2kuhco6qY5p3kJnFrNh3nFZuQ=
github.com/getAlby/ldk-node-go v0.0.0-20241126182233-197f9bcdd475/go.mod h1:8BRjtKcz8E0RyYTPEbMS8VIdgredcGSLne8vHDtcRLg=
github.com/getAlby/ldk-node-go v0.0.0-20241211081207-8911834564db h1:jHUCoYD74IwOsMOc/99ACajlBNwfTy72AX+e5PoPwwo=
github.com/getAlby/ldk-node-go v0.0.0-20241211081207-8911834564db/go.mod h1:8BRjtKcz8E0RyYTPEbMS8VIdgredcGSLne8vHDtcRLg=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
Expand Down
21 changes: 21 additions & 0 deletions http/http_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ func (httpSvc *HttpService) RegisterSharedRoutes(e *echo.Echo) {
restrictedGroup.GET("/api/node/connection-info", httpSvc.nodeConnectionInfoHandler)
restrictedGroup.GET("/api/node/status", httpSvc.nodeStatusHandler)
restrictedGroup.GET("/api/node/network-graph", httpSvc.nodeNetworkGraphHandler)
restrictedGroup.POST("/api/node/migrate-storage", httpSvc.migrateNodeStorageHandler)
restrictedGroup.GET("/api/peers", httpSvc.listPeers)
restrictedGroup.POST("/api/peers", httpSvc.connectPeerHandler)
restrictedGroup.DELETE("/api/peers/:peerId", httpSvc.disconnectPeerHandler)
Expand Down Expand Up @@ -421,6 +422,26 @@ func (httpSvc *HttpService) nodeNetworkGraphHandler(c echo.Context) error {
return c.JSON(http.StatusOK, info)
}

func (httpSvc *HttpService) migrateNodeStorageHandler(c echo.Context) error {
ctx := c.Request().Context()
var migrateNodeStorageRequest api.MigrateNodeStorageRequest
if err := c.Bind(&migrateNodeStorageRequest); err != nil {
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}

err := httpSvc.api.MigrateNodeStorage(ctx, migrateNodeStorageRequest.To)

if err != nil {
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: err.Error(),
})
}

return c.NoContent(http.StatusNoContent)
}

func (httpSvc *HttpService) balancesHandler(c echo.Context) error {
ctx := c.Request().Context()

Expand Down
15 changes: 14 additions & 1 deletion lnclient/ldk/ldk.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,21 @@ func NewLDKService(ctx context.Context, cfg config.Config, eventPublisher events
// The liquidity source below is not used because we do not use the native LDK-node LSPS2 API.
builder.SetLiquiditySourceLsps2("52.88.33.119:9735", lsp.OlympusLSP().Pubkey, nil)

migrateStorage, _ := cfg.Get("LdkMigrateStorage", "")
if migrateStorage == "VSS" {
err = cfg.SetUpdate("LdkMigrateStorage", "", "")
if err != nil {
return nil, err
}
if vssToken == "" {
return nil, errors.New("migration enabled but no vss token found")
}
builder.MigrateStorage(ldk_node.MigrateStorageVss)
}

logger.Logger.WithFields(logrus.Fields{
"vss_enabled": vssToken != "",
"migrate_storage": migrateStorage,
"vss_enabled": vssToken != "",
}).Info("Creating node")
var node *ldk_node.Node
if vssToken != "" {
Expand Down
16 changes: 16 additions & 0 deletions wails/wails_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,22 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
infoResponse.Unlocked = infoResponse.Running
res := WailsRequestRouterResponse{Body: *infoResponse, Error: ""}
return res
case "/api/node/migrate-storage":
migrateNodeStorageRequest := &api.MigrateNodeStorageRequest{}
err := json.Unmarshal([]byte(body), migrateNodeStorageRequest)
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
}).WithError(err).Error("Failed to decode request to wails router")
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}
err = app.api.MigrateNodeStorage(ctx, migrateNodeStorageRequest.To)
if err != nil {
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}
return WailsRequestRouterResponse{Body: nil, Error: ""}
case "/api/alby/auto-channel":
newAutoChannelRequest := &alby.AutoChannelRequest{}
err := json.Unmarshal([]byte(body), newAutoChannelRequest)
Expand Down

0 comments on commit d51db84

Please sign in to comment.