Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cmd/loop/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ var (
monitorCommand, quoteCommand, listAuthCommand, fetchL402Command,
listSwapsCommand, swapInfoCommand, getLiquidityParamsCommand,
setLiquidityRuleCommand, suggestSwapCommand, setParamsCommand,
getInfoCommand, abandonSwapCommand, reservationsCommands,
getInfoCommand, abandonSwapCommand, recoverCommand,
reservationsCommands,
instantOutCommand, listInstantOutsCommand, stopCommand,
printManCommand, printMarkdownCommand,
}
Expand Down
49 changes: 49 additions & 0 deletions cmd/loop/recover.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package main

import (
"context"

"github.com/lightninglabs/loop/looprpc"
"github.com/urfave/cli/v3"
)

var recoverCommand = &cli.Command{
Name: "recover",
Usage: "restore static address and L402 state from a local backup file",
Description: "Restores the local static-address state and L402 token " +
"from an encrypted backup file. If --backup_file is omitted, " +
"loopd will use the latest timestamped network-specific backup " +
"file.",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "backup_file",
Usage: "path to an encrypted backup file; if omitted, " +
"loopd uses the latest timestamped backup file path",
},
},
Action: runRecover,
}

func runRecover(ctx context.Context, cmd *cli.Command) error {
if cmd.NArg() > 0 {
return showCommandHelp(ctx, cmd)
}

client, cleanup, err := getClient(cmd)
if err != nil {
return err
}
defer cleanup()

resp, err := client.Recover(
ctx, &looprpc.RecoverRequest{
BackupFile: cmd.String("backup_file"),
},
)
if err != nil {
return err
}

printRespJSON(resp)
return nil
}
10 changes: 10 additions & 0 deletions docs/loop.1
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,16 @@ abandon a swap with a given swap hash
.PP
\fB--i_know_what_i_am_doing\fP: Specify this flag if you made sure that you read and understood the following consequence of applying this command.

.SH recover
.PP
restore static address and L402 state from a local backup file

.PP
\fB--backup_file\fP="": path to an encrypted backup file; if omitted, loopd uses the latest timestamped backup file path

.PP
\fB--help, -h\fP: show help

.SH reservations, r
.PP
manage reservations
Expand Down
19 changes: 19 additions & 0 deletions docs/loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,25 @@ The following flags are supported:
| `--i_know_what_i_am_doing` | Specify this flag if you made sure that you read and understood the following consequence of applying this command | bool | `false` |
| `--help` (`-h`) | show help | bool | `false` |

### `recover` command

restore static address and L402 state from a local backup file.

Restores the local static-address state and L402 token from an encrypted backup file. If --backup_file is omitted, loopd will use the latest timestamped network-specific backup file.

Usage:

```bash
$ loop [GLOBAL FLAGS] recover [COMMAND FLAGS] [ARGUMENTS...]
```

The following flags are supported:

| Name | Description | Type | Default value |
|---------------------|--------------------------------------------------------------------------------------------------|--------|:-------------:|
| `--backup_file="…"` | path to an encrypted backup file; if omitted, loopd uses the latest timestamped backup file path | string |
| `--help` (`-h`) | show help | bool | `false` |

### `reservations` command (aliases: `r`)

manage reservations.
Expand Down
43 changes: 42 additions & 1 deletion loopd/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/lightninglabs/loop/loopdb"
loop_looprpc "github.com/lightninglabs/loop/looprpc"
"github.com/lightninglabs/loop/notifications"
"github.com/lightninglabs/loop/recovery"
"github.com/lightninglabs/loop/staticaddr/address"
"github.com/lightninglabs/loop/staticaddr/deposit"
"github.com/lightninglabs/loop/staticaddr/loopin"
Expand Down Expand Up @@ -577,17 +578,47 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
withdrawalManager *withdraw.Manager
openChannelManager *openchannel.Manager
staticLoopInManager *loopin.Manager
recoveryService *recovery.Service
)

writeRecoveryBackup := func(ctx context.Context, source string) error {
if recoveryService == nil {
return nil
}

backupFile, err := recoveryService.WriteBackup(ctx)
if err != nil {
return err
}
if backupFile != "" {
infof("Wrote encrypted recovery backup to %s after %s",
backupFile, source)
}

return nil
}

// Static address manager setup.
staticAddressStore := address.NewSqlStore(baseDb)
addrCfg := &address.ManagerConfig{
AddressClient: staticAddressClient,
FetchL402: swapClient.Server.FetchL402,
FetchL402: func(ctx context.Context) error {
err := swapClient.Server.FetchL402(ctx)
if err != nil {
return err
}

return writeRecoveryBackup(ctx, "retrieving a paid L402")
},
Store: staticAddressStore,
WalletKit: d.lnd.WalletKit,
ChainParams: d.lnd.ChainParams,
ChainNotifier: d.lnd.ChainNotifier,
OnStaticAddressCreated: func(ctx context.Context) error {
return writeRecoveryBackup(
ctx, "creating a static address",
)
},
}
staticAddressManager, err = address.NewManager(
addrCfg, int32(blockHeight),
Expand Down Expand Up @@ -689,6 +720,15 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
return fmt.Errorf("unable to create loop-in manager: %w", err)
}

recoveryService = recovery.NewService(
d.cfg.DataDir, d.cfg.Network, d.lnd.Signer, d.lnd.WalletKit,
staticAddressManager, depositManager,
)
err = writeRecoveryBackup(d.mainCtx, "startup")
if err != nil {
return fmt.Errorf("unable to write backup file: %w", err)
}

var (
reservationManager *reservation.Manager
instantOutManager *instantout.Manager
Expand Down Expand Up @@ -753,6 +793,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
staticLoopInManager: staticLoopInManager,
openChannelManager: openChannelManager,
assetClient: d.assetClient,
recoveryService: recoveryService,
stopDaemon: d.Stop,
}

Expand Down
52 changes: 52 additions & 0 deletions loopd/swapclient_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/lightninglabs/loop/liquidity"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/looprpc"
"github.com/lightninglabs/loop/recovery"
"github.com/lightninglabs/loop/staticaddr/address"
"github.com/lightninglabs/loop/staticaddr/deposit"
"github.com/lightninglabs/loop/staticaddr/loopin"
Expand Down Expand Up @@ -101,6 +102,7 @@ type swapClientServer struct {
staticLoopInManager *loopin.Manager
openChannelManager *openchannel.Manager
assetClient *assets.TapdClient
recoveryService *recovery.Service
swaps map[lntypes.Hash]loop.SwapInfo
subscribers map[int]chan<- any
statusChan chan loop.SwapInfo
Expand Down Expand Up @@ -1265,9 +1267,59 @@ func (s *swapClientServer) FetchL402Token(ctx context.Context,
return nil, err
}

err = s.writeRecoveryBackup(ctx, "retrieving a paid L402")
if err != nil {
return nil, err
}

return &looprpc.FetchL402TokenResponse{}, nil
}

// Recover restores the local paid L402 token material and static-address state
// from an encrypted backup file.
func (s *swapClientServer) Recover(ctx context.Context,
req *looprpc.RecoverRequest) (*looprpc.RecoverResponse, error) {

if s.recoveryService == nil {
return nil, status.Error(
codes.Unavailable, "recovery service not configured",
)
}

result, err := s.recoveryService.Restore(ctx, req.GetBackupFile())
if err != nil {
return nil, err
}

return &looprpc.RecoverResponse{
BackupFile: result.BackupFile,
RestoredL402: result.RestoredL402,
RestoredStaticAddress: result.RestoredStaticAddress,
StaticAddress: result.StaticAddress,
NumDepositsFound: uint32(result.NumDepositsFound),
DepositReconciliationError: result.DepositReconciliationError,
}, nil
}

func (s *swapClientServer) writeRecoveryBackup(ctx context.Context,
source string) error {

if s.recoveryService == nil {
return nil
}

backupFile, err := s.recoveryService.WriteBackup(ctx)
if err != nil {
return err
}
if backupFile != "" {
infof("Wrote encrypted recovery backup to %s after %s",
backupFile, source)
}

return nil
}

// GetInfo returns basic information about the loop daemon and details to swaps
// from the swap store.
func (s *swapClientServer) GetInfo(ctx context.Context,
Expand Down
Loading
Loading