Skip to content

Commit 8ed8665

Browse files
committed
contractcourt: Cancel dust htlcs prematurely
We will now cancel dust htlcs on the local/remote commits after we decided to go onchain. This can be done because dust cannot be enforced onchain and therefore there is no way to also reveil the preimage onchain.
1 parent 20dc7f2 commit 8ed8665

File tree

2 files changed

+75
-45
lines changed

2 files changed

+75
-45
lines changed

contractcourt/channel_arbitrator.go

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -932,21 +932,36 @@ func (c *ChannelArbitrator) stateStep(
932932
// arbitrating for. If a commitment has confirmed, then we'll
933933
// use the set snapshot from the chain, otherwise we'll use our
934934
// current set.
935-
var htlcs map[HtlcSetKey]htlcSet
936-
if confCommitSet != nil {
937-
htlcs = confCommitSet.toActiveHTLCSets()
938-
} else {
935+
var (
936+
chainActions ChainActionMap
937+
err error
938+
)
939+
940+
// Normally if we force close the channel locally we will have
941+
// no confCommitSet. However when the remote commitment confirms
942+
// without us ever broadcasting our local commitment we need to
943+
// make sure we cancel all upstream HTLCs for outgoing dust
944+
// HTLCs as well hence we need to fetch the chain actions here
945+
// as well.
946+
if confCommitSet == nil {
939947
// Update the set of activeHTLCs so
940948
// checkLocalChainActions has an up-to-date view of the
941949
// commitments.
942950
c.updateActiveHTLCs()
943-
htlcs = c.activeHTLCs
944-
}
945-
chainActions, err := c.checkLocalChainActions(
946-
triggerHeight, trigger, htlcs, false,
947-
)
948-
if err != nil {
949-
return StateDefault, nil, err
951+
htlcs := c.activeHTLCs
952+
chainActions, err = c.checkLocalChainActions(
953+
triggerHeight, trigger, htlcs, false,
954+
)
955+
if err != nil {
956+
return StateDefault, nil, err
957+
}
958+
} else {
959+
chainActions, err = c.constructChainActions(
960+
confCommitSet, triggerHeight, trigger,
961+
)
962+
if err != nil {
963+
return StateDefault, nil, err
964+
}
950965
}
951966

952967
// If there are no actions to be made, then we'll remain in the
@@ -964,6 +979,25 @@ func (c *ChannelArbitrator) stateStep(
964979
log.Tracef("ChannelArbitrator(%v): logging chain_actions=%v",
965980
c.cfg.ChanPoint, lnutils.SpewLogClosure(chainActions))
966981

982+
// Cancel upstream HTLCs for all outgoing dust HTLCs available
983+
// either on the local or the remote/remote pending commitment
984+
// transaction.
985+
dustHTLCs := chainActions[HtlcFailDustAction]
986+
if len(dustHTLCs) > 0 {
987+
log.Debugf("ChannelArbitrator(%v): canceling %v dust "+
988+
"HTLCs backwards", c.cfg.ChanPoint,
989+
len(dustHTLCs))
990+
991+
getIdx := func(htlc channeldb.HTLC) uint64 {
992+
return htlc.HtlcIndex
993+
}
994+
dustHTLCSet := fn.NewSet(fn.Map(getIdx, dustHTLCs)...)
995+
err = c.abandonForwards(dustHTLCSet)
996+
if err != nil {
997+
return StateError, closeTx, err
998+
}
999+
}
1000+
9671001
// Depending on the type of trigger, we'll either "tunnel"
9681002
// through to a farther state, or just proceed linearly to the
9691003
// next state.
@@ -1214,18 +1248,19 @@ func (c *ChannelArbitrator) stateStep(
12141248
return StateError, closeTx, err
12151249
}
12161250

1217-
// In case its a breach transaction we fail back all outgoing
1218-
// HTLCs on the remote commitment set.
1251+
// In case its a breach transaction we fail back all upstream
1252+
// HTLCs for their corresponding outgoing HTLCs on the remote
1253+
// commitment set (remote and remote pending set).
12191254
if contractResolutions.BreachResolution != nil {
12201255
// cancelBreachedHTLCs is a set which holds HTLCs whose
12211256
// corresponding incoming HTLCs will be failed back
12221257
// because the peer broadcasted an old state.
12231258
cancelBreachedHTLCs := fn.NewSet[uint64]()
12241259

1225-
// We'll use the CommitSet, we'll fail back all outgoing
1226-
// HTLC's that exist on either of the remote
1227-
// commitments. The map is used to deduplicate any
1228-
// shared HTLC's.
1260+
// We'll use the CommitSet, we'll fail back all
1261+
// upstream HTLCs for their corresponding outgoing
1262+
// HTLC that exist on either of the remote commitments.
1263+
// The map is used to deduplicate any shared HTLC's.
12291264
for htlcSetKey, htlcs := range confCommitSet.HtlcSets {
12301265
if !htlcSetKey.IsRemote {
12311266
continue
@@ -1256,9 +1291,11 @@ func (c *ChannelArbitrator) stateStep(
12561291
return StateError, closeTx, err
12571292
}
12581293

1259-
// We can fail the upstream HTLC for an outgoing
1260-
// dangling and dust HTLC because we can be sure they
1261-
// will not be resolved onchain.
1294+
// We fail the upstream HTLCs for all remote pending
1295+
// outgoing HTLCs as soon as the commitment is
1296+
// confirmed. The upstream HTLCs for outgoing dust
1297+
// HTLCs have already been resolved before we reach
1298+
// this point.
12621299
getIdx := func(htlc channeldb.HTLC) uint64 {
12631300
return htlc.HtlcIndex
12641301
}
@@ -1269,15 +1306,6 @@ func (c *ChannelArbitrator) stateStep(
12691306
if err != nil {
12701307
return StateError, closeTx, err
12711308
}
1272-
1273-
dustHTLCs := fn.NewSet(fn.Map(
1274-
getIdx, htlcActions[HtlcFailDustAction],
1275-
)...)
1276-
1277-
err = c.abandonForwards(dustHTLCs)
1278-
if err != nil {
1279-
return StateError, closeTx, err
1280-
}
12811309
}
12821310

12831311
// Now that we know we'll need to act, we'll process all the

contractcourt/channel_arbitrator_test.go

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,24 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) {
931931
t.Fatalf("no response received")
932932
}
933933

934+
// We expect an immediate resolution message for the outgoing dust htlc.
935+
// It is not resolvable on-chain and it can be canceled back even before
936+
// the commitment transaction confirmed.
937+
select {
938+
case msgs := <-chanArbCtx.resolutions:
939+
if len(msgs) != 1 {
940+
t.Fatalf("expected 1 message, instead got %v",
941+
len(msgs))
942+
}
943+
944+
if msgs[0].HtlcIndex != outgoingDustHtlc.HtlcIndex {
945+
t.Fatalf("wrong htlc index: expected %v, got %v",
946+
outgoingDustHtlc.HtlcIndex, msgs[0].HtlcIndex)
947+
}
948+
case <-time.After(defaultTimeout):
949+
t.Fatalf("resolution msgs not sent")
950+
}
951+
934952
// Now notify about the local force close getting confirmed.
935953
closeTx := &wire.MsgTx{
936954
TxIn: []*wire.TxIn{
@@ -993,22 +1011,6 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) {
9931011
StateWaitingFullResolution,
9941012
)
9951013

996-
// We expect an immediate resolution message for the outgoing dust htlc.
997-
// It is not resolvable on-chain.
998-
select {
999-
case msgs := <-chanArbCtx.resolutions:
1000-
if len(msgs) != 1 {
1001-
t.Fatalf("expected 1 message, instead got %v", len(msgs))
1002-
}
1003-
1004-
if msgs[0].HtlcIndex != outgoingDustHtlc.HtlcIndex {
1005-
t.Fatalf("wrong htlc index: expected %v, got %v",
1006-
outgoingDustHtlc.HtlcIndex, msgs[0].HtlcIndex)
1007-
}
1008-
case <-time.After(defaultTimeout):
1009-
t.Fatalf("resolution msgs not sent")
1010-
}
1011-
10121014
// We'll grab the old notifier here as our resolvers are still holding
10131015
// a reference to this instance, and a new one will be created when we
10141016
// restart the channel arb below.

0 commit comments

Comments
 (0)