Skip to content

Commit 7b29431

Browse files
authored
Merge pull request #9150 from yyforyongyu/fix-stuck-payment
routing+htlcswitch: fix stuck inflight payments
2 parents fbc668c + 58e76b7 commit 7b29431

12 files changed

+725
-379
lines changed

channeldb/mp_payment.go

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ import (
1010

1111
"github.com/btcsuite/btcd/btcec/v2"
1212
"github.com/btcsuite/btcd/wire"
13+
"github.com/davecgh/go-spew/spew"
14+
sphinx "github.com/lightningnetwork/lightning-onion"
1315
"github.com/lightningnetwork/lnd/lntypes"
16+
"github.com/lightningnetwork/lnd/lnutils"
1417
"github.com/lightningnetwork/lnd/lnwire"
1518
"github.com/lightningnetwork/lnd/routing/route"
1619
)
@@ -45,12 +48,19 @@ type HTLCAttemptInfo struct {
4548
// in which the payment's PaymentHash in the PaymentCreationInfo should
4649
// be used.
4750
Hash *lntypes.Hash
51+
52+
// onionBlob is the cached value for onion blob created from the sphinx
53+
// construction.
54+
onionBlob [lnwire.OnionPacketSize]byte
55+
56+
// circuit is the cached value for sphinx circuit.
57+
circuit *sphinx.Circuit
4858
}
4959

5060
// NewHtlcAttempt creates a htlc attempt.
5161
func NewHtlcAttempt(attemptID uint64, sessionKey *btcec.PrivateKey,
5262
route route.Route, attemptTime time.Time,
53-
hash *lntypes.Hash) *HTLCAttempt {
63+
hash *lntypes.Hash) (*HTLCAttempt, error) {
5464

5565
var scratch [btcec.PrivKeyBytesLen]byte
5666
copy(scratch[:], sessionKey.Serialize())
@@ -64,7 +74,11 @@ func NewHtlcAttempt(attemptID uint64, sessionKey *btcec.PrivateKey,
6474
Hash: hash,
6575
}
6676

67-
return &HTLCAttempt{HTLCAttemptInfo: info}
77+
if err := info.attachOnionBlobAndCircuit(); err != nil {
78+
return nil, err
79+
}
80+
81+
return &HTLCAttempt{HTLCAttemptInfo: info}, nil
6882
}
6983

7084
// SessionKey returns the ephemeral key used for a htlc attempt. This function
@@ -79,6 +93,45 @@ func (h *HTLCAttemptInfo) SessionKey() *btcec.PrivateKey {
7993
return h.cachedSessionKey
8094
}
8195

96+
// OnionBlob returns the onion blob created from the sphinx construction.
97+
func (h *HTLCAttemptInfo) OnionBlob() ([lnwire.OnionPacketSize]byte, error) {
98+
var zeroBytes [lnwire.OnionPacketSize]byte
99+
if h.onionBlob == zeroBytes {
100+
if err := h.attachOnionBlobAndCircuit(); err != nil {
101+
return zeroBytes, err
102+
}
103+
}
104+
105+
return h.onionBlob, nil
106+
}
107+
108+
// Circuit returns the sphinx circuit for this attempt.
109+
func (h *HTLCAttemptInfo) Circuit() (*sphinx.Circuit, error) {
110+
if h.circuit == nil {
111+
if err := h.attachOnionBlobAndCircuit(); err != nil {
112+
return nil, err
113+
}
114+
}
115+
116+
return h.circuit, nil
117+
}
118+
119+
// attachOnionBlobAndCircuit creates a sphinx packet and caches the onion blob
120+
// and circuit for this attempt.
121+
func (h *HTLCAttemptInfo) attachOnionBlobAndCircuit() error {
122+
onionBlob, circuit, err := generateSphinxPacket(
123+
&h.Route, h.Hash[:], h.SessionKey(),
124+
)
125+
if err != nil {
126+
return err
127+
}
128+
129+
copy(h.onionBlob[:], onionBlob)
130+
h.circuit = circuit
131+
132+
return nil
133+
}
134+
82135
// HTLCAttempt contains information about a specific HTLC attempt for a given
83136
// payment. It contains the HTLCAttemptInfo used to send the HTLC, as well
84137
// as a timestamp and any known outcome of the attempt.
@@ -629,3 +682,69 @@ func serializeTime(w io.Writer, t time.Time) error {
629682
_, err := w.Write(scratch[:])
630683
return err
631684
}
685+
686+
// generateSphinxPacket generates then encodes a sphinx packet which encodes
687+
// the onion route specified by the passed layer 3 route. The blob returned
688+
// from this function can immediately be included within an HTLC add packet to
689+
// be sent to the first hop within the route.
690+
func generateSphinxPacket(rt *route.Route, paymentHash []byte,
691+
sessionKey *btcec.PrivateKey) ([]byte, *sphinx.Circuit, error) {
692+
693+
// Now that we know we have an actual route, we'll map the route into a
694+
// sphinx payment path which includes per-hop payloads for each hop
695+
// that give each node within the route the necessary information
696+
// (fees, CLTV value, etc.) to properly forward the payment.
697+
sphinxPath, err := rt.ToSphinxPath()
698+
if err != nil {
699+
return nil, nil, err
700+
}
701+
702+
log.Tracef("Constructed per-hop payloads for payment_hash=%x: %v",
703+
paymentHash, lnutils.NewLogClosure(func() string {
704+
path := make(
705+
[]sphinx.OnionHop, sphinxPath.TrueRouteLength(),
706+
)
707+
for i := range path {
708+
hopCopy := sphinxPath[i]
709+
path[i] = hopCopy
710+
}
711+
712+
return spew.Sdump(path)
713+
}),
714+
)
715+
716+
// Next generate the onion routing packet which allows us to perform
717+
// privacy preserving source routing across the network.
718+
sphinxPacket, err := sphinx.NewOnionPacket(
719+
sphinxPath, sessionKey, paymentHash,
720+
sphinx.DeterministicPacketFiller,
721+
)
722+
if err != nil {
723+
return nil, nil, err
724+
}
725+
726+
// Finally, encode Sphinx packet using its wire representation to be
727+
// included within the HTLC add packet.
728+
var onionBlob bytes.Buffer
729+
if err := sphinxPacket.Encode(&onionBlob); err != nil {
730+
return nil, nil, err
731+
}
732+
733+
log.Tracef("Generated sphinx packet: %v",
734+
lnutils.NewLogClosure(func() string {
735+
// We make a copy of the ephemeral key and unset the
736+
// internal curve here in order to keep the logs from
737+
// getting noisy.
738+
key := *sphinxPacket.EphemeralKey
739+
packetCopy := *sphinxPacket
740+
packetCopy.EphemeralKey = &key
741+
742+
return spew.Sdump(packetCopy)
743+
}),
744+
)
745+
746+
return onionBlob.Bytes(), &sphinx.Circuit{
747+
SessionKey: sessionKey,
748+
PaymentPath: sphinxPath.NodeKeys(),
749+
}, nil
750+
}

channeldb/mp_payment_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,22 @@ import (
55
"fmt"
66
"testing"
77

8+
"github.com/btcsuite/btcd/btcec/v2"
89
"github.com/lightningnetwork/lnd/lntypes"
910
"github.com/lightningnetwork/lnd/lnwire"
1011
"github.com/lightningnetwork/lnd/routing/route"
1112
"github.com/stretchr/testify/require"
1213
)
1314

15+
var (
16+
testHash = [32]byte{
17+
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
18+
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
19+
0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9,
20+
0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
21+
}
22+
)
23+
1424
// TestLazySessionKeyDeserialize tests that we can read htlc attempt session
1525
// keys that were previously serialized as a private key as raw bytes.
1626
func TestLazySessionKeyDeserialize(t *testing.T) {
@@ -578,3 +588,15 @@ func makeAttemptInfo(total, amtForwarded int) HTLCAttemptInfo {
578588
},
579589
}
580590
}
591+
592+
// TestEmptyRoutesGenerateSphinxPacket tests that the generateSphinxPacket
593+
// function is able to gracefully handle being passed a nil set of hops for the
594+
// route by the caller.
595+
func TestEmptyRoutesGenerateSphinxPacket(t *testing.T) {
596+
t.Parallel()
597+
598+
sessionKey, _ := btcec.NewPrivateKey()
599+
emptyRoute := &route.Route{}
600+
_, _, err := generateSphinxPacket(emptyRoute, testHash[:], sessionKey)
601+
require.ErrorIs(t, err, route.ErrNoRouteHopsProvided)
602+
}

channeldb/payment_control_test.go

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func genPreimage() ([32]byte, error) {
2828
return preimage, nil
2929
}
3030

31-
func genInfo() (*PaymentCreationInfo, *HTLCAttemptInfo,
31+
func genInfo(t *testing.T) (*PaymentCreationInfo, *HTLCAttemptInfo,
3232
lntypes.Preimage, error) {
3333

3434
preimage, err := genPreimage()
@@ -38,9 +38,14 @@ func genInfo() (*PaymentCreationInfo, *HTLCAttemptInfo,
3838
}
3939

4040
rhash := sha256.Sum256(preimage[:])
41-
attempt := NewHtlcAttempt(
42-
0, priv, *testRoute.Copy(), time.Time{}, nil,
41+
var hash lntypes.Hash
42+
copy(hash[:], rhash[:])
43+
44+
attempt, err := NewHtlcAttempt(
45+
0, priv, *testRoute.Copy(), time.Time{}, &hash,
4346
)
47+
require.NoError(t, err)
48+
4449
return &PaymentCreationInfo{
4550
PaymentIdentifier: rhash,
4651
Value: testRoute.ReceiverAmt(),
@@ -60,7 +65,7 @@ func TestPaymentControlSwitchFail(t *testing.T) {
6065

6166
pControl := NewPaymentControl(db)
6267

63-
info, attempt, preimg, err := genInfo()
68+
info, attempt, preimg, err := genInfo(t)
6469
require.NoError(t, err, "unable to generate htlc message")
6570

6671
// Sends base htlc message which initiate StatusInFlight.
@@ -196,7 +201,7 @@ func TestPaymentControlSwitchDoubleSend(t *testing.T) {
196201

197202
pControl := NewPaymentControl(db)
198203

199-
info, attempt, preimg, err := genInfo()
204+
info, attempt, preimg, err := genInfo(t)
200205
require.NoError(t, err, "unable to generate htlc message")
201206

202207
// Sends base htlc message which initiate base status and move it to
@@ -266,7 +271,7 @@ func TestPaymentControlSuccessesWithoutInFlight(t *testing.T) {
266271

267272
pControl := NewPaymentControl(db)
268273

269-
info, _, preimg, err := genInfo()
274+
info, _, preimg, err := genInfo(t)
270275
require.NoError(t, err, "unable to generate htlc message")
271276

272277
// Attempt to complete the payment should fail.
@@ -291,7 +296,7 @@ func TestPaymentControlFailsWithoutInFlight(t *testing.T) {
291296

292297
pControl := NewPaymentControl(db)
293298

294-
info, _, _, err := genInfo()
299+
info, _, _, err := genInfo(t)
295300
require.NoError(t, err, "unable to generate htlc message")
296301

297302
// Calling Fail should return an error.
@@ -346,7 +351,7 @@ func TestPaymentControlDeleteNonInFlight(t *testing.T) {
346351
var numSuccess, numInflight int
347352

348353
for _, p := range payments {
349-
info, attempt, preimg, err := genInfo()
354+
info, attempt, preimg, err := genInfo(t)
350355
if err != nil {
351356
t.Fatalf("unable to generate htlc message: %v", err)
352357
}
@@ -684,7 +689,7 @@ func TestPaymentControlMultiShard(t *testing.T) {
684689

685690
pControl := NewPaymentControl(db)
686691

687-
info, attempt, preimg, err := genInfo()
692+
info, attempt, preimg, err := genInfo(t)
688693
if err != nil {
689694
t.Fatalf("unable to generate htlc message: %v", err)
690695
}
@@ -948,7 +953,7 @@ func TestPaymentControlMPPRecordValidation(t *testing.T) {
948953

949954
pControl := NewPaymentControl(db)
950955

951-
info, attempt, _, err := genInfo()
956+
info, attempt, _, err := genInfo(t)
952957
require.NoError(t, err, "unable to generate htlc message")
953958

954959
// Init the payment.
@@ -997,7 +1002,7 @@ func TestPaymentControlMPPRecordValidation(t *testing.T) {
9971002

9981003
// Create and init a new payment. This time we'll check that we cannot
9991004
// register an MPP attempt if we already registered a non-MPP one.
1000-
info, attempt, _, err = genInfo()
1005+
info, attempt, _, err = genInfo(t)
10011006
require.NoError(t, err, "unable to generate htlc message")
10021007

10031008
err = pControl.InitPayment(info.PaymentIdentifier, info)
@@ -1271,7 +1276,7 @@ func createTestPayments(t *testing.T, p *PaymentControl, payments []*payment) {
12711276
attemptID := uint64(0)
12721277

12731278
for i := 0; i < len(payments); i++ {
1274-
info, attempt, preimg, err := genInfo()
1279+
info, attempt, preimg, err := genInfo(t)
12751280
require.NoError(t, err, "unable to generate htlc message")
12761281

12771282
// Set the payment id accordingly in the payments slice.

channeldb/payments_test.go

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ var (
6464
TotalAmount: 1234567,
6565
SourcePubKey: vertex,
6666
Hops: []*route.Hop{
67-
testHop3,
6867
testHop2,
6968
testHop1,
7069
},
@@ -98,7 +97,7 @@ var (
9897
}
9998
)
10099

101-
func makeFakeInfo() (*PaymentCreationInfo, *HTLCAttemptInfo) {
100+
func makeFakeInfo(t *testing.T) (*PaymentCreationInfo, *HTLCAttemptInfo) {
102101
var preimg lntypes.Preimage
103102
copy(preimg[:], rev[:])
104103

@@ -113,17 +112,18 @@ func makeFakeInfo() (*PaymentCreationInfo, *HTLCAttemptInfo) {
113112
PaymentRequest: []byte("test"),
114113
}
115114

116-
a := NewHtlcAttempt(
115+
a, err := NewHtlcAttempt(
117116
44, priv, testRoute, time.Unix(100, 0), &hash,
118117
)
118+
require.NoError(t, err)
119119

120120
return c, &a.HTLCAttemptInfo
121121
}
122122

123123
func TestSentPaymentSerialization(t *testing.T) {
124124
t.Parallel()
125125

126-
c, s := makeFakeInfo()
126+
c, s := makeFakeInfo(t)
127127

128128
var b bytes.Buffer
129129
require.NoError(t, serializePaymentCreationInfo(&b, c), "serialize")
@@ -174,6 +174,9 @@ func TestSentPaymentSerialization(t *testing.T) {
174174
require.NoError(t, err, "deserialize")
175175
require.Equal(t, s.Route, newWireInfo.Route)
176176

177+
err = newWireInfo.attachOnionBlobAndCircuit()
178+
require.NoError(t, err)
179+
177180
// Clear routes to allow DeepEqual to compare the remaining fields.
178181
newWireInfo.Route = route.Route{}
179182
s.Route = route.Route{}
@@ -517,7 +520,7 @@ func TestQueryPayments(t *testing.T) {
517520

518521
for i := 0; i < nonDuplicatePayments; i++ {
519522
// Generate a test payment.
520-
info, _, preimg, err := genInfo()
523+
info, _, preimg, err := genInfo(t)
521524
if err != nil {
522525
t.Fatalf("unable to create test "+
523526
"payment: %v", err)
@@ -618,7 +621,7 @@ func TestFetchPaymentWithSequenceNumber(t *testing.T) {
618621
pControl := NewPaymentControl(db)
619622

620623
// Generate a test payment which does not have duplicates.
621-
noDuplicates, _, _, err := genInfo()
624+
noDuplicates, _, _, err := genInfo(t)
622625
require.NoError(t, err)
623626

624627
// Create a new payment entry in the database.
@@ -632,7 +635,7 @@ func TestFetchPaymentWithSequenceNumber(t *testing.T) {
632635
require.NoError(t, err)
633636

634637
// Generate a test payment which we will add duplicates to.
635-
hasDuplicates, _, preimg, err := genInfo()
638+
hasDuplicates, _, preimg, err := genInfo(t)
636639
require.NoError(t, err)
637640

638641
// Create a new payment entry in the database.
@@ -783,7 +786,7 @@ func putDuplicatePayment(t *testing.T, duplicateBucket kvdb.RwBucket,
783786
require.NoError(t, err)
784787

785788
// Generate fake information for the duplicate payment.
786-
info, _, _, err := genInfo()
789+
info, _, _, err := genInfo(t)
787790
require.NoError(t, err)
788791

789792
// Write the payment info to disk under the creation info key. This code

0 commit comments

Comments
 (0)