Skip to content

Commit

Permalink
feat: replace start timeout with hub startup state (#1090)
Browse files Browse the repository at this point in the history
* feat: replace start timeout with hub startup state

* fix: update mocks

* fix: make start page copy consistent
  • Loading branch information
rolznz authored Feb 14, 2025
1 parent 0f3f03c commit cc24257
Show file tree
Hide file tree
Showing 11 changed files with 96 additions and 51 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ _If you get a blank screen, try running in your normal terminal (outside of vsco

We use [testify/mock](https://github.com/stretchr/testify) to facilitate mocking in tests. Instead of writing mocks manually, we generate them using [vektra/mockery](https://github.com/vektra/mockery). To regenerate them, [install mockery](https://vektra.github.io/mockery/latest/installation) and run it in the project's root directory:

> Use `go install github.com/vektra/mockery/v2@v2.52.1` as go 1.24.0 is currently not supported by Alby Hub.
$ mockery

Mockery loads its configuration from the .mockery.yaml file in the root directory of this project. To add mocks for new interfaces, add them to the configuration file and run mockery.
Expand Down
1 change: 1 addition & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,7 @@ func (api *api) GetInfo(ctx context.Context) (*InfoResponse, error) {
autoUnlockPassword, _ := api.cfg.Get("AutoUnlockPassword", "")
info.SetupCompleted = api.cfg.SetupCompleted()
info.Currency = api.cfg.GetCurrency()
info.StartupState = api.svc.GetStartupState()
if api.startupError != nil {
info.StartupError = api.startupError.Error()
info.StartupErrorTime = api.startupErrorTime
Expand Down
1 change: 1 addition & 0 deletions api/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ type InfoResponse struct {
EnableAdvancedSetup bool `json:"enableAdvancedSetup"`
LdkVssEnabled bool `json:"ldkVssEnabled"`
VssSupported bool `json:"vssSupported"`
StartupState string `json:"startupState"`
StartupError string `json:"startupError"`
StartupErrorTime time.Time `json:"startupErrorTime"`
AutoUnlockPasswordSupported bool `json:"autoUnlockPasswordSupported"`
Expand Down
64 changes: 17 additions & 47 deletions frontend/src/screens/Start.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,25 @@ import { AuthTokenResponse } from "src/types";
import { handleRequestError } from "src/utils/handleRequestError";
import { request } from "src/utils/request";

const messages: string[] = [
"Unlocking",
"Starting the wallet",
"Connecting to the network",
"Syncing",
"Still syncing, please wait...",
];

export default function Start() {
const [unlockPassword, setUnlockPassword] = React.useState("");
const [loading, setLoading] = React.useState(false);
const [buttonText, setButtonText] = React.useState("Login");
const [buttonText, setButtonText] = React.useState("Start");

const { data: info } = useInfo(true); // poll the info endpoint to auto-redirect when app is running

const { toast } = useToast();

const startupState = info?.startupState;
const startupError = info?.startupError;
const startupErrorTime = info?.startupErrorTime;

React.useEffect(() => {
if (startupState) {
setButtonText(startupState);
}
}, [startupState]);

React.useEffect(() => {
if (startupError && startupErrorTime) {
toast({
Expand All @@ -40,50 +39,16 @@ export default function Start() {
variant: "destructive",
});
setLoading(false);
setButtonText("Login");
setButtonText("Start");
setUnlockPassword("");
}
}, [startupError, toast, startupErrorTime]);

React.useEffect(() => {
if (!loading) {
return;
}
let messageIndex = 1;
const intervalId = setInterval(() => {
if (messageIndex < messages.length) {
setButtonText(messages[messageIndex]);
messageIndex++;
} else {
clearInterval(intervalId);
}
}, 5000);

const timeoutId = setTimeout(() => {
// if redirection didn't happen in 3 minutes info.running is false
toast({
title: "Failed to start",
description: "Please try starting the node again.",
variant: "destructive",
});

setLoading(false);
setButtonText("Login");
setUnlockPassword("");
return;
}, 60000); // wait for 60 seconds

return () => {
clearInterval(intervalId);
clearTimeout(timeoutId);
};
}, [loading, toast]);

async function onSubmit(e: React.FormEvent) {
e.preventDefault();
try {
setLoading(true);
setButtonText(messages[0]);
setButtonText("Please wait...");

const authTokenResponse = await request<AuthTokenResponse>("/api/start", {
method: "POST",
Expand All @@ -100,7 +65,7 @@ export default function Start() {
} catch (error) {
handleRequestError(toast, "Failed to connect", error);
setLoading(false);
setButtonText("Login");
setButtonText("Start");
setUnlockPassword("");
}
}
Expand All @@ -110,7 +75,7 @@ export default function Start() {
<Container>
<div className="mx-auto grid gap-5">
<TwoColumnLayoutHeader
title="Login"
title="Start"
description="Enter your password to unlock and start Alby Hub."
/>
<form onSubmit={onSubmit}>
Expand All @@ -129,6 +94,11 @@ export default function Start() {
<LoadingButton type="submit" loading={loading}>
{buttonText}
</LoadingButton>
{loading && (
<p className="text-muted-foreground text-xs text-center">
Starting Alby Hub may take a few minutes.
</p>
)}
</div>
</form>
</div>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ export interface InfoResponse {
version: string;
unlocked: boolean;
enableAdvancedSetup: boolean;
startupState: string;
startupError: string;
startupErrorTime: string;
autoUnlockPasswordSupported: boolean;
Expand Down
8 changes: 7 additions & 1 deletion lnclient/ldk/ldk.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ type LDKService struct {
const resetRouterKey = "ResetRouter"
const maxInvoiceExpiry = 24 * time.Hour

func NewLDKService(ctx context.Context, cfg config.Config, eventPublisher events.EventPublisher, mnemonic, workDir string, network string, vssToken string) (result lnclient.LNClient, err error) {
func NewLDKService(ctx context.Context, cfg config.Config, eventPublisher events.EventPublisher, mnemonic, workDir string, network string, vssToken string, setStartupState func(startupState string)) (result lnclient.LNClient, err error) {
if mnemonic == "" || workDir == "" {
return nil, errors.New("one or more required LDK configuration are missing")
}

setStartupState("Configuring node")

// create dir if not exists
newpath := filepath.Join(workDir)
err = os.MkdirAll(newpath, os.ModePerm)
Expand Down Expand Up @@ -153,6 +155,7 @@ func NewLDKService(ctx context.Context, cfg config.Config, eventPublisher events
"vss_enabled": vssToken != "",
"listening_addresses": listeningAddresses,
}).Info("Creating LDK node")
setStartupState("Loading node data...")
var node *ldk_node.Node
if vssToken != "" {
node, err = builder.BuildWithVssStoreAndFixedHeaders(cfg.GetEnv().LDKVssUrl, "albyhub", map[string]string{
Expand Down Expand Up @@ -228,6 +231,8 @@ func NewLDKService(ctx context.Context, cfg config.Config, eventPublisher events
"nodeId": nodeId,
}).Info("Starting LDK node...")

setStartupState("Starting node...")

err = node.Start()
if err != nil {
logger.Logger.WithError(err).Error("Failed to start LDK node")
Expand All @@ -239,6 +244,7 @@ func NewLDKService(ctx context.Context, cfg config.Config, eventPublisher events
"status": node.Status(),
}).Info("Started LDK node. Syncing wallet...")

setStartupState("Syncing node...")
syncStartTime := time.Now()
err = node.SyncWallets()
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions service/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ type Service interface {
GetConfig() config.Config
GetKeys() keys.Keys
IsRelayReady() bool
GetStartupState() string
}
5 changes: 5 additions & 0 deletions service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ type service struct {
appCancelFn context.CancelFunc
keys keys.Keys
isRelayReady atomic.Bool
startupState string
}

func NewService(ctx context.Context) (*service, error) {
Expand Down Expand Up @@ -258,3 +259,7 @@ func (svc *service) setRelayReady(ready bool) {
func (svc *service) IsRelayReady() bool {
return svc.isRelayReady.Load()
}

func (svc *service) GetStartupState() string {
return svc.startupState
}
15 changes: 14 additions & 1 deletion service/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,11 @@ func (svc *service) StartSubscription(ctx context.Context, sub *nostr.Subscripti
}

func (svc *service) StartApp(encryptionKey string) error {
defer func() {
svc.startupState = ""
}()

svc.startupState = "Initializing"
albyIdentifier, err := svc.albyOAuthSvc.GetUserIdentifier()
if err != nil {
return err
Expand All @@ -247,6 +252,7 @@ func (svc *service) StartApp(encryptionKey string) error {
return err
}

svc.startupState = "Launching Node"
err = svc.launchLNBackend(ctx, encryptionKey)
if err != nil {
logger.Logger.Errorf("Failed to launch LN backend: %v", err)
Expand All @@ -257,6 +263,7 @@ func (svc *service) StartApp(encryptionKey string) error {
return err
}

svc.startupState = "Connecting To Relay"
err = svc.startNostr(ctx)
if err != nil {
cancelFn()
Expand Down Expand Up @@ -307,7 +314,11 @@ func (svc *service) launchLNBackend(ctx context.Context, encryptionKey string) e
}
vssEnabled = vssToken != ""

lnClient, err = ldk.NewLDKService(ctx, svc.cfg, svc.eventPublisher, mnemonic, ldkWorkdir, svc.cfg.GetEnv().LDKNetwork, vssToken)
svc.startupState = "Launching Node"
setStartupState := func(startupState string) {
svc.startupState = startupState
}
lnClient, err = ldk.NewLDKService(ctx, svc.cfg, svc.eventPublisher, mnemonic, ldkWorkdir, svc.cfg.GetEnv().LDKNetwork, vssToken, setStartupState)
case config.GreenlightBackendType:
Mnemonic, _ := svc.cfg.Get("Mnemonic", encryptionKey)
GreenlightInviteCode, _ := svc.cfg.Get("GreenlightInviteCode", encryptionKey)
Expand Down Expand Up @@ -395,6 +406,7 @@ func (svc *service) requestVssToken(ctx context.Context) (string, error) {

// for brand new nodes, consider enabling VSS
if nodeLastStartTime == "" && svc.cfg.GetEnv().LDKVssUrl != "" {
svc.startupState = "Checking Subscription"
albyUserIdentifier, err := svc.albyOAuthSvc.GetUserIdentifier()
if err != nil {
logger.Logger.WithError(err).Error("Failed to fetch alby user identifier")
Expand All @@ -416,6 +428,7 @@ func (svc *service) requestVssToken(ctx context.Context) (string, error) {
vssToken := ""
vssEnabled, _ := svc.cfg.Get("LdkVssEnabled", "")
if vssEnabled == "true" {
svc.startupState = "Fetching VSS token"
vssNodeIdentifier, err := ldk.GetVssNodeIdentifier(svc.keys)
if err != nil {
logger.Logger.WithError(err).Error("Failed to get VSS node identifier")
Expand Down
2 changes: 1 addition & 1 deletion tests/mocks/LNClient.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 46 additions & 1 deletion tests/mocks/Service.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit cc24257

Please sign in to comment.