Skip to content

Commit 95beb88

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 da7f194 commit 95beb88

3 files changed

Lines changed: 146 additions & 1 deletion

File tree

loopd/daemon.go

Lines changed: 43 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"
@@ -255,6 +256,7 @@ func (d *Daemon) startWebServers() error {
255256
grpc.StreamInterceptor(streamInterceptor),
256257
)
257258
loop_looprpc.RegisterSwapClientServer(d.grpcServer, d)
259+
d.grpcServer.RegisterService(&recoveryServiceDesc, d)
258260

259261
// Register our debug server if it is compiled in.
260262
d.registerDebugServer()
@@ -577,17 +579,47 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
577579
withdrawalManager *withdraw.Manager
578580
openChannelManager *openchannel.Manager
579581
staticLoopInManager *loopin.Manager
582+
recoveryService *recovery.Service
580583
)
581584

585+
writeRecoveryBackup := func(ctx context.Context, source string) error {
586+
if recoveryService == nil {
587+
return nil
588+
}
589+
590+
backupFile, err := recoveryService.WriteBackup(ctx)
591+
if err != nil {
592+
return err
593+
}
594+
if backupFile != "" {
595+
infof("Wrote encrypted recovery backup to %s after %s",
596+
backupFile, source)
597+
}
598+
599+
return nil
600+
}
601+
582602
// Static address manager setup.
583603
staticAddressStore := address.NewSqlStore(baseDb)
584604
addrCfg := &address.ManagerConfig{
585605
AddressClient: staticAddressClient,
586-
FetchL402: swapClient.Server.FetchL402,
606+
FetchL402: func(ctx context.Context) error {
607+
err := swapClient.Server.FetchL402(ctx)
608+
if err != nil {
609+
return err
610+
}
611+
612+
return writeRecoveryBackup(ctx, "retrieving a paid L402")
613+
},
587614
Store: staticAddressStore,
588615
WalletKit: d.lnd.WalletKit,
589616
ChainParams: d.lnd.ChainParams,
590617
ChainNotifier: d.lnd.ChainNotifier,
618+
OnStaticAddressCreated: func(ctx context.Context) error {
619+
return writeRecoveryBackup(
620+
ctx, "creating a static address",
621+
)
622+
},
591623
}
592624
staticAddressManager, err = address.NewManager(
593625
addrCfg, int32(blockHeight),
@@ -689,6 +721,15 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
689721
return fmt.Errorf("unable to create loop-in manager: %w", err)
690722
}
691723

724+
recoveryService = recovery.NewService(
725+
d.cfg.DataDir, d.cfg.Network, d.lnd.Signer, d.lnd.WalletKit,
726+
staticAddressManager, depositManager,
727+
)
728+
err = writeRecoveryBackup(d.mainCtx, "startup")
729+
if err != nil {
730+
return fmt.Errorf("unable to write backup file: %w", err)
731+
}
732+
692733
var (
693734
reservationManager *reservation.Manager
694735
instantOutManager *instantout.Manager
@@ -753,6 +794,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
753794
staticLoopInManager: staticLoopInManager,
754795
openChannelManager: openChannelManager,
755796
assetClient: d.assetClient,
797+
recoveryService: recoveryService,
756798
stopDaemon: d.Stop,
757799
}
758800

loopd/recovery_service.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package loopd
2+
3+
import (
4+
"context"
5+
6+
"google.golang.org/grpc"
7+
"google.golang.org/protobuf/types/known/structpb"
8+
"google.golang.org/protobuf/types/known/wrapperspb"
9+
)
10+
11+
type recoveryServer interface {
12+
Recover(context.Context, *wrapperspb.StringValue) (*structpb.Struct,
13+
error)
14+
}
15+
16+
var recoveryServiceDesc = grpc.ServiceDesc{
17+
ServiceName: "looprpc.Recovery",
18+
HandlerType: (*recoveryServer)(nil),
19+
Methods: []grpc.MethodDesc{{
20+
MethodName: "Recover",
21+
Handler: recoveryHandler,
22+
}},
23+
Metadata: "loopd/recovery_service.go",
24+
}
25+
26+
func recoveryHandler(srv interface{}, ctx context.Context,
27+
dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (
28+
interface{}, error) {
29+
30+
in := new(wrapperspb.StringValue)
31+
if err := dec(in); err != nil {
32+
return nil, err
33+
}
34+
35+
if interceptor == nil {
36+
return srv.(recoveryServer).Recover(ctx, in)
37+
}
38+
39+
info := &grpc.UnaryServerInfo{
40+
Server: srv,
41+
FullMethod: "/looprpc.Recovery/Recover",
42+
}
43+
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
44+
return srv.(recoveryServer).Recover(
45+
ctx, req.(*wrapperspb.StringValue),
46+
)
47+
}
48+
49+
return interceptor(ctx, in, info, handler)
50+
}
51+
52+
// Recover restores the local L402 token material and static address state from
53+
// an encrypted backup file.
54+
func (s *swapClientServer) Recover(ctx context.Context,
55+
req *wrapperspb.StringValue) (*structpb.Struct, error) {
56+
57+
result, err := s.recoveryService.Restore(ctx, req.GetValue())
58+
if err != nil {
59+
return nil, err
60+
}
61+
62+
values := map[string]any{
63+
"backup_file": result.BackupFile,
64+
"restored_l402": result.RestoredL402,
65+
"restored_static_address": result.RestoredStaticAddress,
66+
"num_deposits_found": result.NumDepositsFound,
67+
}
68+
if result.StaticAddress != "" {
69+
values["static_address"] = result.StaticAddress
70+
}
71+
if result.DepositReconciliationError != "" {
72+
values["deposit_reconciliation_error"] =
73+
result.DepositReconciliationError
74+
}
75+
76+
return structpb.NewStruct(values)
77+
}

loopd/swapclient_server.go

Lines changed: 26 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
@@ -1265,9 +1267,33 @@ func (s *swapClientServer) FetchL402Token(ctx context.Context,
12651267
return nil, err
12661268
}
12671269

1270+
err = s.writeRecoveryBackup(ctx, "retrieving a paid L402")
1271+
if err != nil {
1272+
return nil, err
1273+
}
1274+
12681275
return &looprpc.FetchL402TokenResponse{}, nil
12691276
}
12701277

1278+
func (s *swapClientServer) writeRecoveryBackup(ctx context.Context,
1279+
source string) error {
1280+
1281+
if s.recoveryService == nil {
1282+
return nil
1283+
}
1284+
1285+
backupFile, err := s.recoveryService.WriteBackup(ctx)
1286+
if err != nil {
1287+
return err
1288+
}
1289+
if backupFile != "" {
1290+
infof("Wrote encrypted recovery backup to %s after %s",
1291+
backupFile, source)
1292+
}
1293+
1294+
return nil
1295+
}
1296+
12711297
// GetInfo returns basic information about the loop daemon and details to swaps
12721298
// from the swap store.
12731299
func (s *swapClientServer) GetInfo(ctx context.Context,

0 commit comments

Comments
 (0)