mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-01-19 05:45:21 +01:00
contratcourt: refactor resolving htlc logic
Refactor the part where we are failing back the incoming htlc when the channel of the corresponding outgoing htlc is force closed. We do this because in furture commits we separate the logic when we fail back the incoming htlc (abandonForward). Right now we fail abandon dust forwards and non-dust forwards only when the commitment transaction is confirmed. Later we will move the canceling of the upstream htlc when the commitment transaction is broadcasted instead of waiting until the commitment tx is confirmed. The reason for that is that dust cannot be enforced onchain anyways so there is no reason to wait.
This commit is contained in:
parent
98a270bd30
commit
8890f83bde
@ -1204,12 +1204,78 @@ func (c *ChannelArbitrator) stateStep(
|
||||
break
|
||||
}
|
||||
|
||||
// First, we'll reconstruct a fresh set of chain actions as the
|
||||
// set of actions we need to act on may differ based on if it
|
||||
// was our commitment, or they're commitment that hit the chain.
|
||||
htlcActions, err := c.constructChainActions(
|
||||
confCommitSet, triggerHeight, trigger,
|
||||
)
|
||||
if err != nil {
|
||||
return StateError, closeTx, err
|
||||
}
|
||||
|
||||
// In case its a breach transaction we fail back all outgoing
|
||||
// HTLCs on the remote commitment set.
|
||||
if contractResolutions.BreachResolution != nil {
|
||||
// cancelBreachedHTLCs is a set which holds HTLCs whose
|
||||
// corresponding incoming HTLCs will be failed back
|
||||
// because the peer broadcasted an old state.
|
||||
cancelBreachedHTLCs := fn.NewSet[uint64]()
|
||||
|
||||
// We'll use the CommitSet, we'll fail back all outgoing
|
||||
// HTLC's that exist on either of the remote
|
||||
// commitments. The map is used to deduplicate any
|
||||
// shared HTLC's.
|
||||
for htlcSetKey, htlcs := range confCommitSet.HtlcSets {
|
||||
if !htlcSetKey.IsRemote {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, htlc := range htlcs {
|
||||
// Only outgoing HTLCs have a
|
||||
// corresponding incoming HTLC.
|
||||
if htlc.Incoming {
|
||||
continue
|
||||
}
|
||||
|
||||
cancelBreachedHTLCs.Add(htlc.HtlcIndex)
|
||||
}
|
||||
}
|
||||
|
||||
err := c.abandonForwards(cancelBreachedHTLCs)
|
||||
if err != nil {
|
||||
return StateError, closeTx, err
|
||||
}
|
||||
} else {
|
||||
// If it's not a breach, we resolve all incoming dust
|
||||
// HTLCs immediately after the commitment is confirmed.
|
||||
err = c.failIncomingDust(
|
||||
htlcActions[HtlcIncomingDustFinalAction],
|
||||
)
|
||||
if err != nil {
|
||||
return StateError, closeTx, err
|
||||
}
|
||||
|
||||
// If we can fail an HTLC immediately (an outgoing HTLC
|
||||
// with no contract and it was not canceled before),
|
||||
// then we'll assemble an HTLC fail packet to send.
|
||||
getIdx := func(htlc channeldb.HTLC) uint64 {
|
||||
return htlc.HtlcIndex
|
||||
}
|
||||
remoteDangling := fn.NewSet(fn.Map(
|
||||
getIdx, htlcActions[HtlcFailNowAction],
|
||||
)...)
|
||||
err := c.abandonForwards(remoteDangling)
|
||||
if err != nil {
|
||||
return StateError, closeTx, err
|
||||
}
|
||||
}
|
||||
|
||||
// Now that we know we'll need to act, we'll process all the
|
||||
// resolvers, then create the structures we need to resolve all
|
||||
// outstanding contracts.
|
||||
resolvers, pktsToSend, err := c.prepContractResolutions(
|
||||
contractResolutions, triggerHeight, trigger,
|
||||
confCommitSet,
|
||||
resolvers, err := c.prepContractResolutions(
|
||||
contractResolutions, triggerHeight, htlcActions,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("ChannelArbitrator(%v): unable to "+
|
||||
@ -1217,20 +1283,6 @@ func (c *ChannelArbitrator) stateStep(
|
||||
return StateError, closeTx, err
|
||||
}
|
||||
|
||||
// With the commitment broadcast, we'll then send over all
|
||||
// messages we can send immediately.
|
||||
if len(pktsToSend) != 0 {
|
||||
log.Debugf("ChannelArbitrator(%v): sending "+
|
||||
"resolution message=%v", c.cfg.ChanPoint,
|
||||
lnutils.SpewLogClosure(pktsToSend))
|
||||
|
||||
err := c.cfg.DeliverResolutionMsg(pktsToSend...)
|
||||
if err != nil {
|
||||
log.Errorf("unable to send pkts: %v", err)
|
||||
return StateError, closeTx, err
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("ChannelArbitrator(%v): inserting %v contract "+
|
||||
"resolvers", c.cfg.ChanPoint, len(resolvers))
|
||||
|
||||
@ -2246,24 +2298,13 @@ func (c *ChannelArbitrator) constructChainActions(confCommitSet *CommitSet,
|
||||
// are properly resolved.
|
||||
func (c *ChannelArbitrator) prepContractResolutions(
|
||||
contractResolutions *ContractResolutions, height uint32,
|
||||
trigger transitionTrigger,
|
||||
confCommitSet *CommitSet) ([]ContractResolver, []ResolutionMsg, error) {
|
||||
|
||||
// First, we'll reconstruct a fresh set of chain actions as the set of
|
||||
// actions we need to act on may differ based on if it was our
|
||||
// commitment, or they're commitment that hit the chain.
|
||||
htlcActions, err := c.constructChainActions(
|
||||
confCommitSet, height, trigger,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
htlcActions ChainActionMap) ([]ContractResolver, error) {
|
||||
|
||||
// We'll also fetch the historical state of this channel, as it should
|
||||
// have been marked as closed by now, and supplement it to each resolver
|
||||
// such that we can properly resolve our pending contracts.
|
||||
var chanState *channeldb.OpenChannel
|
||||
chanState, err = c.cfg.FetchHistoricalChannel()
|
||||
chanState, err := c.cfg.FetchHistoricalChannel()
|
||||
switch {
|
||||
// If we don't find this channel, then it may be the case that it
|
||||
// was closed before we started to retain the final state
|
||||
@ -2275,16 +2316,9 @@ func (c *ChannelArbitrator) prepContractResolutions(
|
||||
"state", c.cfg.ChanPoint)
|
||||
|
||||
case err != nil:
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// There may be a class of HTLC's which we can fail back immediately,
|
||||
// for those we'll prepare a slice of packets to add to our outbox. Any
|
||||
// packets we need to send, will be cancels.
|
||||
var (
|
||||
msgsToSend []ResolutionMsg
|
||||
)
|
||||
|
||||
incomingResolutions := contractResolutions.HtlcResolutions.IncomingHTLCs
|
||||
outgoingResolutions := contractResolutions.HtlcResolutions.OutgoingHTLCs
|
||||
|
||||
@ -2313,7 +2347,6 @@ func (c *ChannelArbitrator) prepContractResolutions(
|
||||
}
|
||||
|
||||
commitHash := contractResolutions.CommitHash
|
||||
failureMsg := &lnwire.FailPermanentChannelFailure{}
|
||||
|
||||
var htlcResolvers []ContractResolver
|
||||
|
||||
@ -2337,37 +2370,7 @@ func (c *ChannelArbitrator) prepContractResolutions(
|
||||
breachResolver := newBreachResolver(resolverCfg)
|
||||
htlcResolvers = append(htlcResolvers, breachResolver)
|
||||
|
||||
// We'll use the CommitSet, we'll fail back all outgoing HTLC's
|
||||
// that exist on either of the remote commitments. The map is
|
||||
// used to deduplicate any shared htlc's.
|
||||
remoteOutgoing := make(map[uint64]channeldb.HTLC)
|
||||
for htlcSetKey, htlcs := range confCommitSet.HtlcSets {
|
||||
if !htlcSetKey.IsRemote {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, htlc := range htlcs {
|
||||
if htlc.Incoming {
|
||||
continue
|
||||
}
|
||||
|
||||
remoteOutgoing[htlc.HtlcIndex] = htlc
|
||||
}
|
||||
}
|
||||
|
||||
// Now we'll loop over the map and create ResolutionMsgs for
|
||||
// each of them.
|
||||
for _, htlc := range remoteOutgoing {
|
||||
failMsg := ResolutionMsg{
|
||||
SourceChan: c.cfg.ShortChanID,
|
||||
HtlcIndex: htlc.HtlcIndex,
|
||||
Failure: failureMsg,
|
||||
}
|
||||
|
||||
msgsToSend = append(msgsToSend, failMsg)
|
||||
}
|
||||
|
||||
return htlcResolvers, msgsToSend, nil
|
||||
return htlcResolvers, nil
|
||||
}
|
||||
|
||||
// For each HTLC, we'll either act immediately, meaning we'll instantly
|
||||
@ -2375,20 +2378,6 @@ func (c *ChannelArbitrator) prepContractResolutions(
|
||||
// confirmed, in which case we'll need an HTLC resolver.
|
||||
for htlcAction, htlcs := range htlcActions {
|
||||
switch htlcAction {
|
||||
|
||||
// If we can fail an HTLC immediately (an outgoing HTLC with no
|
||||
// contract), then we'll assemble an HTLC fail packet to send.
|
||||
case HtlcFailNowAction:
|
||||
for _, htlc := range htlcs {
|
||||
failMsg := ResolutionMsg{
|
||||
SourceChan: c.cfg.ShortChanID,
|
||||
HtlcIndex: htlc.HtlcIndex,
|
||||
Failure: failureMsg,
|
||||
}
|
||||
|
||||
msgsToSend = append(msgsToSend, failMsg)
|
||||
}
|
||||
|
||||
// If we can claim this HTLC, we'll create an HTLC resolver to
|
||||
// claim the HTLC (second-level or directly), then add the pre
|
||||
case HtlcClaimAction:
|
||||
@ -2487,36 +2476,6 @@ func (c *ChannelArbitrator) prepContractResolutions(
|
||||
htlcResolvers = append(htlcResolvers, resolver)
|
||||
}
|
||||
|
||||
// We've lost an htlc because it isn't manifested on the
|
||||
// commitment transaction that closed the channel.
|
||||
case HtlcIncomingDustFinalAction:
|
||||
for _, htlc := range htlcs {
|
||||
htlc := htlc
|
||||
|
||||
key := models.CircuitKey{
|
||||
ChanID: c.cfg.ShortChanID,
|
||||
HtlcID: htlc.HtlcIndex,
|
||||
}
|
||||
|
||||
// Mark this dust htlc as final failed.
|
||||
chainArbCfg := c.cfg.ChainArbitratorConfig
|
||||
err := chainArbCfg.PutFinalHtlcOutcome(
|
||||
key.ChanID, key.HtlcID, false,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Send notification.
|
||||
chainArbCfg.HtlcNotifier.NotifyFinalHtlcEvent(
|
||||
key,
|
||||
channeldb.FinalHtlcInfo{
|
||||
Settled: false,
|
||||
Offchain: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Finally, if this is an outgoing HTLC we've sent, then we'll
|
||||
// launch a resolver to watch for the pre-image (and settle
|
||||
// backwards), or just timeout.
|
||||
@ -2531,9 +2490,11 @@ func (c *ChannelArbitrator) prepContractResolutions(
|
||||
|
||||
resolution, ok := outResolutionMap[htlcOp]
|
||||
if !ok {
|
||||
log.Errorf("ChannelArbitrator(%v) unable to find "+
|
||||
"outgoing resolution: %v",
|
||||
log.Errorf("ChannelArbitrator(%v) "+
|
||||
"unable to find outgoing "+
|
||||
"resolution: %v",
|
||||
c.cfg.ChanPoint, htlcOp)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
@ -2570,7 +2531,7 @@ func (c *ChannelArbitrator) prepContractResolutions(
|
||||
htlcResolvers = append(htlcResolvers, resolver)
|
||||
}
|
||||
|
||||
return htlcResolvers, msgsToSend, nil
|
||||
return htlcResolvers, nil
|
||||
}
|
||||
|
||||
// replaceResolver replaces a in the list of active resolvers. If the resolver
|
||||
@ -3159,3 +3120,81 @@ func (c *ChannelArbitrator) checkLegacyBreach() (ArbitratorState, error) {
|
||||
// This is a modern breach close with resolvers.
|
||||
return StateContractClosed, nil
|
||||
}
|
||||
|
||||
// failIncomingDust resolves the incoming dust HTLCs because they do not have
|
||||
// an output on the commitment transaction and cannot be resolved onchain. We
|
||||
// mark them as failed here.
|
||||
func (c *ChannelArbitrator) failIncomingDust(
|
||||
incomingDustHTLCs []channeldb.HTLC) error {
|
||||
|
||||
for _, htlc := range incomingDustHTLCs {
|
||||
if !htlc.Incoming || htlc.OutputIndex >= 0 {
|
||||
return fmt.Errorf("htlc with index %v is not incoming "+
|
||||
"dust", htlc.OutputIndex)
|
||||
}
|
||||
|
||||
key := models.CircuitKey{
|
||||
ChanID: c.cfg.ShortChanID,
|
||||
HtlcID: htlc.HtlcIndex,
|
||||
}
|
||||
|
||||
// Mark this dust htlc as final failed.
|
||||
chainArbCfg := c.cfg.ChainArbitratorConfig
|
||||
err := chainArbCfg.PutFinalHtlcOutcome(
|
||||
key.ChanID, key.HtlcID, false,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Send notification.
|
||||
chainArbCfg.HtlcNotifier.NotifyFinalHtlcEvent(
|
||||
key,
|
||||
channeldb.FinalHtlcInfo{
|
||||
Settled: false,
|
||||
Offchain: false,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// abandonForwards cancels back the incoming HTLCs for their corresponding
|
||||
// outgoing HTLCs. We use a set here to avoid sending duplicate failure messages
|
||||
// for the same HTLC. This also needs to be done for locally initiated outgoing
|
||||
// HTLCs they are special cased in the switch.
|
||||
func (c *ChannelArbitrator) abandonForwards(htlcs fn.Set[uint64]) error {
|
||||
log.Debugf("ChannelArbitrator(%v): cancelling back %v incoming "+
|
||||
"HTLC(s)", c.cfg.ChanPoint,
|
||||
len(htlcs))
|
||||
|
||||
msgsToSend := make([]ResolutionMsg, 0, len(htlcs))
|
||||
failureMsg := &lnwire.FailPermanentChannelFailure{}
|
||||
|
||||
for idx := range htlcs {
|
||||
failMsg := ResolutionMsg{
|
||||
SourceChan: c.cfg.ShortChanID,
|
||||
HtlcIndex: idx,
|
||||
Failure: failureMsg,
|
||||
}
|
||||
|
||||
msgsToSend = append(msgsToSend, failMsg)
|
||||
}
|
||||
|
||||
// Send the msges to the switch, if there are any.
|
||||
if len(msgsToSend) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debugf("ChannelArbitrator(%v): sending resolution message=%v",
|
||||
c.cfg.ChanPoint, lnutils.SpewLogClosure(msgsToSend))
|
||||
|
||||
err := c.cfg.DeliverResolutionMsg(msgsToSend...)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to send resolution msges to switch: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user