@@ -10,9 +10,21 @@ import (
1010 "github.com/btcsuite/btcd/btcec/v2"
1111 "github.com/btcsuite/btcd/btcutil"
1212 "github.com/lightninglabs/loop/fsm"
13+ "github.com/lightninglabs/loop/swapserverrpc"
1314 reservationrpc "github.com/lightninglabs/loop/swapserverrpc"
1415)
1516
17+ var (
18+ defaultWaitForStateTime = time .Second * 15
19+ )
20+
21+ // FSMSendEventReq contains the information needed to send an event to the FSM.
22+ type FSMSendEventReq struct {
23+ fsm * FSM
24+ event fsm.EventType
25+ eventCtx fsm.EventContext
26+ }
27+
1628// Manager manages the reservation state machines.
1729type Manager struct {
1830 // cfg contains all the services that the reservation manager needs to
@@ -22,6 +34,10 @@ type Manager struct {
2234 // activeReservations contains all the active reservationsFSMs.
2335 activeReservations map [ID ]* FSM
2436
37+ currentHeight int32
38+
39+ reqChan chan * FSMSendEventReq
40+
2541 sync.Mutex
2642}
2743
@@ -30,6 +46,7 @@ func NewManager(cfg *Config) *Manager {
3046 return & Manager {
3147 cfg : cfg ,
3248 activeReservations : make (map [ID ]* FSM ),
49+ reqChan : make (chan * FSMSendEventReq ),
3350 }
3451}
3552
@@ -42,7 +59,7 @@ func (m *Manager) Run(ctx context.Context, height int32,
4259 runCtx , cancel := context .WithCancel (ctx )
4360 defer cancel ()
4461
45- currentHeight : = height
62+ m . currentHeight = height
4663
4764 err := m .RecoverReservations (runCtx )
4865 if err != nil {
@@ -64,7 +81,9 @@ func (m *Manager) Run(ctx context.Context, height int32,
6481 select {
6582 case height := <- newBlockChan :
6683 log .Debugf ("Received block %v" , height )
67- currentHeight = height
84+ m .Lock ()
85+ m .currentHeight = height
86+ m .Unlock ()
6887
6988 case reservationRes , ok := <- ntfnChan :
7089 if ! ok {
@@ -76,13 +95,26 @@ func (m *Manager) Run(ctx context.Context, height int32,
7695
7796 log .Debugf ("Received reservation %x" ,
7897 reservationRes .ReservationId )
79- _ , err := m .newReservation (
80- runCtx , uint32 (currentHeight ), reservationRes ,
98+ _ , err := m .newReservationFromNtfn (
99+ runCtx , uint32 (m . currentHeight ), reservationRes ,
81100 )
82101 if err != nil {
83102 return err
84103 }
85104
105+ case req := <- m .reqChan :
106+ // We'll send the event in a goroutine to avoid blocking
107+ // the main loop.
108+ go func () {
109+ err := req .fsm .SendEvent (
110+ runCtx , req .event , req .eventCtx ,
111+ )
112+ if err != nil {
113+ log .Errorf ("Error sending event: %v" ,
114+ err )
115+ }
116+ }()
117+
86118 case err := <- newBlockErrChan :
87119 return err
88120
@@ -93,9 +125,11 @@ func (m *Manager) Run(ctx context.Context, height int32,
93125 }
94126}
95127
96- // newReservation creates a new reservation from the reservation request.
97- func (m * Manager ) newReservation (ctx context.Context , currentHeight uint32 ,
98- req * reservationrpc.ServerReservationNotification ) (* FSM , error ) {
128+ // newReservationFromNtfn creates a new reservation from the reservation
129+ // notification.
130+ func (m * Manager ) newReservationFromNtfn (ctx context.Context ,
131+ currentHeight uint32 , req * reservationrpc.ServerReservationNotification ,
132+ ) (* FSM , error ) {
99133
100134 var reservationID ID
101135 err := reservationID .FromByteSlice (
@@ -120,7 +154,7 @@ func (m *Manager) newReservation(ctx context.Context, currentHeight uint32,
120154 m .activeReservations [reservationID ] = reservationFSM
121155 m .Unlock ()
122156
123- initContext := & InitReservationContext {
157+ initContext := & ServerRequestedInitContext {
124158 reservationID : reservationID ,
125159 serverPubkey : serverKey ,
126160 value : btcutil .Amount (req .Value ),
@@ -154,6 +188,66 @@ func (m *Manager) newReservation(ctx context.Context, currentHeight uint32,
154188 return reservationFSM , nil
155189}
156190
191+ // RequestReservationFromServer sends a request to the server to create a new
192+ // reservation.
193+ func (m * Manager ) RequestReservationFromServer (ctx context.Context ,
194+ value btcutil.Amount , expiry uint32 , maxPrepaymentAmt btcutil.Amount ) (
195+ * Reservation , error ) {
196+
197+ m .Lock ()
198+ currentHeight := m .currentHeight
199+ m .Unlock ()
200+ // Create a new reservation req.
201+ req := & ClientRequestedInitContext {
202+ value : value ,
203+ relativeExpiry : expiry ,
204+ heightHint : uint32 (currentHeight ),
205+ maxPrepaymentAmt : maxPrepaymentAmt ,
206+ }
207+
208+ reservationFSM := NewFSM (m .cfg , ProtocolVersionClientInitiated )
209+ // Send the event to the main loop.
210+ m .reqChan <- & FSMSendEventReq {
211+ fsm : reservationFSM ,
212+ event : OnClientInitialized ,
213+ eventCtx : req ,
214+ }
215+
216+ // We'll now wait for the reservation to be in the state where we are
217+ // sending the prepayment.
218+ err := reservationFSM .DefaultObserver .WaitForState (
219+ ctx , defaultWaitForStateTime , SendPrepaymentPayment ,
220+ fsm .WithAbortEarlyOnErrorOption (),
221+ )
222+ if err != nil {
223+ return nil , err
224+ }
225+
226+ // Now we can add the reservation to our active fsm.
227+ m .Lock ()
228+ m .activeReservations [reservationFSM .reservation .ID ] = reservationFSM
229+ m .Unlock ()
230+
231+ return reservationFSM .reservation , nil
232+ }
233+
234+ // QuoteReservation quotes the server for a new reservation.
235+ func (m * Manager ) QuoteReservation (ctx context.Context , value btcutil.Amount ,
236+ expiry uint32 ) (btcutil.Amount , error ) {
237+
238+ quoteReq := & swapserverrpc.QuoteReservationRequest {
239+ Value : uint64 (value ),
240+ Expiry : expiry ,
241+ }
242+
243+ req , err := m .cfg .ReservationClient .QuoteReservation (ctx , quoteReq )
244+ if err != nil {
245+ return 0 , err
246+ }
247+
248+ return btcutil .Amount (req .PrepayCost ), nil
249+ }
250+
157251// RecoverReservations tries to recover all reservations that are still active
158252// from the database.
159253func (m * Manager ) RecoverReservations (ctx context.Context ) error {
0 commit comments