From 56dcda0f7ce3b05a565732cc9b5d61a07b3f9229 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 28 Apr 2023 20:31:30 +0800 Subject: [PATCH 1/2] contractcourt: add debug logs for unresolved contracts --- contractcourt/channel_arbitrator.go | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) 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: From 186569c610799aae3f34deccc051a0dceb466c63 Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Fri, 28 Apr 2023 20:23:57 +0800 Subject: [PATCH 2/2] contractcourt: skip checkpoint timeout resolver upon mempool spend Previously when a block spend is found for the outpoint, our htlc timeout resolver will do a checkpoint, which implicitly creates a db record if there isn't one. Now, if the spend is found in mempool, the resolver will be deleted once the contract is resolved. Later on when the spend is found in the block again, the resolver will be created again, but never gets resolved this time. --- contractcourt/htlc_timeout_resolver.go | 32 ++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) 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.