Skip to content

Commit f0aff9b

Browse files
committed
looprpc+loopin: support for native segwit htlcs
This commit extends SwapResponse and SwapStatus with np2wsh and p2wsh htlc output addresses to support both nested and native segwit htlcs in loop-in. Furthermore the commit adds support for native segwith loop-in htlcs. When the htlc is paid internally, as of this commit we'll use NP2WSH, otherwise users are free to select whether to pay the NP2WSH or the P2WSH htlc.
1 parent 62f654e commit f0aff9b

File tree

9 files changed

+388
-181
lines changed

9 files changed

+388
-181
lines changed

client.go

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -194,17 +194,17 @@ func (s *Client) FetchSwaps() ([]*SwapInfo, error) {
194194
}
195195

196196
swaps = append(swaps, &SwapInfo{
197-
SwapType: swap.TypeOut,
198-
SwapContract: swp.Contract.SwapContract,
199-
SwapStateData: swp.State(),
200-
SwapHash: swp.Hash,
201-
LastUpdate: swp.LastUpdateTime(),
202-
HtlcAddress: htlc.Address,
197+
SwapType: swap.TypeOut,
198+
SwapContract: swp.Contract.SwapContract,
199+
SwapStateData: swp.State(),
200+
SwapHash: swp.Hash,
201+
LastUpdate: swp.LastUpdateTime(),
202+
HtlcAddressP2WSH: htlc.Address,
203203
})
204204
}
205205

206206
for _, swp := range loopInSwaps {
207-
htlc, err := swap.NewHtlc(
207+
htlcNP2WSH, err := swap.NewHtlc(
208208
swp.Contract.CltvExpiry, swp.Contract.SenderKey,
209209
swp.Contract.ReceiverKey, swp.Hash, swap.HtlcNP2WSH,
210210
s.lndServices.ChainParams,
@@ -213,13 +213,23 @@ func (s *Client) FetchSwaps() ([]*SwapInfo, error) {
213213
return nil, err
214214
}
215215

216+
htlcP2WSH, err := swap.NewHtlc(
217+
swp.Contract.CltvExpiry, swp.Contract.SenderKey,
218+
swp.Contract.ReceiverKey, swp.Hash, swap.HtlcP2WSH,
219+
s.lndServices.ChainParams,
220+
)
221+
if err != nil {
222+
return nil, err
223+
}
224+
216225
swaps = append(swaps, &SwapInfo{
217-
SwapType: swap.TypeIn,
218-
SwapContract: swp.Contract.SwapContract,
219-
SwapStateData: swp.State(),
220-
SwapHash: swp.Hash,
221-
LastUpdate: swp.LastUpdateTime(),
222-
HtlcAddress: htlc.Address,
226+
SwapType: swap.TypeIn,
227+
SwapContract: swp.Contract.SwapContract,
228+
SwapStateData: swp.State(),
229+
SwapHash: swp.Hash,
230+
LastUpdate: swp.LastUpdateTime(),
231+
HtlcAddressP2WSH: htlcP2WSH.Address,
232+
HtlcAddressNP2WSH: htlcNP2WSH.Address,
223233
})
224234
}
225235

@@ -495,8 +505,9 @@ func (s *Client) LoopIn(globalCtx context.Context,
495505
// Return hash so that the caller can identify this swap in the updates
496506
// stream.
497507
swapInfo := &LoopInSwapInfo{
498-
SwapHash: swap.hash,
499-
HtlcAddress: swap.htlc.Address,
508+
SwapHash: swap.hash,
509+
HtlcAddressP2WSH: swap.htlcP2WSH.Address,
510+
HtlcAddressNP2WSH: swap.htlcNP2WSH.Address,
500511
}
501512
return swapInfo, nil
502513
}

interface.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,13 @@ type LoopInSwapInfo struct { // nolint
241241
// SwapHash contains the sha256 hash of the swap preimage.
242242
SwapHash lntypes.Hash
243243

244-
// HtlcAddress contains the swap htlc address, where the loop-in
245-
// funds will be paid.
246-
HtlcAddress btcutil.Address
244+
// HtlcAddressP2WSH contains the native segwit swap htlc address,
245+
// where the loop-in funds may be paid.
246+
HtlcAddressP2WSH btcutil.Address
247+
248+
// HtlcAddressNP2WSH contains the nested segwit swap htlc address,
249+
// where the loop-in funds may be paid.
250+
HtlcAddressNP2WSH btcutil.Address
247251
}
248252

249253
// SwapInfoKit contains common swap info fields.
@@ -271,8 +275,13 @@ type SwapInfo struct {
271275
// SwapType describes whether this is a loop in or loop out swap.
272276
SwapType swap.Type
273277

274-
// HtlcAddress holds the HTLC address of the swap.
275-
HtlcAddress btcutil.Address
278+
// HtlcAddressP2WSH stores the address of the P2WSH (native segwit)
279+
// swap htlc. This is used for both loop-in and loop-out.
280+
HtlcAddressP2WSH btcutil.Address
281+
282+
// HtlcAddressNP2WSH stores the address of the NP2WSH (nested segwit)
283+
// swap htlc. This is only used for external loop-in.
284+
HtlcAddressNP2WSH btcutil.Address
276285

277286
// ExternalHtlc is set to true for external loop-in swaps.
278287
ExternalHtlc bool

loopd/swapclient_server.go

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -126,27 +126,40 @@ func (s *swapClientServer) marshallSwap(loopSwap *loop.SwapInfo) (
126126
}
127127

128128
var swapType looprpc.SwapType
129+
var htlcAddress, htlcAddressP2WSH, htlcAddressNP2WSH string
130+
129131
switch loopSwap.SwapType {
130132
case swap.TypeIn:
131133
swapType = looprpc.SwapType_LOOP_IN
134+
if loopSwap.ExternalHtlc {
135+
htlcAddressP2WSH = loopSwap.HtlcAddressP2WSH.EncodeAddress()
136+
}
137+
htlcAddressNP2WSH = loopSwap.HtlcAddressNP2WSH.EncodeAddress()
138+
htlcAddress = htlcAddressNP2WSH
139+
132140
case swap.TypeOut:
133141
swapType = looprpc.SwapType_LOOP_OUT
142+
htlcAddressP2WSH = loopSwap.HtlcAddressP2WSH.EncodeAddress()
143+
htlcAddress = htlcAddressP2WSH
144+
134145
default:
135146
return nil, errors.New("unknown swap type")
136147
}
137148

138149
return &looprpc.SwapStatus{
139-
Amt: int64(loopSwap.AmountRequested),
140-
Id: loopSwap.SwapHash.String(),
141-
IdBytes: loopSwap.SwapHash[:],
142-
State: state,
143-
InitiationTime: loopSwap.InitiationTime.UnixNano(),
144-
LastUpdateTime: loopSwap.LastUpdate.UnixNano(),
145-
HtlcAddress: loopSwap.HtlcAddress.EncodeAddress(),
146-
Type: swapType,
147-
CostServer: int64(loopSwap.Cost.Server),
148-
CostOnchain: int64(loopSwap.Cost.Onchain),
149-
CostOffchain: int64(loopSwap.Cost.Offchain),
150+
Amt: int64(loopSwap.AmountRequested),
151+
Id: loopSwap.SwapHash.String(),
152+
IdBytes: loopSwap.SwapHash[:],
153+
State: state,
154+
InitiationTime: loopSwap.InitiationTime.UnixNano(),
155+
LastUpdateTime: loopSwap.LastUpdate.UnixNano(),
156+
HtlcAddress: htlcAddress,
157+
HtlcAddressP2Wsh: htlcAddressP2WSH,
158+
HtlcAddressNp2Wsh: htlcAddressNP2WSH,
159+
Type: swapType,
160+
CostServer: int64(loopSwap.Cost.Server),
161+
CostOnchain: int64(loopSwap.Cost.Onchain),
162+
CostOffchain: int64(loopSwap.Cost.Offchain),
150163
}, nil
151164
}
152165

@@ -418,11 +431,19 @@ func (s *swapClientServer) LoopIn(ctx context.Context,
418431
return nil, err
419432
}
420433

421-
return &looprpc.SwapResponse{
422-
Id: swapInfo.SwapHash.String(),
423-
IdBytes: swapInfo.SwapHash[:],
424-
HtlcAddress: swapInfo.HtlcAddress.String(),
425-
}, nil
434+
np2wshAddress := swapInfo.HtlcAddressNP2WSH.String()
435+
response := &looprpc.SwapResponse{
436+
Id: swapInfo.SwapHash.String(),
437+
IdBytes: swapInfo.SwapHash[:],
438+
HtlcAddress: np2wshAddress,
439+
HtlcAddressNp2Wsh: np2wshAddress,
440+
}
441+
442+
if req.ExternalHtlc {
443+
response.HtlcAddressP2Wsh = swapInfo.HtlcAddressP2WSH.String()
444+
}
445+
446+
return response, nil
426447
}
427448

428449
// GetLsatTokens returns all tokens that are contained in the LSAT token store.

loopin.go

Lines changed: 67 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ type loopInSwap struct {
5454

5555
htlc *swap.Htlc
5656

57+
htlcP2WSH *swap.Htlc
58+
59+
htlcNP2WSH *swap.Htlc
60+
5761
timeoutAddr btcutil.Address
5862
}
5963

@@ -158,19 +162,13 @@ func newLoopInSwap(globalCtx context.Context, cfg *swapConfig,
158162

159163
swapKit.lastUpdateTime = initiationTime
160164

161-
// Create the htlc.
162-
htlc, err := swapKit.getHtlc(swap.HtlcNP2WSH)
163-
if err != nil {
164-
return nil, err
165-
}
166-
167-
// Log htlc address for debugging.
168-
swapKit.log.Infof("Htlc address: %v", htlc.Address)
169-
170165
swap := &loopInSwap{
171166
LoopInContract: contract,
172167
swapKit: *swapKit,
173-
htlc: htlc,
168+
}
169+
170+
if err := swap.initHtlcs(); err != nil {
171+
return nil, err
174172
}
175173

176174
// Persist the data before exiting this function, so that the caller can
@@ -196,19 +194,13 @@ func resumeLoopInSwap(reqContext context.Context, cfg *swapConfig,
196194
hash, swap.TypeIn, cfg, &pend.Contract.SwapContract,
197195
)
198196

199-
// Create the htlc.
200-
htlc, err := swapKit.getHtlc(swap.HtlcNP2WSH)
201-
if err != nil {
202-
return nil, err
203-
}
204-
205-
// Log htlc address for debugging.
206-
swapKit.log.Infof("Htlc address: %v", htlc.Address)
207-
208197
swap := &loopInSwap{
209198
LoopInContract: *pend.Contract,
210199
swapKit: *swapKit,
211-
htlc: htlc,
200+
}
201+
202+
if err := swap.initHtlcs(); err != nil {
203+
return nil, err
212204
}
213205

214206
lastUpdate := pend.LastUpdate()
@@ -238,12 +230,36 @@ func validateLoopInContract(lnd *lndclient.LndServices,
238230
return nil
239231
}
240232

233+
// initHtlcs creates and updates the native and nested segwit htlcs
234+
// of the loopInSwap.
235+
func (s *loopInSwap) initHtlcs() error {
236+
htlcP2WSH, err := s.swapKit.getHtlc(swap.HtlcP2WSH)
237+
if err != nil {
238+
return err
239+
}
240+
241+
htlcNP2WSH, err := s.swapKit.getHtlc(swap.HtlcNP2WSH)
242+
if err != nil {
243+
return err
244+
}
245+
246+
// Log htlc addresses for debugging.
247+
s.swapKit.log.Infof("Htlc address (P2WSH): %v", htlcP2WSH.Address)
248+
s.swapKit.log.Infof("Htlc address (NP2WSH): %v", htlcNP2WSH.Address)
249+
250+
s.htlcP2WSH = htlcP2WSH
251+
s.htlcNP2WSH = htlcNP2WSH
252+
253+
return nil
254+
}
255+
241256
// sendUpdate reports an update to the swap state.
242257
func (s *loopInSwap) sendUpdate(ctx context.Context) error {
243258
info := s.swapInfo()
244259
s.log.Infof("Loop in swap state: %v", info.State)
245260

246-
info.HtlcAddress = s.htlc.Address
261+
info.HtlcAddressP2WSH = s.htlcP2WSH.Address
262+
info.HtlcAddressNP2WSH = s.htlcNP2WSH.Address
247263
info.ExternalHtlc = s.ExternalHtlc
248264

249265
select {
@@ -373,21 +389,44 @@ func (s *loopInSwap) waitForHtlcConf(globalCtx context.Context) (
373389

374390
ctx, cancel := context.WithCancel(globalCtx)
375391
defer cancel()
376-
confChan, confErr, err := s.lnd.ChainNotifier.RegisterConfirmationsNtfn(
377-
ctx, nil, s.htlc.PkScript, 1, s.InitiationHeight,
392+
393+
notifier := s.lnd.ChainNotifier
394+
395+
confChanP2WSH, confErrP2WSH, err := notifier.RegisterConfirmationsNtfn(
396+
ctx, nil, s.htlcP2WSH.PkScript, 1, s.InitiationHeight,
397+
)
398+
if err != nil {
399+
return nil, err
400+
}
401+
402+
confChanNP2WSH, confErrNP2WSH, err := notifier.RegisterConfirmationsNtfn(
403+
ctx, nil, s.htlcNP2WSH.PkScript, 1, s.InitiationHeight,
378404
)
379405
if err != nil {
380406
return nil, err
381407
}
408+
382409
for {
383410
select {
384411

385-
// Htlc confirmed.
386-
case conf := <-confChan:
412+
// P2WSH htlc confirmed.
413+
case conf := <-confChanP2WSH:
414+
s.htlc = s.htlcP2WSH
415+
s.log.Infof("P2WSH htlc confirmed")
416+
return conf, nil
417+
418+
// NP2WSH htlc confirmed.
419+
case conf := <-confChanNP2WSH:
420+
s.htlc = s.htlcNP2WSH
421+
s.log.Infof("NP2WSH htlc confirmed")
387422
return conf, nil
388423

389424
// Conf ntfn error.
390-
case err := <-confErr:
425+
case err := <-confErrP2WSH:
426+
return nil, err
427+
428+
// Conf ntfn error.
429+
case err := <-confErrNP2WSH:
391430
return nil, err
392431

393432
// Keep up with block height.
@@ -432,9 +471,10 @@ func (s *loopInSwap) publishOnChainHtlc(ctx context.Context) (bool, error) {
432471
}
433472

434473
s.log.Infof("Publishing on chain HTLC with fee rate %v", feeRate)
474+
435475
tx, err := s.lnd.WalletKit.SendOutputs(ctx,
436476
[]*wire.TxOut{{
437-
PkScript: s.htlc.PkScript,
477+
PkScript: s.htlcNP2WSH.PkScript,
438478
Value: int64(s.LoopInContract.AmountRequested),
439479
}},
440480
feeRate,

0 commit comments

Comments
 (0)