diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 4df8364fb..8130de0a8 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -1132,19 +1132,27 @@ func (c *ChannelArbitrator) stateStep( log.Infof("ChannelArbitrator(%v): still awaiting contract "+ "resolution", c.cfg.ChanPoint) - numUnresolved, err := c.log.FetchUnresolvedContracts() + unresolved, err := c.log.FetchUnresolvedContracts() if err != nil { return StateError, closeTx, err } - // If we still have unresolved contracts, then we'll stay alive - // to oversee their resolution. - if len(numUnresolved) != 0 { - nextState = StateWaitingFullResolution + // If we have no unresolved contracts, then we can move to the + // final state. + if len(unresolved) == 0 { + nextState = StateFullyResolved break } - nextState = StateFullyResolved + // Otherwise we still have unresolved contracts, then we'll + // stay alive to oversee their resolution. + nextState = StateWaitingFullResolution + + // Add debug logs. + for _, r := range unresolved { + log.Debugf("ChannelArbitrator(%v): still have "+ + "unresolved contract: %T", c.cfg.ChanPoint, r) + } // If we start as fully resolved, then we'll end as fully resolved. case StateFullyResolved: diff --git a/contractcourt/htlc_timeout_resolver.go b/contractcourt/htlc_timeout_resolver.go index 06941da04..095e538e4 100644 --- a/contractcourt/htlc_timeout_resolver.go +++ b/contractcourt/htlc_timeout_resolver.go @@ -822,6 +822,12 @@ func (h *htlcTimeoutResolver) consumeSpendEvents(resultChan chan *spendResult, // Create a result chan to hold the results. result := &spendResult{} + // hasMempoolSpend is a flag that indicates whether we have found a + // preimage spend from the mempool. This is used to determine whether + // to checkpoint the resolver or not when later we found the + // corresponding block spend. + hasMempoolSpent := false + // Wait for a spend event to arrive. for { select { @@ -846,8 +852,26 @@ func (h *htlcTimeoutResolver) consumeSpendEvents(resultChan chan *spendResult, result.spend = spendDetail - // Once confirmed, persist the state on disk. - result.err = h.checkPointSecondLevelTx() + // Once confirmed, persist the state on disk if + // we haven't seen the output's spending tx in + // mempool before. + // + // NOTE: we don't checkpoint the resolver if + // it's spending tx has already been found in + // mempool - the resolver will take care of the + // checkpoint in its `claimCleanUp`. If we do + // checkpoint here, however, we'd create a new + // record in db for the same htlc resolver + // which won't be cleaned up later, resulting + // the channel to stay in unresolved state. + // + // TODO(yy): when fee bumper is implemented, we + // need to further check whether this is a + // preimage spend. Also need to refactor here + // to save us some indentation. + if !hasMempoolSpent { + result.err = h.checkPointSecondLevelTx() + } } // Send the result and exit the loop. @@ -894,6 +918,10 @@ func (h *htlcTimeoutResolver) consumeSpendEvents(resultChan chan *spendResult, result.spend = spendDetail resultChan <- result + // Set the hasMempoolSpent flag to true so we won't + // checkpoint the resolver again in db. + hasMempoolSpent = true + continue // If the resolver exits, we exit the goroutine.