diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 93f369258..9008894f7 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -1316,6 +1316,15 @@ func (c *ChannelArbitrator) checkCommitChainActions(height uint32, // either learn of it eventually from the outgoing HTLC, or the sender // will timeout the HTLC. for _, htlc := range htlcs.incomingHTLCs { + // If the HTLC is dust, there is no action to be taken. + if htlc.OutputIndex < 0 { + log.Debugf("ChannelArbitrator(%v): no resolution "+ + "needed for incoming dust htlc=%x", + c.cfg.ChanPoint, htlc.RHash[:]) + + continue + } + log.Tracef("ChannelArbitrator(%v): watching chain to decide "+ "action for incoming htlc=%x", c.cfg.ChanPoint, htlc.RHash[:]) @@ -1871,8 +1880,8 @@ func (c *ChannelArbitrator) resolveContract(currentContract ContractResolver) { } log.Errorf("ChannelArbitrator(%v): unable to "+ - "progress resolver: %v", - c.cfg.ChanPoint, err) + "progress %T: %v", + c.cfg.ChanPoint, currentContract, err) return } diff --git a/contractcourt/channel_arbitrator_test.go b/contractcourt/channel_arbitrator_test.go index 65bfde650..7b413669f 100644 --- a/contractcourt/channel_arbitrator_test.go +++ b/contractcourt/channel_arbitrator_test.go @@ -496,6 +496,8 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) { return nil } + chanArb.cfg.PreimageDB = newMockWitnessBeacon() + chanArb.cfg.Registry = &mockRegistry{} if err := chanArb.Start(); err != nil { t.Fatalf("unable to start ChannelArbitrator: %v", err) @@ -512,16 +514,33 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) { chanArb.UpdateContractSignals(signals) // Add HTLC to channel arbitrator. - htlcIndex := uint64(99) htlc := channeldb.HTLC{ Incoming: false, Amt: 10000, - HtlcIndex: htlcIndex, + HtlcIndex: 99, + } + + outgoingDustHtlc := channeldb.HTLC{ + Incoming: false, + Amt: 100, + HtlcIndex: 100, + OutputIndex: -1, + } + + incomingDustHtlc := channeldb.HTLC{ + Incoming: true, + Amt: 105, + HtlcIndex: 101, + OutputIndex: -1, + } + + htlcSet := []channeldb.HTLC{ + htlc, outgoingDustHtlc, incomingDustHtlc, } htlcUpdates <- &ContractUpdate{ HtlcKey: LocalHtlcSet, - Htlcs: []channeldb.HTLC{htlc}, + Htlcs: htlcSet, } errChan := make(chan error, 1) @@ -607,7 +626,7 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) { CommitSet: CommitSet{ ConfCommitKey: &LocalHtlcSet, HtlcSets: map[HtlcSetKey][]channeldb.HTLC{ - LocalHtlcSet: {htlc}, + LocalHtlcSet: htlcSet, }, }, } @@ -617,6 +636,22 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) { StateWaitingFullResolution, ) + // We expect an immediate resolution message for the outgoing dust htlc. + // It is not resolvable on-chain. + select { + case msgs := <-resolutions: + if len(msgs) != 1 { + t.Fatalf("expected 1 message, instead got %v", len(msgs)) + } + + if msgs[0].HtlcIndex != outgoingDustHtlc.HtlcIndex { + t.Fatalf("wrong htlc index: expected %v, got %v", + outgoingDustHtlc.HtlcIndex, msgs[0].HtlcIndex) + } + case <-time.After(5 * time.Second): + t.Fatalf("resolution msgs not sent") + } + // htlcOutgoingContestResolver is now active and waiting for the HTLC to // expire. It should not yet have passed it on for incubation. select { @@ -649,9 +684,9 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) { t.Fatalf("expected 1 message, instead got %v", len(msgs)) } - if msgs[0].HtlcIndex != htlcIndex { + if msgs[0].HtlcIndex != htlc.HtlcIndex { t.Fatalf("wrong htlc index: expected %v, got %v", - htlcIndex, msgs[0].HtlcIndex) + htlc.HtlcIndex, msgs[0].HtlcIndex) } case <-time.After(5 * time.Second): t.Fatalf("resolution msgs not sent")