Skip to content

Commit e98a739

Browse files
authored
Merge pull request #6518 from Crypt-iQ/dupestone
htlcswitch: remove synchronous link handoff, special-case keystone err
2 parents ab91f85 + c9a6c80 commit e98a739

File tree

7 files changed

+132
-66
lines changed

7 files changed

+132
-66
lines changed

docs/release-notes/release-notes-0.15.0.md

+9
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,15 @@ compact filters and block/block headers.
119119

120120
* [Fixed crash in MuSig2Combine](https://github.com/lightningnetwork/lnd/pull/6502)
121121

122+
* [Fixed an issue where lnd would end up sending an Error and triggering a force
123+
close.](https://github.com/lightningnetwork/lnd/pull/6518)
124+
125+
## Neutrino
126+
127+
* [New neutrino sub-server](https://github.com/lightningnetwork/lnd/pull/5652)
128+
capable of status checks, adding, disconnecting and listing
129+
peers, fetching compact filters and block/block headers.
130+
122131
* [Added signature length
123132
validation](https://github.com/lightningnetwork/lnd/pull/6314) when calling
124133
`NewSigFromRawSignature`.

htlcswitch/interfaces.go

-4
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,6 @@ type packetHandler interface {
5252
//
5353
// NOTE: This function should block as little as possible.
5454
handleSwitchPacket(*htlcPacket) error
55-
56-
// handleLocalAddPacket handles a locally-initiated UpdateAddHTLC
57-
// packet. It will be processed synchronously.
58-
handleLocalAddPacket(*htlcPacket) error
5955
}
6056

6157
// dustHandler is an interface used exclusively by the Switch to evaluate

htlcswitch/link.go

+52-51
Original file line numberDiff line numberDiff line change
@@ -296,14 +296,6 @@ type ChannelLinkConfig struct {
296296
HtlcNotifier htlcNotifier
297297
}
298298

299-
// localUpdateAddMsg contains a locally initiated htlc and a channel that will
300-
// receive the outcome of the link processing. This channel must be buffered to
301-
// prevent the link from blocking.
302-
type localUpdateAddMsg struct {
303-
pkt *htlcPacket
304-
err chan error
305-
}
306-
307299
// shutdownReq contains an error channel that will be used by the channelLink
308300
// to send an error if shutdown failed. If shutdown succeeded, the channel will
309301
// be closed.
@@ -371,10 +363,6 @@ type channelLink struct {
371363
// by the HTLC switch.
372364
downstream chan *htlcPacket
373365

374-
// localUpdateAdd is a channel to which locally initiated HTLCs are
375-
// sent across.
376-
localUpdateAdd chan *localUpdateAddMsg
377-
378366
// shutdownRequest is a channel that the channelLink will listen on to
379367
// service shutdown requests from ShutdownIfChannelClean calls.
380368
shutdownRequest chan *shutdownReq
@@ -428,7 +416,6 @@ func NewChannelLink(cfg ChannelLinkConfig,
428416
hodlQueue: queue.NewConcurrentQueue(10),
429417
log: build.NewPrefixLog(logPrefix, log),
430418
quit: make(chan struct{}),
431-
localUpdateAdd: make(chan *localUpdateAddMsg),
432419
}
433420
}
434421

@@ -1057,7 +1044,21 @@ func (l *channelLink) htlcManager() {
10571044
// the channel is not pending, otherwise we should have no htlcs to
10581045
// reforward.
10591046
if l.ShortChanID() != hop.Source {
1060-
if err := l.resolveFwdPkgs(); err != nil {
1047+
err := l.resolveFwdPkgs()
1048+
switch err {
1049+
// No error was encountered, success.
1050+
case nil:
1051+
1052+
// If the duplicate keystone error was encountered, we'll fail
1053+
// without sending an Error message to the peer.
1054+
case ErrDuplicateKeystone:
1055+
l.fail(LinkFailureError{code: ErrCircuitError},
1056+
"temporary circuit error: %v", err)
1057+
return
1058+
1059+
// A non-nil error was encountered, send an Error message to
1060+
// the peer.
1061+
default:
10611062
l.fail(LinkFailureError{code: ErrInternalError},
10621063
"unable to resolve fwd pkgs: %v", err)
10631064
return
@@ -1180,10 +1181,6 @@ func (l *channelLink) htlcManager() {
11801181
case pkt := <-l.downstream:
11811182
l.handleDownstreamPkt(pkt)
11821183

1183-
// A message containing a locally initiated add was received.
1184-
case msg := <-l.localUpdateAdd:
1185-
msg.err <- l.handleDownstreamUpdateAdd(msg.pkt)
1186-
11871184
// A message from the connected peer was just received. This
11881185
// indicates that we have a new incoming HTLC, either directly
11891186
// for us, or part of a multi-hop HTLC circuit.
@@ -1195,12 +1192,27 @@ func (l *channelLink) htlcManager() {
11951192
case hodlItem := <-l.hodlQueue.ChanOut():
11961193
htlcResolution := hodlItem.(invoices.HtlcResolution)
11971194
err := l.processHodlQueue(htlcResolution)
1198-
if err != nil {
1195+
switch err {
1196+
// No error, success.
1197+
case nil:
1198+
1199+
// If the duplicate keystone error was encountered,
1200+
// fail back gracefully.
1201+
case ErrDuplicateKeystone:
1202+
l.fail(LinkFailureError{code: ErrCircuitError},
1203+
fmt.Sprintf("process hodl queue: "+
1204+
"temporary circuit error: %v",
1205+
err,
1206+
),
1207+
)
1208+
1209+
// Send an Error message to the peer.
1210+
default:
11991211
l.fail(LinkFailureError{code: ErrInternalError},
1200-
fmt.Sprintf("process hodl queue: %v",
1201-
err.Error()),
1212+
fmt.Sprintf("process hodl queue: "+
1213+
"unable to update commitment:"+
1214+
" %v", err),
12021215
)
1203-
return
12041216
}
12051217

12061218
case req := <-l.shutdownRequest:
@@ -1259,7 +1271,7 @@ loop:
12591271

12601272
// Update the commitment tx.
12611273
if err := l.updateCommitTx(); err != nil {
1262-
return fmt.Errorf("unable to update commitment: %v", err)
1274+
return err
12631275
}
12641276

12651277
return nil
@@ -2081,7 +2093,21 @@ func (l *channelLink) ackDownStreamPackets() error {
20812093
// updateCommitTxOrFail updates the commitment tx and if that fails, it fails
20822094
// the link.
20832095
func (l *channelLink) updateCommitTxOrFail() bool {
2084-
if err := l.updateCommitTx(); err != nil {
2096+
err := l.updateCommitTx()
2097+
switch err {
2098+
// No error encountered, success.
2099+
case nil:
2100+
2101+
// A duplicate keystone error should be resolved and is not fatal, so
2102+
// we won't send an Error message to the peer.
2103+
case ErrDuplicateKeystone:
2104+
l.fail(LinkFailureError{code: ErrCircuitError},
2105+
"temporary circuit error: %v", err)
2106+
return false
2107+
2108+
// Any other error is treated results in an Error message being sent to
2109+
// the peer.
2110+
default:
20852111
l.fail(LinkFailureError{code: ErrInternalError},
20862112
"unable to update commitment: %v", err)
20872113
return false
@@ -2099,6 +2125,8 @@ func (l *channelLink) updateCommitTx() error {
20992125
// sign a commitment state.
21002126
err := l.cfg.Circuits.OpenCircuits(l.keystoneBatch...)
21012127
if err != nil {
2128+
// If ErrDuplicateKeystone is returned, the caller will catch
2129+
// it.
21022130
return err
21032131
}
21042132

@@ -2568,33 +2596,6 @@ func (l *channelLink) handleSwitchPacket(pkt *htlcPacket) error {
25682596
return l.mailBox.AddPacket(pkt)
25692597
}
25702598

2571-
// handleLocalAddPacket handles a locally-initiated UpdateAddHTLC packet. It
2572-
// will be processed synchronously.
2573-
//
2574-
// NOTE: Part of the packetHandler interface.
2575-
func (l *channelLink) handleLocalAddPacket(pkt *htlcPacket) error {
2576-
l.log.Tracef("received switch packet outkey=%v", pkt.outKey())
2577-
2578-
// Create a buffered result channel to prevent the link from blocking.
2579-
errChan := make(chan error, 1)
2580-
2581-
select {
2582-
case l.localUpdateAdd <- &localUpdateAddMsg{
2583-
pkt: pkt,
2584-
err: errChan,
2585-
}:
2586-
case <-l.quit:
2587-
return ErrLinkShuttingDown
2588-
}
2589-
2590-
select {
2591-
case err := <-errChan:
2592-
return err
2593-
case <-l.quit:
2594-
return ErrLinkShuttingDown
2595-
}
2596-
}
2597-
25982599
// HandleChannelUpdate handles the htlc requests as settle/add/fail which sent
25992600
// to us from remote peer we have a channel with.
26002601
//

htlcswitch/linkfailure.go

+7
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ const (
4646
// remote party to force close the channel out on chain now as a
4747
// result.
4848
ErrRecoveryError
49+
50+
// ErrCircuitError indicates a duplicate keystone error was hit in the
51+
// circuit map. This is non-fatal and will resolve itself (usually
52+
// within several minutes).
53+
ErrCircuitError
4954
)
5055

5156
// LinkFailureError encapsulates an error that will make us fail the current
@@ -94,6 +99,8 @@ func (e LinkFailureError) Error() string {
9499
return "invalid revocation"
95100
case ErrRecoveryError:
96101
return "unable to resume channel, recovery required"
102+
case ErrCircuitError:
103+
return "non-fatal circuit map error"
97104
default:
98105
return "unknown error"
99106
}

htlcswitch/mock.go

-5
Original file line numberDiff line numberDiff line change
@@ -729,11 +729,6 @@ func (f *mockChannelLink) handleSwitchPacket(pkt *htlcPacket) error {
729729
return nil
730730
}
731731

732-
func (f *mockChannelLink) handleLocalAddPacket(pkt *htlcPacket) error {
733-
_ = f.mailBox.AddPacket(pkt)
734-
return nil
735-
}
736-
737732
func (f *mockChannelLink) getDustSum(remote bool) lnwire.MilliSatoshi {
738733
return 0
739734
}

htlcswitch/switch.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,7 @@ func (s *Switch) SendHTLC(firstHop lnwire.ShortChannelID, attemptID uint64,
484484
incomingHTLCID: attemptID,
485485
outgoingChanID: firstHop,
486486
htlc: htlc,
487+
amount: htlc.Amount,
487488
}
488489

489490
// Attempt to fetch the target link before creating a circuit so that
@@ -547,10 +548,11 @@ func (s *Switch) SendHTLC(firstHop lnwire.ShortChannelID, attemptID uint64,
547548
return ErrLocalAddFailed
548549
}
549550

550-
// Send packet to link.
551+
// Give the packet to the link's mailbox so that HTLC's are properly
552+
// canceled back if the mailbox timeout elapses.
551553
packet.circuit = circuit
552554

553-
return link.handleLocalAddPacket(packet)
555+
return link.handleSwitchPacket(packet)
554556
}
555557

556558
// UpdateForwardingPolicies sends a message to the switch to update the

htlcswitch/switch_test.go

+60-4
Original file line numberDiff line numberDiff line change
@@ -3600,8 +3600,6 @@ func TestSwitchDustForwarding(t *testing.T) {
36003600

36013601
// We'll test that once the default threshold is exceeded on the
36023602
// Alice -> Bob channel, either side's calls to SendHTLC will fail.
3603-
// This does not rely on the mailbox sum since there's no intermediate
3604-
// hop.
36053603
//
36063604
// Alice will send 357 HTLC's of 700sats. Bob will also send 357 HTLC's
36073605
// of 700sats. If either side attempts to send a dust HTLC, it will
@@ -3630,6 +3628,47 @@ func TestSwitchDustForwarding(t *testing.T) {
36303628
OnionBlob: blob,
36313629
}
36323630

3631+
checkAlmostDust := func(link *channelLink, mbox MailBox,
3632+
remote bool) bool {
3633+
3634+
timeout := time.After(15 * time.Second)
3635+
pollInterval := 300 * time.Millisecond
3636+
expectedDust := 357 * 2 * amt
3637+
3638+
for {
3639+
<-time.After(pollInterval)
3640+
3641+
select {
3642+
case <-timeout:
3643+
return false
3644+
default:
3645+
}
3646+
3647+
linkDust := link.getDustSum(remote)
3648+
localMailDust, remoteMailDust := mbox.DustPackets()
3649+
3650+
totalDust := linkDust
3651+
if remote {
3652+
totalDust += remoteMailDust
3653+
} else {
3654+
totalDust += localMailDust
3655+
}
3656+
3657+
if totalDust == expectedDust {
3658+
break
3659+
}
3660+
}
3661+
3662+
return true
3663+
}
3664+
3665+
// Wait until Bob is almost at the dust threshold.
3666+
bobMbox := n.bobServer.htlcSwitch.mailOrchestrator.GetOrCreateMailBox(
3667+
n.firstBobChannelLink.ChanID(),
3668+
n.firstBobChannelLink.ShortChanID(),
3669+
)
3670+
require.True(t, checkAlmostDust(n.firstBobChannelLink, bobMbox, false))
3671+
36333672
// Assert that the HTLC is failed due to the dust threshold.
36343673
err = n.bobServer.htlcSwitch.SendHTLC(
36353674
aliceBobFirstHop, uint64(357), failingHtlc,
@@ -3723,6 +3762,14 @@ func TestSwitchDustForwarding(t *testing.T) {
37233762
OnionBlob: blob,
37243763
}
37253764

3765+
// Wait until Alice's expected dust for the remote commitment is just
3766+
// under the dust threshold.
3767+
aliceOrch := n.aliceServer.htlcSwitch.mailOrchestrator
3768+
aliceMbox := aliceOrch.GetOrCreateMailBox(
3769+
n.aliceChannelLink.ChanID(), n.aliceChannelLink.ShortChanID(),
3770+
)
3771+
require.True(t, checkAlmostDust(n.aliceChannelLink, aliceMbox, true))
3772+
37263773
err = n.aliceServer.htlcSwitch.SendHTLC(
37273774
n.aliceChannelLink.ShortChanID(), uint64(357),
37283775
aliceMultihopHtlc,
@@ -3792,8 +3839,17 @@ func sendDustHtlcs(t *testing.T, n *threeHopNetwork, alice bool,
37923839
OnionBlob: blob,
37933840
}
37943841

3795-
err = sendingSwitch.SendHTLC(sid, attemptID, htlc)
3796-
require.NoError(t, err)
3842+
for {
3843+
// It may be the case that the dust threshold is hit
3844+
// before all 357*2 HTLC's are sent due to double
3845+
// counting. Get around this by continuing to send
3846+
// until successful.
3847+
err = sendingSwitch.SendHTLC(sid, attemptID, htlc)
3848+
if err == nil {
3849+
break
3850+
}
3851+
}
3852+
37973853
attemptID++
37983854
}
37993855
}

0 commit comments

Comments
 (0)