@@ -2,31 +2,194 @@ package reservation
2
2
3
3
import (
4
4
"context"
5
+ "errors"
6
+ "fmt"
7
+ "time"
5
8
6
9
"github.com/btcsuite/btcd/btcec/v2"
7
10
"github.com/btcsuite/btcd/btcutil"
11
+ "github.com/lightninglabs/lndclient"
8
12
"github.com/lightninglabs/loop/fsm"
13
+ "github.com/lightninglabs/loop/swap"
9
14
"github.com/lightninglabs/loop/swapserverrpc"
10
15
"github.com/lightningnetwork/lnd/chainntnfs"
16
+ "github.com/lightningnetwork/lnd/lnrpc"
11
17
)
12
18
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 {
15
178
reservationID ID
16
179
serverPubkey * btcec.PublicKey
17
180
value btcutil.Amount
18
181
expiry uint32
19
182
heightHint uint32
20
183
}
21
184
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 ,
26
189
eventCtx fsm.EventContext ) fsm.EventType {
27
190
28
191
// Check if the context is of the correct type.
29
- reservationRequest , ok := eventCtx .(* InitReservationContext )
192
+ reservationRequest , ok := eventCtx .(* ServerRequestedInitContext )
30
193
if ! ok {
31
194
return f .HandleError (fsm .ErrInvalidContextType )
32
195
}
@@ -240,3 +403,7 @@ func (f *FSM) handleAsyncError(ctx context.Context, err error) {
240
403
f .Errorf ("Error sending event: %v" , err2 )
241
404
}
242
405
}
406
+
407
+ func getMaxRoutingFee (amt btcutil.Amount ) btcutil.Amount {
408
+ return swap .CalcFee (amt , maxRoutingFeeBase , maxRoutingFeeRate )
409
+ }
0 commit comments