diff --git a/contractcourt/anchor_resolver.go b/contractcourt/anchor_resolver.go index 0c95b9416..ec90b6ed3 100644 --- a/contractcourt/anchor_resolver.go +++ b/contractcourt/anchor_resolver.go @@ -84,7 +84,7 @@ func (c *anchorResolver) ResolverKey() []byte { } // Resolve offers the anchor output to the sweeper and waits for it to be swept. -func (c *anchorResolver) Resolve() (ContractResolver, error) { +func (c *anchorResolver) Resolve(_ bool) (ContractResolver, error) { // Attempt to update the sweep parameters to the post-confirmation // situation. We don't want to force sweep anymore, because the anchor // lost its special purpose to get the commitment confirmed. It is just diff --git a/contractcourt/breach_resolver.go b/contractcourt/breach_resolver.go index c76d20a6a..740b4471d 100644 --- a/contractcourt/breach_resolver.go +++ b/contractcourt/breach_resolver.go @@ -45,7 +45,9 @@ func (b *breachResolver) ResolverKey() []byte { // Resolve queries the BreachArbitrator to see if the justice transaction has // been broadcast. -func (b *breachResolver) Resolve() (ContractResolver, error) { +// +// TODO(yy): let sweeper handle the breach inputs. +func (b *breachResolver) Resolve(_ bool) (ContractResolver, error) { if !b.subscribed { complete, err := b.SubscribeBreachComplete( &b.ChanPoint, b.replyChan, diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 7f582b333..27c73bcb2 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -787,7 +787,7 @@ func (c *ChannelArbitrator) relaunchResolvers(commitSet *CommitSet, // TODO(roasbeef): this isn't re-launched? } - c.launchResolvers(unresolvedContracts) + c.launchResolvers(unresolvedContracts, true) return nil } @@ -1245,7 +1245,7 @@ func (c *ChannelArbitrator) stateStep( // Finally, we'll launch all the required contract resolvers. // Once they're all resolved, we're no longer needed. - c.launchResolvers(resolvers) + c.launchResolvers(resolvers, false) nextState = StateWaitingFullResolution @@ -1553,14 +1553,16 @@ func (c *ChannelArbitrator) findCommitmentDeadlineAndValue(heightHint uint32, } // launchResolvers updates the activeResolvers list and starts the resolvers. -func (c *ChannelArbitrator) launchResolvers(resolvers []ContractResolver) { +func (c *ChannelArbitrator) launchResolvers(resolvers []ContractResolver, + immediate bool) { + c.activeResolversLock.Lock() defer c.activeResolversLock.Unlock() c.activeResolvers = resolvers for _, contract := range resolvers { c.wg.Add(1) - go c.resolveContract(contract) + go c.resolveContract(contract, immediate) } } @@ -2573,7 +2575,9 @@ func (c *ChannelArbitrator) replaceResolver(oldResolver, // contracts. // // NOTE: This MUST be run as a goroutine. -func (c *ChannelArbitrator) resolveContract(currentContract ContractResolver) { +func (c *ChannelArbitrator) resolveContract(currentContract ContractResolver, + immediate bool) { + defer c.wg.Done() log.Debugf("ChannelArbitrator(%v): attempting to resolve %T", @@ -2594,7 +2598,7 @@ func (c *ChannelArbitrator) resolveContract(currentContract ContractResolver) { default: // Otherwise, we'll attempt to resolve the current // contract. - nextContract, err := currentContract.Resolve() + nextContract, err := currentContract.Resolve(immediate) if err != nil { if err == errResolverShuttingDown { return diff --git a/contractcourt/commit_sweep_resolver.go b/contractcourt/commit_sweep_resolver.go index 507e8bb76..296ea38e5 100644 --- a/contractcourt/commit_sweep_resolver.go +++ b/contractcourt/commit_sweep_resolver.go @@ -184,7 +184,9 @@ func (c *commitSweepResolver) getCommitTxConfHeight() (uint32, error) { // returned. // // NOTE: This function MUST be run as a goroutine. -func (c *commitSweepResolver) Resolve() (ContractResolver, error) { +// +//nolint:funlen +func (c *commitSweepResolver) Resolve(_ bool) (ContractResolver, error) { // If we're already resolved, then we can exit early. if c.resolved { return nil, nil diff --git a/contractcourt/commit_sweep_resolver_test.go b/contractcourt/commit_sweep_resolver_test.go index 2b63384dd..bf6f70cbc 100644 --- a/contractcourt/commit_sweep_resolver_test.go +++ b/contractcourt/commit_sweep_resolver_test.go @@ -83,7 +83,7 @@ func (i *commitSweepResolverTestContext) resolve() { // Start resolver. i.resolverResultChan = make(chan resolveResult, 1) go func() { - nextResolver, err := i.resolver.Resolve() + nextResolver, err := i.resolver.Resolve(false) i.resolverResultChan <- resolveResult{ nextResolver: nextResolver, err: err, diff --git a/contractcourt/contract_resolver.go b/contractcourt/contract_resolver.go index 36495ea52..5acf80064 100644 --- a/contractcourt/contract_resolver.go +++ b/contractcourt/contract_resolver.go @@ -43,7 +43,7 @@ type ContractResolver interface { // resolution, then another resolve is returned. // // NOTE: This function MUST be run as a goroutine. - Resolve() (ContractResolver, error) + Resolve(immediate bool) (ContractResolver, error) // SupplementState allows the user of a ContractResolver to supplement // it with state required for the proper resolution of a contract. diff --git a/contractcourt/htlc_incoming_contest_resolver.go b/contractcourt/htlc_incoming_contest_resolver.go index b64826bdc..e73e3e45b 100644 --- a/contractcourt/htlc_incoming_contest_resolver.go +++ b/contractcourt/htlc_incoming_contest_resolver.go @@ -90,7 +90,9 @@ func (h *htlcIncomingContestResolver) processFinalHtlcFail() error { // as we have no remaining actions left at our disposal. // // NOTE: Part of the ContractResolver interface. -func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) { +func (h *htlcIncomingContestResolver) Resolve( + _ bool) (ContractResolver, error) { + // If we're already full resolved, then we don't have anything further // to do. if h.resolved { diff --git a/contractcourt/htlc_incoming_contest_resolver_test.go b/contractcourt/htlc_incoming_contest_resolver_test.go index 34a672706..a87b1991c 100644 --- a/contractcourt/htlc_incoming_contest_resolver_test.go +++ b/contractcourt/htlc_incoming_contest_resolver_test.go @@ -396,7 +396,7 @@ func (i *incomingResolverTestContext) resolve() { i.resolveErr = make(chan error, 1) go func() { var err error - i.nextResolver, err = i.resolver.Resolve() + i.nextResolver, err = i.resolver.Resolve(false) i.resolveErr <- err }() diff --git a/contractcourt/htlc_outgoing_contest_resolver.go b/contractcourt/htlc_outgoing_contest_resolver.go index 874d26ab9..2466544c9 100644 --- a/contractcourt/htlc_outgoing_contest_resolver.go +++ b/contractcourt/htlc_outgoing_contest_resolver.go @@ -49,7 +49,9 @@ func newOutgoingContestResolver(res lnwallet.OutgoingHtlcResolution, // When either of these two things happens, we'll create a new resolver which // is able to handle the final resolution of the contract. We're only the pivot // point. -func (h *htlcOutgoingContestResolver) Resolve() (ContractResolver, error) { +func (h *htlcOutgoingContestResolver) Resolve( + _ bool) (ContractResolver, error) { + // If we're already full resolved, then we don't have anything further // to do. if h.resolved { diff --git a/contractcourt/htlc_outgoing_contest_resolver_test.go b/contractcourt/htlc_outgoing_contest_resolver_test.go index e4a3aaee0..f67c34ff4 100644 --- a/contractcourt/htlc_outgoing_contest_resolver_test.go +++ b/contractcourt/htlc_outgoing_contest_resolver_test.go @@ -209,7 +209,7 @@ func (i *outgoingResolverTestContext) resolve() { // Start resolver. i.resolverResultChan = make(chan resolveResult, 1) go func() { - nextResolver, err := i.resolver.Resolve() + nextResolver, err := i.resolver.Resolve(false) i.resolverResultChan <- resolveResult{ nextResolver: nextResolver, err: err, diff --git a/contractcourt/htlc_success_resolver.go b/contractcourt/htlc_success_resolver.go index 213df8e4a..6eee939ea 100644 --- a/contractcourt/htlc_success_resolver.go +++ b/contractcourt/htlc_success_resolver.go @@ -115,7 +115,9 @@ func (h *htlcSuccessResolver) ResolverKey() []byte { // TODO(roasbeef): create multi to batch // // NOTE: Part of the ContractResolver interface. -func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) { +func (h *htlcSuccessResolver) Resolve( + immediate bool) (ContractResolver, error) { + // If we're already resolved, then we can exit early. if h.resolved { return nil, nil @@ -124,12 +126,12 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) { // If we don't have a success transaction, then this means that this is // an output on the remote party's commitment transaction. if h.htlcResolution.SignedSuccessTx == nil { - return h.resolveRemoteCommitOutput() + return h.resolveRemoteCommitOutput(immediate) } // Otherwise this an output on our own commitment, and we must start by // broadcasting the second-level success transaction. - secondLevelOutpoint, err := h.broadcastSuccessTx() + secondLevelOutpoint, err := h.broadcastSuccessTx(immediate) if err != nil { return nil, err } @@ -163,7 +165,9 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) { // broadcasting the second-level success transaction. It returns the ultimate // outpoint of the second-level tx, that we must wait to be spent for the // resolver to be fully resolved. -func (h *htlcSuccessResolver) broadcastSuccessTx() (*wire.OutPoint, error) { +func (h *htlcSuccessResolver) broadcastSuccessTx( + immediate bool) (*wire.OutPoint, error) { + // If we have non-nil SignDetails, this means that have a 2nd level // HTLC transaction that is signed using sighash SINGLE|ANYONECANPAY // (the case for anchor type channels). In this case we can re-sign it @@ -171,7 +175,7 @@ func (h *htlcSuccessResolver) broadcastSuccessTx() (*wire.OutPoint, error) { // the checkpointed outputIncubating field to determine if we already // swept the HTLC output into the second level transaction. if h.htlcResolution.SignDetails != nil { - return h.broadcastReSignedSuccessTx() + return h.broadcastReSignedSuccessTx(immediate) } // Otherwise we'll publish the second-level transaction directly and @@ -221,7 +225,9 @@ func (h *htlcSuccessResolver) broadcastSuccessTx() (*wire.OutPoint, error) { // broadcastReSignedSuccessTx handles the case where we have non-nil // SignDetails, and offers the second level transaction to the Sweeper, that // will re-sign it and attach fees at will. -func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() ( +// +//nolint:funlen +func (h *htlcSuccessResolver) broadcastReSignedSuccessTx(immediate bool) ( *wire.OutPoint, error) { // Keep track of the tx spending the HTLC output on the commitment, as @@ -278,6 +284,7 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() ( sweep.Params{ Budget: budget, DeadlineHeight: deadline, + Immediate: immediate, }, ) if err != nil { @@ -433,7 +440,7 @@ func (h *htlcSuccessResolver) broadcastReSignedSuccessTx() ( // resolveRemoteCommitOutput handles sweeping an HTLC output on the remote // commitment with the preimage. In this case we can sweep the output directly, // and don't have to broadcast a second-level transaction. -func (h *htlcSuccessResolver) resolveRemoteCommitOutput() ( +func (h *htlcSuccessResolver) resolveRemoteCommitOutput(immediate bool) ( ContractResolver, error) { isTaproot := txscript.IsPayToTaproot( @@ -482,6 +489,7 @@ func (h *htlcSuccessResolver) resolveRemoteCommitOutput() ( sweep.Params{ Budget: budget, DeadlineHeight: deadline, + Immediate: immediate, }, ) if err != nil { diff --git a/contractcourt/htlc_success_resolver_test.go b/contractcourt/htlc_success_resolver_test.go index b0ee21f1e..b9182500b 100644 --- a/contractcourt/htlc_success_resolver_test.go +++ b/contractcourt/htlc_success_resolver_test.go @@ -134,7 +134,7 @@ func (i *htlcResolverTestContext) resolve() { // Start resolver. i.resolverResultChan = make(chan resolveResult, 1) go func() { - nextResolver, err := i.resolver.Resolve() + nextResolver, err := i.resolver.Resolve(false) i.resolverResultChan <- resolveResult{ nextResolver: nextResolver, err: err, diff --git a/contractcourt/htlc_timeout_resolver.go b/contractcourt/htlc_timeout_resolver.go index e8f846cd9..62ff83207 100644 --- a/contractcourt/htlc_timeout_resolver.go +++ b/contractcourt/htlc_timeout_resolver.go @@ -418,7 +418,9 @@ func checkSizeAndIndex(witness wire.TxWitness, size, index int) bool { // see a direct sweep via the timeout clause. // // NOTE: Part of the ContractResolver interface. -func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) { +func (h *htlcTimeoutResolver) Resolve( + immediate bool) (ContractResolver, error) { + // If we're already resolved, then we can exit early. if h.resolved { return nil, nil @@ -427,7 +429,7 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) { // Start by spending the HTLC output, either by broadcasting the // second-level timeout transaction, or directly if this is the remote // commitment. - commitSpend, err := h.spendHtlcOutput() + commitSpend, err := h.spendHtlcOutput(immediate) if err != nil { return nil, err } @@ -471,7 +473,7 @@ func (h *htlcTimeoutResolver) Resolve() (ContractResolver, error) { // sweepSecondLevelTx sends a second level timeout transaction to the sweeper. // This transaction uses the SINLGE|ANYONECANPAY flag. -func (h *htlcTimeoutResolver) sweepSecondLevelTx() error { +func (h *htlcTimeoutResolver) sweepSecondLevelTx(immediate bool) error { log.Infof("%T(%x): offering second-layer timeout tx to sweeper: %v", h, h.htlc.RHash[:], spew.Sdump(h.htlcResolution.SignedTimeoutTx)) @@ -529,6 +531,7 @@ func (h *htlcTimeoutResolver) sweepSecondLevelTx() error { sweep.Params{ Budget: budget, DeadlineHeight: h.incomingHTLCExpiryHeight, + Immediate: immediate, }, ) if err != nil { @@ -564,14 +567,16 @@ func (h *htlcTimeoutResolver) sendSecondLevelTxLegacy() error { // used to spend the output into the next stage. If this is the remote // commitment, the output will be swept directly without the timeout // transaction. -func (h *htlcTimeoutResolver) spendHtlcOutput() (*chainntnfs.SpendDetail, error) { +func (h *htlcTimeoutResolver) spendHtlcOutput( + immediate bool) (*chainntnfs.SpendDetail, error) { + switch { // If we have non-nil SignDetails, this means that have a 2nd level // HTLC transaction that is signed using sighash SINGLE|ANYONECANPAY // (the case for anchor type channels). In this case we can re-sign it // and attach fees at will. We let the sweeper handle this job. case h.htlcResolution.SignDetails != nil && !h.outputIncubating: - if err := h.sweepSecondLevelTx(); err != nil { + if err := h.sweepSecondLevelTx(immediate); err != nil { log.Errorf("Sending timeout tx to sweeper: %v", err) return nil, err diff --git a/contractcourt/htlc_timeout_resolver_test.go b/contractcourt/htlc_timeout_resolver_test.go index 12cf63886..c551a6f1c 100644 --- a/contractcourt/htlc_timeout_resolver_test.go +++ b/contractcourt/htlc_timeout_resolver_test.go @@ -375,7 +375,7 @@ func TestHtlcTimeoutResolver(t *testing.T) { go func() { defer wg.Done() - _, err := resolver.Resolve() + _, err := resolver.Resolve(false) if err != nil { resolveErr <- err }