Skip to content

Commit 49c40d9

Browse files
committed
loopd: add reservation handling
1 parent 4d558b1 commit 49c40d9

File tree

5 files changed

+161
-33
lines changed

5 files changed

+161
-33
lines changed

instantout/reservation/manager.go

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package reservation
22

33
import (
44
"context"
5+
"fmt"
56
"sync"
67
"time"
78

@@ -24,8 +25,8 @@ type Manager struct {
2425
sync.Mutex
2526
}
2627

27-
// NewReservationManager creates a new reservation manager.
28-
func NewReservationManager(cfg *Config) *Manager {
28+
// NewManager creates a new reservation manager.
29+
func NewManager(cfg *Config) *Manager {
2930
return &Manager{
3031
cfg: cfg,
3132
activeReservations: make(map[ID]*FSM),
@@ -71,7 +72,7 @@ func (m *Manager) Run(ctx context.Context, height int32) error {
7172
case reservationRes := <-reservationResChan:
7273
log.Debugf("Received reservation %x",
7374
reservationRes.ReservationId)
74-
err := m.newReservation(
75+
_, err := m.newReservation(
7576
runCtx, uint32(currentHeight), reservationRes,
7677
)
7778
if err != nil {
@@ -90,19 +91,19 @@ func (m *Manager) Run(ctx context.Context, height int32) error {
9091

9192
// newReservation creates a new reservation from the reservation request.
9293
func (m *Manager) newReservation(ctx context.Context, currentHeight uint32,
93-
req *reservationrpc.ServerReservationNotification) error {
94+
req *reservationrpc.ServerReservationNotification) (*FSM, error) {
9495

9596
var reservationID ID
9697
err := reservationID.FromByteSlice(
9798
req.ReservationId,
9899
)
99100
if err != nil {
100-
return err
101+
return nil, err
101102
}
102103

103104
serverKey, err := btcec.ParsePubKey(req.ServerKey)
104105
if err != nil {
105-
return err
106+
return nil, err
106107
}
107108

108109
// Create the reservation state machine. We need to pass in the runCtx
@@ -136,14 +137,19 @@ func (m *Manager) newReservation(ctx context.Context, currentHeight uint32,
136137
// We'll now wait for the reservation to be in the state where it is
137138
// waiting to be confirmed.
138139
err = reservationFSM.DefaultObserver.WaitForState(
139-
ctx, time.Minute, WaitForConfirmation,
140+
ctx, 5*time.Second, WaitForConfirmation,
140141
fsm.WithWaitForStateOption(time.Second),
141142
)
142143
if err != nil {
143-
return err
144+
if reservationFSM.LastActionError != nil {
145+
return nil, fmt.Errorf("error waiting for "+
146+
"state: %v, last action error: %v",
147+
err, reservationFSM.LastActionError)
148+
}
149+
return nil, err
144150
}
145151

146-
return nil
152+
return reservationFSM, nil
147153
}
148154

149155
// RegisterReservationNotifications registers a new reservation notification

loopd/daemon.go

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ import (
1818
"github.com/lightninglabs/loop"
1919
"github.com/lightninglabs/loop/loopd/perms"
2020
"github.com/lightninglabs/loop/loopdb"
21-
"github.com/lightninglabs/loop/looprpc"
21+
22+
"github.com/lightninglabs/loop/instantout/reservation"
23+
loop_looprpc "github.com/lightninglabs/loop/looprpc"
24+
25+
loop_swaprpc "github.com/lightninglabs/loop/swapserverrpc"
2226
"github.com/lightningnetwork/lnd/lntypes"
2327
"github.com/lightningnetwork/lnd/macaroons"
2428
"google.golang.org/grpc"
@@ -62,6 +66,10 @@ type Daemon struct {
6266
// same process.
6367
swapClientServer
6468

69+
// reservationManager is the manager that handles all reservation state
70+
// machines.
71+
reservationManager *reservation.Manager
72+
6573
// ErrChan is an error channel that users of the Daemon struct must use
6674
// to detect runtime errors and also whether a shutdown is fully
6775
// completed.
@@ -226,7 +234,7 @@ func (d *Daemon) startWebServers() error {
226234
grpc.UnaryInterceptor(unaryInterceptor),
227235
grpc.StreamInterceptor(streamInterceptor),
228236
)
229-
looprpc.RegisterSwapClientServer(d.grpcServer, d)
237+
loop_looprpc.RegisterSwapClientServer(d.grpcServer, d)
230238

231239
// Register our debug server if it is compiled in.
232240
d.registerDebugServer()
@@ -286,7 +294,7 @@ func (d *Daemon) startWebServers() error {
286294
restProxyDest, "[::]", "[::1]", 1,
287295
)
288296
}
289-
err = looprpc.RegisterSwapClientHandlerFromEndpoint(
297+
err = loop_looprpc.RegisterSwapClientHandlerFromEndpoint(
290298
ctx, mux, restProxyDest, proxyOpts,
291299
)
292300
if err != nil {
@@ -399,7 +407,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
399407
return err
400408
}
401409

402-
swapDb, _, err := openDatabase(d.cfg, chainParams)
410+
swapDb, baseDb, err := openDatabase(d.cfg, chainParams)
403411
if err != nil {
404412
return err
405413
}
@@ -413,6 +421,15 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
413421
}
414422
d.clientCleanup = clientCleanup
415423

424+
// Create a reservation server client.
425+
reservationClient := loop_swaprpc.NewReservationServiceClient(
426+
swapClient.Conn,
427+
)
428+
429+
// Both the client RPC server and the swap server client should stop
430+
// on main context cancel. So we create it early and pass it down.
431+
d.mainCtx, d.mainCtxCancel = context.WithCancel(context.Background())
432+
416433
// Add our debug permissions to our main set of required permissions
417434
// if compiled in.
418435
for endpoint, perm := range debugRequiredPermissions {
@@ -466,17 +483,32 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
466483
}
467484
}
468485

486+
// Create the reservation rpc server.
487+
reservationStore := reservation.NewSQLStore(baseDb)
488+
reservationConfig := &reservation.Config{
489+
Store: reservationStore,
490+
Wallet: d.lnd.WalletKit,
491+
ChainNotifier: d.lnd.ChainNotifier,
492+
ReservationClient: reservationClient,
493+
FetchL402: swapClient.Server.FetchL402,
494+
}
495+
496+
d.reservationManager = reservation.NewManager(
497+
reservationConfig,
498+
)
499+
469500
// Now finally fully initialize the swap client RPC server instance.
470501
d.swapClientServer = swapClientServer{
471-
config: d.cfg,
472-
network: lndclient.Network(d.cfg.Network),
473-
impl: swapClient,
474-
liquidityMgr: getLiquidityManager(swapClient),
475-
lnd: &d.lnd.LndServices,
476-
swaps: make(map[lntypes.Hash]loop.SwapInfo),
477-
subscribers: make(map[int]chan<- interface{}),
478-
statusChan: make(chan loop.SwapInfo),
479-
mainCtx: d.mainCtx,
502+
config: d.cfg,
503+
network: lndclient.Network(d.cfg.Network),
504+
impl: swapClient,
505+
liquidityMgr: getLiquidityManager(swapClient),
506+
lnd: &d.lnd.LndServices,
507+
swaps: make(map[lntypes.Hash]loop.SwapInfo),
508+
subscribers: make(map[int]chan<- interface{}),
509+
statusChan: make(chan loop.SwapInfo),
510+
mainCtx: d.mainCtx,
511+
reservationManager: d.reservationManager,
480512
}
481513

482514
// Retrieve all currently existing swaps from the database.
@@ -543,6 +575,30 @@ func (d *Daemon) initialize(withMacaroonService bool) error {
543575
log.Info("Liquidity manager stopped")
544576
}()
545577

578+
// Start the reservation manager.
579+
d.wg.Add(1)
580+
go func() {
581+
defer d.wg.Done()
582+
583+
// We need to know the current block height to properly
584+
// initialize the reservation manager.
585+
getInfo, err := d.lnd.Client.GetInfo(d.mainCtx)
586+
if err != nil {
587+
d.internalErrChan <- err
588+
return
589+
}
590+
591+
log.Info("Starting reservation manager")
592+
defer log.Info("Reservation manager stopped")
593+
594+
err = d.reservationManager.Run(
595+
d.mainCtx, int32(getInfo.BlockHeight),
596+
)
597+
if err != nil && !errors.Is(err, context.Canceled) {
598+
d.internalErrChan <- err
599+
}
600+
}()
601+
546602
// Last, start our internal error handler. This will return exactly one
547603
// error or nil on the main error channel to inform the caller that
548604
// something went wrong or that shutdown is complete. We don't add to

loopd/log.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/lightninglabs/lndclient"
77
"github.com/lightninglabs/loop"
88
"github.com/lightninglabs/loop/fsm"
9+
"github.com/lightninglabs/loop/instantout/reservation"
910
"github.com/lightninglabs/loop/liquidity"
1011
"github.com/lightninglabs/loop/loopdb"
1112
"github.com/lightningnetwork/lnd"
@@ -38,6 +39,9 @@ func SetupLoggers(root *build.RotatingLogWriter, intercept signal.Interceptor) {
3839
root, liquidity.Subsystem, intercept, liquidity.UseLogger,
3940
)
4041
lnd.AddSubLogger(root, fsm.Subsystem, intercept, fsm.UseLogger)
42+
lnd.AddSubLogger(
43+
root, reservation.Subsystem, intercept, reservation.UseLogger,
44+
)
4145
}
4246

4347
// genSubLogger creates a logger for a subsystem. We provide an instance of

loopd/perms/perms.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,8 @@ var RequiredPermissions = map[string][]bakery.Op{
9696
Entity: "loop",
9797
Action: "in",
9898
}},
99+
"/looprpc.SwapClient/ListReservations": {{
100+
Entity: "reservation",
101+
Action: "read",
102+
}},
99103
}

loopd/swapclient_server.go

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/lightninglabs/aperture/lsat"
1919
"github.com/lightninglabs/lndclient"
2020
"github.com/lightninglabs/loop"
21+
"github.com/lightninglabs/loop/instantout/reservation"
2122
"github.com/lightninglabs/loop/labels"
2223
"github.com/lightninglabs/loop/liquidity"
2324
"github.com/lightninglabs/loop/loopdb"
@@ -74,17 +75,18 @@ type swapClientServer struct {
7475
clientrpc.UnimplementedSwapClientServer
7576
clientrpc.UnimplementedDebugServer
7677

77-
config *Config
78-
network lndclient.Network
79-
impl *loop.Client
80-
liquidityMgr *liquidity.Manager
81-
lnd *lndclient.LndServices
82-
swaps map[lntypes.Hash]loop.SwapInfo
83-
subscribers map[int]chan<- interface{}
84-
statusChan chan loop.SwapInfo
85-
nextSubscriberID int
86-
swapsLock sync.Mutex
87-
mainCtx context.Context
78+
config *Config
79+
network lndclient.Network
80+
impl *loop.Client
81+
liquidityMgr *liquidity.Manager
82+
lnd *lndclient.LndServices
83+
reservationManager *reservation.Manager
84+
swaps map[lntypes.Hash]loop.SwapInfo
85+
subscribers map[int]chan<- interface{}
86+
statusChan chan loop.SwapInfo
87+
nextSubscriberID int
88+
swapsLock sync.Mutex
89+
mainCtx context.Context
8890
}
8991

9092
// LoopOut initiates a loop out swap with the given parameters. The call returns
@@ -1138,6 +1140,25 @@ func (s *swapClientServer) SuggestSwaps(ctx context.Context,
11381140
return resp, nil
11391141
}
11401142

1143+
// ListReservations lists all existing reservations the client has ever made.
1144+
func (s *swapClientServer) ListReservations(ctx context.Context,
1145+
_ *clientrpc.ListReservationsRequest) (
1146+
*clientrpc.ListReservationsResponse, error) {
1147+
1148+
reservations, err := s.reservationManager.GetReservations(
1149+
ctx,
1150+
)
1151+
if err != nil {
1152+
return nil, err
1153+
}
1154+
1155+
return &clientrpc.ListReservationsResponse{
1156+
Reservations: ToClientReservations(
1157+
reservations,
1158+
),
1159+
}, nil
1160+
}
1161+
11411162
func rpcAutoloopReason(reason liquidity.Reason) (clientrpc.AutoReason, error) {
11421163
switch reason {
11431164
case liquidity.ReasonNone:
@@ -1397,3 +1418,40 @@ func getPublicationDeadline(unixTimestamp uint64) time.Time {
13971418
return time.Unix(int64(unixTimestamp), 0)
13981419
}
13991420
}
1421+
1422+
// ToClientReservations converts a slice of server
1423+
// reservations to a slice of client reservations.
1424+
func ToClientReservations(
1425+
res []*reservation.Reservation) []*clientrpc.ClientReservation {
1426+
1427+
var result []*clientrpc.ClientReservation
1428+
for _, r := range res {
1429+
result = append(result, toClientReservation(r))
1430+
}
1431+
1432+
return result
1433+
}
1434+
1435+
// toClientReservation converts a server reservation to a
1436+
// client reservation.
1437+
func toClientReservation(
1438+
res *reservation.Reservation) *clientrpc.ClientReservation {
1439+
1440+
var (
1441+
txid []byte
1442+
vout uint32
1443+
)
1444+
if res.Outpoint != nil {
1445+
txid = res.Outpoint.Hash[:]
1446+
vout = res.Outpoint.Index
1447+
}
1448+
1449+
return &clientrpc.ClientReservation{
1450+
ReservationId: res.ID[:],
1451+
State: string(res.State),
1452+
Amount: uint64(res.Value),
1453+
TxId: txid,
1454+
Vout: vout,
1455+
Expiry: res.Expiry,
1456+
}
1457+
}

0 commit comments

Comments
 (0)