forked from lightningnetwork/lnd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
channel.go
596 lines (495 loc) · 17.7 KB
/
channel.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
package lnwallet
import (
"bytes"
"fmt"
"sync"
"github.com/lightningnetwork/lnd/chainntfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/txsort"
)
const (
// TODO(roasbeef): make not random value
MaxPendingPayments = 10
)
// PaymentHash presents the hash160 of a random value. This hash is used to
// uniquely track incoming/outgoing payments within this channel, as well as
// payments requested by the wallet/daemon.
type PaymentHash [20]byte
// LightningChannel...
// TODO(roasbeef): future peer struct should embed this struct
type LightningChannel struct {
lnwallet *LightningWallet
channelEvents chainntnfs.ChainNotifier
// TODO(roasbeef): Stores all previous R values + timeouts for each
// commitment update, plus some other meta-data...Or just use OP_RETURN
// to help out?
// currently going for: nSequence/nLockTime overloading
channelDB *channeldb.DB
// stateMtx protects concurrent access to the state struct.
stateMtx sync.RWMutex
channelState *channeldb.OpenChannel
updateTotem chan struct{}
// Uncleared HTLC's.
pendingPayments map[PaymentHash]*PaymentDescriptor
// Payment's which we've requested.
unfufilledPayments map[PaymentHash]*PaymentRequest
fundingTxIn *wire.TxIn
fundingP2SH []byte
// TODO(roasbeef): create and embed 'Service' interface w/ below?
started int32
shutdown int32
quit chan struct{}
wg sync.WaitGroup
}
// newLightningChannel...
func newLightningChannel(wallet *LightningWallet, events chainntnfs.ChainNotifier,
chanDB *channeldb.DB, state *channeldb.OpenChannel) (*LightningChannel, error) {
lc := &LightningChannel{
lnwallet: wallet,
channelEvents: events,
channelState: state,
channelDB: chanDB,
updateTotem: make(chan struct{}, 1),
pendingPayments: make(map[PaymentHash]*PaymentDescriptor),
unfufilledPayments: make(map[PaymentHash]*PaymentRequest),
}
// TODO(roasbeef): do a NotifySpent for the funding input, and
// NotifyReceived for all commitment outputs.
// Populate the totem.
lc.updateTotem <- struct{}{}
fundingTxId := state.FundingTx.TxSha()
fundingPkScript, err := scriptHashPkScript(state.FundingRedeemScript)
if err != nil {
return nil, err
}
_, multiSigIndex := findScriptOutputIndex(state.FundingTx, fundingPkScript)
lc.fundingTxIn = wire.NewTxIn(wire.NewOutPoint(&fundingTxId, multiSigIndex), nil)
lc.fundingP2SH = fundingPkScript
return lc, nil
}
// PaymentDescriptor...
type PaymentDescriptor struct {
RHash [20]byte
Timeout uint32
Value btcutil.Amount
OurRevocation [20]byte // TODO(roasbeef): don't need these?
TheirRevocation [20]byte
PayToUs bool
}
// ChannelUpdate...
type ChannelUpdate struct {
pendingDesc *PaymentDescriptor
deletion bool
currentUpdateNum uint64
pendingUpdateNum uint64
ourPendingCommitTx *wire.MsgTx
theirPendingCommitTx *wire.MsgTx
pendingRevocation [20]byte
sigTheirNewCommit []byte
// TODO(roasbeef): some enum to track current state in lifetime?
// state UpdateStag
lnChannel *LightningChannel
}
// RevocationHash...
func (c *ChannelUpdate) RevocationHash() ([]byte, error) {
c.lnChannel.stateMtx.RLock()
defer c.lnChannel.stateMtx.RUnlock()
shachain := c.lnChannel.channelState.OurShaChain
nextPreimage, err := shachain.GetHash(c.pendingUpdateNum)
if err != nil {
return nil, err
}
return btcutil.Hash160(nextPreimage[:]), nil
}
// SignCounterPartyCommitment...
func (c *ChannelUpdate) SignCounterPartyCommitment() ([]byte, error) {
c.lnChannel.stateMtx.RLock()
defer c.lnChannel.stateMtx.RUnlock()
if c.sigTheirNewCommit != nil {
return c.sigTheirNewCommit, nil
}
// Sign their version of the commitment transaction.
sig, err := txscript.RawTxInSignature(c.theirPendingCommitTx, 0,
c.lnChannel.channelState.FundingRedeemScript, txscript.SigHashAll,
c.lnChannel.channelState.MultiSigKey)
if err != nil {
return nil, err
}
c.sigTheirNewCommit = sig
return sig, nil
}
// PreviousRevocationPreImage...
func (c *ChannelUpdate) PreviousRevocationPreImage() ([]byte, error) {
c.lnChannel.stateMtx.RLock()
defer c.lnChannel.stateMtx.RUnlock()
// Retrieve the pre-image to the revocation hash our current commitment
// transaction.
shachain := c.lnChannel.channelState.OurShaChain
revokePreImage, err := shachain.GetHash(c.currentUpdateNum)
if err != nil {
return nil, err
}
return revokePreImage[:], nil
}
// VerifyNewCommitmentSigs...
func (c *ChannelUpdate) VerifyNewCommitmentSigs(ourSig, theirSig []byte) error {
c.lnChannel.stateMtx.RLock()
defer c.lnChannel.stateMtx.RUnlock()
var err error
var scriptSig []byte
channelState := c.lnChannel.channelState
// When initially generating the redeemScript, we sorted the serialized
// public keys in descending order. So we do a quick comparison in order
// ensure the signatures appear on the Script Virual Machine stack in
// the correct order.
redeemScript := channelState.FundingRedeemScript
ourKey := channelState.OurCommitKey.PubKey().SerializeCompressed()
theirKey := channelState.TheirCommitKey.SerializeCompressed()
scriptSig, err = spendMultiSig(redeemScript, ourKey, ourSig, theirKey, theirSig)
if err != nil {
return err
}
// Attach the scriptSig to our commitment transaction's only input,
// then validate that the scriptSig executes correctly.
commitTx := c.ourPendingCommitTx
commitTx.TxIn[0].SignatureScript = scriptSig
vm, err := txscript.NewEngine(c.lnChannel.fundingP2SH, commitTx, 0,
txscript.StandardVerifyFlags, nil)
if err != nil {
return err
}
return vm.Execute()
}
// Commit...
func (c *ChannelUpdate) Commit(pastRevokePreimage []byte) error {
c.lnChannel.stateMtx.Lock()
defer c.lnChannel.stateMtx.Unlock()
// First, ensure that the pre-image properly links into the shachain.
theirShaChain := c.lnChannel.channelState.TheirShaChain
var preImage [32]byte
copy(preImage[:], pastRevokePreimage)
if err := theirShaChain.AddNextHash(preImage); err != nil {
return err
}
channelState := c.lnChannel.channelState
// Finally, verify that that this is indeed the pre-image to the
// revocation hash we were given earlier.
if !bytes.Equal(btcutil.Hash160(pastRevokePreimage),
channelState.TheirCurrentRevocation[:]) {
return fmt.Errorf("pre-image hash does not match revocation")
}
// Store this current revocation in the channel state so we can
// verify future channel updates.
channelState.TheirCurrentRevocation = c.pendingRevocation
// The channel update is now complete, roll over to the newest commitment
// transaction.
channelState.OurCommitTx = c.ourPendingCommitTx
channelState.TheirCommitTx = c.theirPendingCommitTx
channelState.NumUpdates = c.pendingUpdateNum
// If this channel update involved deleting an HTLC, remove it from the
// set of pending payments.
if c.deletion {
delete(c.lnChannel.pendingPayments, c.pendingDesc.RHash)
}
// TODO(roasbeef): db writes, checkpoints, and such
// Return the updateTotem, allowing another update to be created now
// that this pending update has been commited, and finalized.
c.lnChannel.updateTotem <- struct{}{}
return nil
}
// AddHTLC...
// 1. request R_Hash from receiver (only if single hop, would be out of band)
// 2. propose HTLC
// * timeout
// * value
// * r_hash
// * next revocation hash
// 3. they accept
// * their next revocation hash
// * their sig for our new commitment tx (verify correctness)
// Can buld both new commitment txns at this point
// 4. we give sigs
// * our sigs for their new commitment tx
// * the pre-image to our old commitment tx
// 5. they complete
// * the pre-image to their old commitment tx (verify is part of their chain, is pre-image)
func (lc *LightningChannel) AddHTLC(timeout uint32, value btcutil.Amount,
rHash, revocation PaymentHash, payToUs bool) (*ChannelUpdate, error) {
// Grab the updateTotem, this acts as a barrier upholding the invariant
// that only one channel update transaction should exist at any moment.
// This aides in ensuring the channel updates are atomic, and consistent.
<-lc.updateTotem
chanUpdate := &ChannelUpdate{
pendingDesc: &PaymentDescriptor{
RHash: rHash,
TheirRevocation: revocation,
Timeout: timeout,
Value: value,
PayToUs: payToUs,
},
pendingRevocation: revocation,
lnChannel: lc,
}
// Get next revocation hash, updating the number of updates in the
// channel as a result.
chanUpdate.currentUpdateNum = lc.channelState.NumUpdates
chanUpdate.pendingUpdateNum = lc.channelState.NumUpdates + 1
nextPreimage, err := lc.channelState.OurShaChain.GetHash(chanUpdate.pendingUpdateNum)
if err != nil {
return nil, err
}
copy(chanUpdate.pendingDesc.OurRevocation[:], btcutil.Hash160(nextPreimage[:]))
// Re-calculate the amount of cleared funds for each side.
var amountToUs, amountToThem btcutil.Amount
if payToUs {
amountToUs = lc.channelState.OurBalance
amountToThem = lc.channelState.TheirBalance - value
} else {
amountToUs = lc.channelState.OurBalance - value
amountToThem = lc.channelState.TheirBalance
}
// Re-create copies of the current commitment transactions to be updated.
ourNewCommitTx, theirNewCommitTx, err := createNewCommitmentTxns(
lc.fundingTxIn, lc.channelState, chanUpdate, amountToUs, amountToThem,
)
if err != nil {
return nil, err
}
// First, re-add all the old HTLCs.
for _, paymentDesc := range lc.pendingPayments {
if err := lc.addHTLC(ourNewCommitTx, theirNewCommitTx, paymentDesc); err != nil {
return nil, err
}
}
// Then add this new HTLC.
if err := lc.addHTLC(ourNewCommitTx, theirNewCommitTx, chanUpdate.pendingDesc); err != nil {
return nil, err
}
lc.pendingPayments[rHash] = chanUpdate.pendingDesc // TODO(roasbeef): check for dups?
// Sort both transactions according to the agreed upon cannonical
// ordering. This lets us skip sending the entire transaction over,
// instead we'll just send signatures.
txsort.InPlaceSort(ourNewCommitTx)
txsort.InPlaceSort(theirNewCommitTx)
// TODO(roasbeef): locktimes/sequence set
// TODO(roasbeef): write checkpoint here...
chanUpdate.ourPendingCommitTx = ourNewCommitTx
chanUpdate.theirPendingCommitTx = theirNewCommitTx
return chanUpdate, nil
}
// addHTLC...
// NOTE: This MUST be called with stateMtx held.
func (lc *LightningChannel) addHTLC(ourCommitTx, theirCommitTx *wire.MsgTx,
paymentDesc *PaymentDescriptor) error {
// If the HTLC is going to us, then we're the sender, otherwise they
// are.
var senderKey, receiverKey *btcec.PublicKey
var senderRevocation, receiverRevocation []byte
if paymentDesc.PayToUs {
receiverKey = lc.channelState.OurCommitKey.PubKey()
receiverRevocation = paymentDesc.OurRevocation[:]
senderKey = lc.channelState.TheirCommitKey
senderRevocation = paymentDesc.TheirRevocation[:]
} else {
senderKey = lc.channelState.OurCommitKey.PubKey()
senderRevocation = paymentDesc.OurRevocation[:]
receiverKey = lc.channelState.TheirCommitKey
receiverRevocation = paymentDesc.TheirRevocation[:]
}
// Generate the proper redeem scripts for the HTLC output for both the
// sender and the receiver.
timeout := paymentDesc.Timeout
rHash := paymentDesc.RHash
delay := lc.channelState.CsvDelay
senderPKScript, err := senderHTLCScript(timeout, delay, senderKey,
receiverKey, senderRevocation[:], rHash[:])
if err != nil {
return nil
}
receiverPKScript, err := receiverHTLCScript(timeout, delay, senderKey,
receiverKey, receiverRevocation[:], rHash[:])
if err != nil {
return nil
}
// Now that we have the redeem scripts, create the P2SH public key
// script for each.
senderP2SH, err := scriptHashPkScript(senderPKScript)
if err != nil {
return nil
}
receiverP2SH, err := scriptHashPkScript(receiverPKScript)
if err != nil {
return nil
}
// Add the new HTLC outputs to the respective commitment transactions.
amountPending := int64(paymentDesc.Value)
if paymentDesc.PayToUs {
ourCommitTx.AddTxOut(wire.NewTxOut(amountPending, receiverP2SH))
theirCommitTx.AddTxOut(wire.NewTxOut(amountPending, senderP2SH))
} else {
ourCommitTx.AddTxOut(wire.NewTxOut(amountPending, senderP2SH))
theirCommitTx.AddTxOut(wire.NewTxOut(amountPending, receiverP2SH))
}
return nil
}
// SettleHTLC...
// R-VALUE, NEW REVOKE HASH
// accept, sig
func (lc *LightningChannel) SettleHTLC(rValue [20]byte, newRevocation [20]byte) (*ChannelUpdate, error) {
// Grab the updateTotem, this acts as a barrier upholding the invariant
// that only one channel update transaction should exist at any moment.
// This aides in ensuring the channel updates are atomic, and consistent.
<-lc.updateTotem
// Find the matching payment descriptor, bailing out early if it
// doesn't exist.
var rHash PaymentHash
copy(rHash[:], btcutil.Hash160(rValue[:]))
payDesc, ok := lc.pendingPayments[rHash]
if !ok {
return nil, fmt.Errorf("r-hash for preimage not found")
}
chanUpdate := &ChannelUpdate{
pendingDesc: payDesc,
deletion: true,
pendingRevocation: newRevocation,
lnChannel: lc,
}
// TODO(roasbeef): such copy pasta, make into func...
// Get next revocation hash, updating the number of updates in the
// channel as a result.
chanUpdate.currentUpdateNum = lc.channelState.NumUpdates
chanUpdate.pendingUpdateNum = lc.channelState.NumUpdates + 1
nextPreimage, err := lc.channelState.OurShaChain.GetHash(chanUpdate.pendingUpdateNum)
if err != nil {
return nil, err
}
copy(chanUpdate.pendingDesc.OurRevocation[:], btcutil.Hash160(nextPreimage[:]))
// Re-calculate the amount of cleared funds for each side.
var amountToUs, amountToThem btcutil.Amount
if payDesc.PayToUs {
amountToUs = lc.channelState.OurBalance + payDesc.Value
amountToThem = lc.channelState.TheirBalance
} else {
amountToUs = lc.channelState.OurBalance
amountToThem = lc.channelState.TheirBalance + payDesc.Value
}
// Create new commitment transactions that reflect the settlement of
// this pending HTLC.
ourNewCommitTx, theirNewCommitTx, err := createNewCommitmentTxns(
lc.fundingTxIn, lc.channelState, chanUpdate, amountToUs, amountToThem,
)
if err != nil {
return nil, err
}
// Re-add all the HTLC's skipping over this newly settled payment.
for paymentHash, paymentDesc := range lc.pendingPayments {
if bytes.Equal(paymentHash[:], rHash[:]) {
continue
}
if err := lc.addHTLC(ourNewCommitTx, theirNewCommitTx, paymentDesc); err != nil {
return nil, err
}
}
// Sort both transactions according to the agreed upon cannonical
// ordering. This lets us skip sending the entire transaction over,
// instead we'll just send signatures.
txsort.InPlaceSort(ourNewCommitTx)
txsort.InPlaceSort(theirNewCommitTx)
// TODO(roasbeef): locktimes/sequence set
// TODO(roasbeef): write checkpoint here...
chanUpdate.ourPendingCommitTx = ourNewCommitTx
chanUpdate.theirPendingCommitTx = theirNewCommitTx
return chanUpdate, nil
}
// createNewCommitmentTxns....
// NOTE: This MUST be called with stateMtx held.
func createNewCommitmentTxns(fundingTxIn *wire.TxIn, state *channeldb.OpenChannel,
chanUpdate *ChannelUpdate, amountToUs, amountToThem btcutil.Amount) (*wire.MsgTx, *wire.MsgTx, error) {
ourNewCommitTx, err := createCommitTx(fundingTxIn,
state.OurCommitKey.PubKey(), state.TheirCommitKey,
chanUpdate.pendingDesc.OurRevocation[:], state.CsvDelay,
amountToUs, amountToThem)
if err != nil {
return nil, nil, err
}
theirNewCommitTx, err := createCommitTx(fundingTxIn,
state.TheirCommitKey, state.OurCommitKey.PubKey(),
chanUpdate.pendingDesc.TheirRevocation[:], state.CsvDelay,
amountToThem, amountToUs)
if err != nil {
return nil, nil, err
}
return ourNewCommitTx, theirNewCommitTx, nil
}
// CancelHTLC...
func (lc *LightningChannel) CancelHTLC() error {
return nil
}
// OurBalance...
func (lc *LightningChannel) OurBalance() btcutil.Amount {
lc.stateMtx.RLock()
defer lc.stateMtx.RUnlock()
return lc.channelState.OurBalance
}
// TheirBalance...
func (lc *LightningChannel) TheirBalance() btcutil.Amount {
lc.stateMtx.RLock()
defer lc.stateMtx.RUnlock()
return lc.channelState.TheirBalance
}
// ForceClose...
func (lc *LightningChannel) ForceClose() error {
return nil
}
// RequestPayment...
func (lc *LightningChannel) RequestPayment(amount btcutil.Amount) error {
// Validate amount
return nil
}
// PaymentRequest...
// TODO(roasbeef): serialization (bip 70, QR code, etc)
// * routing handled by upper layer
type PaymentRequest struct {
PaymentPreImage [20]byte
Value btcutil.Amount
}
// createCommitTx...
// TODO(roasbeef): fix inconsistency of 32 vs 20 byte revocation hashes everywhere...
func createCommitTx(fundingOutput *wire.TxIn, selfKey, theirKey *btcec.PublicKey,
revokeHash []byte, csvTimeout uint32, amountToSelf,
amountToThem btcutil.Amount) (*wire.MsgTx, error) {
// First, we create the script for the delayed "pay-to-self" output.
ourRedeemScript, err := commitScriptToSelf(csvTimeout, selfKey, theirKey,
revokeHash)
if err != nil {
return nil, err
}
payToUsScriptHash, err := scriptHashPkScript(ourRedeemScript)
if err != nil {
return nil, err
}
// Next, we create the script paying to them. This is just a regular
// P2PKH-like output, without any added CSV delay. However, we instead
// use P2SH.
theirRedeemScript, err := commitScriptUnencumbered(theirKey)
if err != nil {
return nil, err
}
payToThemScriptHash, err := scriptHashPkScript(theirRedeemScript)
if err != nil {
return nil, err
}
// Now that both output scripts have been created, we can finally create
// the transaction itself. We use a transaction version of 2 since CSV
// will fail unless the tx version is >= 2.
commitTx := wire.NewMsgTx()
commitTx.Version = 2
commitTx.AddTxIn(fundingOutput)
commitTx.AddTxOut(wire.NewTxOut(int64(amountToSelf), payToUsScriptHash))
commitTx.AddTxOut(wire.NewTxOut(int64(amountToThem), payToThemScriptHash))
return commitTx, nil
}