@@ -2,31 +2,194 @@ package reservation
22
33import (
44 "context"
5+ "errors"
6+ "fmt"
7+ "time"
58
69 "github.com/btcsuite/btcd/btcec/v2"
710 "github.com/btcsuite/btcd/btcutil"
11+ "github.com/lightninglabs/lndclient"
812 "github.com/lightninglabs/loop/fsm"
13+ "github.com/lightninglabs/loop/swap"
914 "github.com/lightninglabs/loop/swapserverrpc"
1015 "github.com/lightningnetwork/lnd/chainntnfs"
16+ "github.com/lightningnetwork/lnd/lnrpc"
1117)
1218
13- // InitReservationContext contains the request parameters for a reservation.
14- type InitReservationContext struct {
19+ const (
20+ // Define route independent max routing fees. We have currently no way
21+ // to get a reliable estimate of the routing fees. Best we can do is
22+ // the minimum routing fees, which is not very indicative.
23+ maxRoutingFeeBase = btcutil .Amount (10 )
24+
25+ maxRoutingFeeRate = int64 (20000 )
26+ )
27+
28+ var (
29+ // The allowed delta between what we accept as the expiry height and
30+ // the actual expiry height.
31+ expiryDelta = uint32 (3 )
32+
33+ // defaultPrepayTimeout is the default timeout for the prepayment.
34+ DefaultPrepayTimeout = time .Minute * 120
35+ )
36+
37+ // ClientRequestedInitContext contains the request parameters for a reservation.
38+ type ClientRequestedInitContext struct {
39+ value btcutil.Amount
40+ relativeExpiry uint32
41+ heightHint uint32
42+ maxPrepaymentAmt btcutil.Amount
43+ }
44+
45+ // InitFromClientRequestAction is the action that is executed when the
46+ // reservation state machine is initialized from a client request. It creates
47+ // the reservation in the database and sends the reservation request to the
48+ // server.
49+ func (f * FSM ) InitFromClientRequestAction (ctx context.Context ,
50+ eventCtx fsm.EventContext ) fsm.EventType {
51+
52+ // Check if the context is of the correct type.
53+ reservationRequest , ok := eventCtx .(* ClientRequestedInitContext )
54+ if ! ok {
55+ return f .HandleError (fsm .ErrInvalidContextType )
56+ }
57+
58+ // Create the reservation in the database.
59+ keyRes , err := f .cfg .Wallet .DeriveNextKey (ctx , KeyFamily )
60+ if err != nil {
61+ return f .HandleError (err )
62+ }
63+
64+ // Send the request to the server.
65+ requestResponse , err := f .cfg .ReservationClient .RequestReservation (
66+ ctx , & swapserverrpc.RequestReservationRequest {
67+ Value : uint64 (reservationRequest .value ),
68+ Expiry : reservationRequest .relativeExpiry ,
69+ ClientKey : keyRes .PubKey .SerializeCompressed (),
70+ },
71+ )
72+ if err != nil {
73+ return f .HandleError (err )
74+ }
75+
76+ expectedExpiry := reservationRequest .relativeExpiry +
77+ reservationRequest .heightHint
78+
79+ // Check that the expiry is in the delta.
80+ if requestResponse .Expiry < expectedExpiry - expiryDelta ||
81+ requestResponse .Expiry > expectedExpiry + expiryDelta {
82+
83+ return f .HandleError (
84+ fmt .Errorf ("unexpected expiry height: %v, expected %v" ,
85+ requestResponse .Expiry , expectedExpiry ))
86+ }
87+
88+ prepayment , err := f .cfg .LightningClient .DecodePaymentRequest (
89+ ctx , requestResponse .Invoice ,
90+ )
91+ if err != nil {
92+ return f .HandleError (err )
93+ }
94+
95+ if prepayment .Value .ToSatoshis () > reservationRequest .maxPrepaymentAmt {
96+ return f .HandleError (
97+ errors .New ("prepayment amount too high" ))
98+ }
99+
100+ serverKey , err := btcec .ParsePubKey (requestResponse .ServerKey )
101+ if err != nil {
102+ return f .HandleError (err )
103+ }
104+
105+ var Id ID
106+ copy (Id [:], requestResponse .ReservationId )
107+
108+ reservation , err := NewReservation (
109+ Id , serverKey , keyRes .PubKey , reservationRequest .value ,
110+ requestResponse .Expiry , reservationRequest .heightHint ,
111+ keyRes .KeyLocator , ProtocolVersionClientInitiated ,
112+ )
113+ if err != nil {
114+ return f .HandleError (err )
115+ }
116+ reservation .PrepayInvoice = requestResponse .Invoice
117+ f .reservation = reservation
118+
119+ // Create the reservation in the database.
120+ err = f .cfg .Store .CreateReservation (ctx , reservation )
121+ if err != nil {
122+ return f .HandleError (err )
123+ }
124+
125+ return OnClientInitialized
126+ }
127+
128+ // SendPrepayment is the action that is executed when the reservation
129+ // is initialized from a client request. It dispatches the prepayment to the
130+ // server and wait for it to be settled, signaling confirmation of the
131+ // reservation.
132+ func (f * FSM ) SendPrepayment (ctx context.Context ,
133+ _ fsm.EventContext ) fsm.EventType {
134+
135+ prepayment , err := f .cfg .LightningClient .DecodePaymentRequest (
136+ ctx , f .reservation .PrepayInvoice ,
137+ )
138+ if err != nil {
139+ return f .HandleError (err )
140+ }
141+
142+ payReq := lndclient.SendPaymentRequest {
143+ Invoice : f .reservation .PrepayInvoice ,
144+ Timeout : DefaultPrepayTimeout ,
145+ MaxFee : getMaxRoutingFee (prepayment .Value .ToSatoshis ()),
146+ }
147+ // Send the prepayment to the server.
148+ payChan , errChan , err := f .cfg .RouterClient .SendPayment (
149+ ctx , payReq ,
150+ )
151+ if err != nil {
152+ return f .HandleError (err )
153+ }
154+
155+ for {
156+ select {
157+ case <- ctx .Done ():
158+ return fsm .NoOp
159+
160+ case err := <- errChan :
161+ return f .HandleError (err )
162+
163+ case prepayResp := <- payChan :
164+ if prepayResp .State == lnrpc .Payment_FAILED {
165+ return f .HandleError (
166+ fmt .Errorf ("prepayment failed: %v" ,
167+ prepayResp .FailureReason ))
168+ }
169+ if prepayResp .State == lnrpc .Payment_SUCCEEDED {
170+ return OnBroadcast
171+ }
172+ }
173+ }
174+ }
175+
176+ // ServerRequestedInitContext contains the request parameters for a reservation.
177+ type ServerRequestedInitContext struct {
15178 reservationID ID
16179 serverPubkey * btcec.PublicKey
17180 value btcutil.Amount
18181 expiry uint32
19182 heightHint uint32
20183}
21184
22- // InitAction is the action that is executed when the reservation state machine
23- // is initialized. It creates the reservation in the database and dispatches the
24- // payment to the server.
25- func (f * FSM ) InitAction (ctx context.Context ,
185+ // InitFromServerRequestAction is the action that is executed when the
186+ // reservation state machine is initialized from a server request. It creates
187+ // the reservation in the database and dispatches the payment to the server.
188+ func (f * FSM ) InitFromServerRequestAction (ctx context.Context ,
26189 eventCtx fsm.EventContext ) fsm.EventType {
27190
28191 // Check if the context is of the correct type.
29- reservationRequest , ok := eventCtx .(* InitReservationContext )
192+ reservationRequest , ok := eventCtx .(* ServerRequestedInitContext )
30193 if ! ok {
31194 return f .HandleError (fsm .ErrInvalidContextType )
32195 }
@@ -240,3 +403,7 @@ func (f *FSM) handleAsyncError(ctx context.Context, err error) {
240403 f .Errorf ("Error sending event: %v" , err2 )
241404 }
242405}
406+
407+ func getMaxRoutingFee (amt btcutil.Amount ) btcutil.Amount {
408+ return swap .CalcFee (amt , maxRoutingFeeBase , maxRoutingFeeRate )
409+ }
0 commit comments