Skip to content

Commit c5d9d1d

Browse files
committed
loopd: wire recovery service into daemon startup
Register a recovery gRPC endpoint, attach the new recovery package to the swap client server, and have daemon startup write an encrypted backup file whenever recoverable static-address or l402 state already exists locally.
1 parent d4ef548 commit c5d9d1d

3 files changed

Lines changed: 115 additions & 11 deletions

File tree

cmd/loop/staticaddr.go

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import (
44
"context"
55
"errors"
66
"fmt"
7+
"strings"
78

89
"github.com/lightninglabs/loop/labels"
910
"github.com/lightninglabs/loop/looprpc"
11+
"github.com/lightninglabs/loop/staticaddr/address"
1012
"github.com/lightninglabs/loop/staticaddr/deposit"
1113
"github.com/lightninglabs/loop/staticaddr/loopin"
1214
"github.com/lightninglabs/loop/swapserverrpc"
@@ -40,13 +42,15 @@ var staticAddressCommands = &cli.Command{
4042
var newStaticAddressCommand = &cli.Command{
4143
Name: "new",
4244
Aliases: []string{"n"},
43-
Usage: "Create a new static loop in address.",
45+
Usage: "Return the static loop in address.",
4446
Description: `
45-
Requests a new static loop in address from the server. Funds that are
46-
sent to this address will be locked by a 2:2 multisig between us and the
47-
loop server, or a timeout path that we can sweep once it opens up. The
48-
funds can either be cooperatively spent with a signature from the server
49-
or looped in.
47+
Returns the current static loop in address. On a fresh installation loopd
48+
initializes the current static-address generation during startup. If the
49+
address is still missing, this call will create it on demand. Funds sent
50+
to the address will be locked by a 2:2 multisig between us and the loop
51+
server, or a timeout path that we can sweep once it opens up. The funds
52+
can either be cooperatively spent with a signature from the server or
53+
looped in.
5054
`,
5155
Action: newStaticAddress,
5256
}
@@ -56,16 +60,16 @@ func newStaticAddress(ctx context.Context, cmd *cli.Command) error {
5660
return showCommandHelp(ctx, cmd)
5761
}
5862

59-
err := displayNewAddressWarning()
63+
client, cleanup, err := getClient(cmd)
6064
if err != nil {
6165
return err
6266
}
67+
defer cleanup()
6368

64-
client, cleanup, err := getClient(cmd)
69+
err = maybeDisplayNewAddressWarning(ctx, client)
6570
if err != nil {
6671
return err
6772
}
68-
defer cleanup()
6973

7074
resp, err := client.NewStaticAddress(
7175
ctx, &looprpc.NewStaticAddressRequest{},
@@ -669,8 +673,26 @@ func depositsToOutpoints(deposits []*looprpc.Deposit) []string {
669673
return outpoints
670674
}
671675

676+
func maybeDisplayNewAddressWarning(ctx context.Context,
677+
client looprpc.SwapClientClient) error {
678+
679+
_, err := client.GetStaticAddressSummary(
680+
ctx, &looprpc.StaticAddressSummaryRequest{},
681+
)
682+
switch {
683+
case err == nil:
684+
return nil
685+
686+
case strings.Contains(err.Error(), address.ErrNoStaticAddress.Error()):
687+
return displayNewAddressWarning()
688+
689+
default:
690+
return nil
691+
}
692+
}
693+
672694
func displayNewAddressWarning() error {
673-
fmt.Printf("\nWARNING: Be aware that loosing your l402.token file in " +
695+
fmt.Printf("\nWARNING: Be aware that losing your l402.token file in " +
674696
".loop under your home directory will take your ability to " +
675697
"spend funds sent to the static address via loop-ins or " +
676698
"withdrawals. You will have to wait until the deposit " +

loopd/daemon.go

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/lightninglabs/loop/loopdb"
2323
loop_looprpc "github.com/lightninglabs/loop/looprpc"
2424
"github.com/lightninglabs/loop/notifications"
25+
"github.com/lightninglabs/loop/recovery"
2526
"github.com/lightninglabs/loop/staticaddr/address"
2627
"github.com/lightninglabs/loop/staticaddr/deposit"
2728
"github.com/lightninglabs/loop/staticaddr/loopin"
@@ -577,13 +578,16 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
577578
withdrawalManager *withdraw.Manager
578579
openChannelManager *openchannel.Manager
579580
staticLoopInManager *loopin.Manager
581+
recoveryService *recovery.Service
580582
)
581583

582584
// Static address manager setup.
583585
staticAddressStore := address.NewSqlStore(baseDb)
584586
addrCfg := &address.ManagerConfig{
585587
AddressClient: staticAddressClient,
586-
FetchL402: swapClient.Server.FetchL402,
588+
FetchL402: func(ctx context.Context) error {
589+
return swapClient.Server.FetchL402(ctx)
590+
},
587591
Store: staticAddressStore,
588592
WalletKit: d.lnd.WalletKit,
589593
ChainParams: d.lnd.ChainParams,
@@ -689,6 +693,44 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
689693
return fmt.Errorf("unable to create loop-in manager: %w", err)
690694
}
691695

696+
// Keep startup restore/write-backup free of deposit reconciliation so we
697+
// don't create deposit FSMs before the deposit manager is running.
698+
startupRecoveryService := recovery.NewService(
699+
d.cfg.DataDir, d.cfg.Network, d.lnd.Signer, d.lnd.WalletKit,
700+
staticAddressManager, nil,
701+
)
702+
703+
restoreResult, restoredFromBackup, err :=
704+
startupRecoveryService.RestoreLatestOnFreshInstall(d.mainCtx)
705+
if err != nil {
706+
return fmt.Errorf("unable to restore latest recovery "+
707+
"backup on fresh install: %w", err)
708+
}
709+
if restoredFromBackup {
710+
infof("Restored fresh install from encrypted recovery "+
711+
"backup %s", restoreResult.BackupFile)
712+
} else {
713+
_, _, err = staticAddressManager.NewAddress(d.mainCtx)
714+
if err != nil {
715+
warnf("Unable to initialize static address generation "+
716+
"during startup: %v", err)
717+
}
718+
}
719+
720+
backupFile, err := startupRecoveryService.WriteBackup(d.mainCtx)
721+
if err != nil {
722+
warnf("Unable to write startup recovery backup: %v", err)
723+
}
724+
if backupFile != "" {
725+
infof("Wrote encrypted recovery backup to %s after "+
726+
"initializing the current L402 generation", backupFile)
727+
}
728+
729+
recoveryService = recovery.NewService(
730+
d.cfg.DataDir, d.cfg.Network, d.lnd.Signer, d.lnd.WalletKit,
731+
staticAddressManager, depositManager,
732+
)
733+
692734
var (
693735
reservationManager *reservation.Manager
694736
instantOutManager *instantout.Manager
@@ -753,6 +795,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
753795
staticLoopInManager: staticLoopInManager,
754796
openChannelManager: openChannelManager,
755797
assetClient: d.assetClient,
798+
recoveryService: recoveryService,
756799
stopDaemon: d.Stop,
757800
}
758801

loopd/swapclient_server.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/lightninglabs/loop/liquidity"
3030
"github.com/lightninglabs/loop/loopdb"
3131
"github.com/lightninglabs/loop/looprpc"
32+
"github.com/lightninglabs/loop/recovery"
3233
"github.com/lightninglabs/loop/staticaddr/address"
3334
"github.com/lightninglabs/loop/staticaddr/deposit"
3435
"github.com/lightninglabs/loop/staticaddr/loopin"
@@ -101,6 +102,7 @@ type swapClientServer struct {
101102
staticLoopInManager *loopin.Manager
102103
openChannelManager *openchannel.Manager
103104
assetClient *assets.TapdClient
105+
recoveryService *recovery.Service
104106
swaps map[lntypes.Hash]loop.SwapInfo
105107
subscribers map[int]chan<- any
106108
statusChan chan loop.SwapInfo
@@ -1268,6 +1270,32 @@ func (s *swapClientServer) FetchL402Token(ctx context.Context,
12681270
return &looprpc.FetchL402TokenResponse{}, nil
12691271
}
12701272

1273+
// Recover restores the local paid L402 token material and static-address state
1274+
// from an encrypted backup file.
1275+
func (s *swapClientServer) Recover(ctx context.Context,
1276+
req *looprpc.RecoverRequest) (*looprpc.RecoverResponse, error) {
1277+
1278+
if s.recoveryService == nil {
1279+
return nil, status.Error(
1280+
codes.Unavailable, "recovery service not configured",
1281+
)
1282+
}
1283+
1284+
result, err := s.recoveryService.Restore(ctx, req.GetBackupFile())
1285+
if err != nil {
1286+
return nil, err
1287+
}
1288+
1289+
return &looprpc.RecoverResponse{
1290+
BackupFile: result.BackupFile,
1291+
RestoredL402: result.RestoredL402,
1292+
RestoredStaticAddress: result.RestoredStaticAddress,
1293+
StaticAddress: result.StaticAddress,
1294+
NumDepositsFound: uint32(result.NumDepositsFound),
1295+
DepositReconciliationError: result.DepositReconciliationError,
1296+
}, nil
1297+
}
1298+
12711299
// GetInfo returns basic information about the loop daemon and details to swaps
12721300
// from the swap store.
12731301
func (s *swapClientServer) GetInfo(ctx context.Context,
@@ -1637,6 +1665,17 @@ func (s *swapClientServer) NewStaticAddress(ctx context.Context,
16371665
return nil, err
16381666
}
16391667

1668+
if s.recoveryService != nil {
1669+
backupFile, backupErr := s.recoveryService.WriteBackup(ctx)
1670+
if backupErr != nil {
1671+
warnf("Unable to write recovery backup after static "+
1672+
"address request: %v", backupErr)
1673+
} else if backupFile != "" {
1674+
infof("Wrote encrypted recovery backup to %s after "+
1675+
"static address request", backupFile)
1676+
}
1677+
}
1678+
16401679
return &looprpc.NewStaticAddressResponse{
16411680
Address: staticAddress.String(),
16421681
Expiry: uint32(expiry),

0 commit comments

Comments
 (0)