From a98763494f9d43978609f416797d13366527fd6e Mon Sep 17 00:00:00 2001 From: yyforyongyu Date: Mon, 24 Jun 2024 21:49:21 +0800 Subject: [PATCH] contractcourt: add `Launch` method to anchor/breach resolver We will use this and its following commits to break the original `Resolve` methods into two parts - the first part is moved to a new method `Launch`, which handles sending a sweep request to the sweeper. The second part remains in `Resolve`, which is mainly waiting for a spending tx. Breach resolver currently doesn't do anything in its `Launch` since the sweeping of justice outputs are not handled by the sweeper yet. --- contractcourt/anchor_resolver.go | 209 +++++++++++++++++------------ contractcourt/breach_resolver.go | 21 +++ contractcourt/contract_resolver.go | 10 ++ 3 files changed, 153 insertions(+), 87 deletions(-) diff --git a/contractcourt/anchor_resolver.go b/contractcourt/anchor_resolver.go index b50e061f4..ded3ebd83 100644 --- a/contractcourt/anchor_resolver.go +++ b/contractcourt/anchor_resolver.go @@ -84,8 +84,125 @@ func (c *anchorResolver) ResolverKey() []byte { return nil } -// Resolve offers the anchor output to the sweeper and waits for it to be swept. +// Resolve waits for the output to be swept. +// +// NOTE: Part of the ContractResolver interface. func (c *anchorResolver) Resolve() (ContractResolver, error) { + // If we're already resolved, then we can exit early. + if c.resolved { + c.log.Errorf("already resolved") + return nil, nil + } + + var ( + outcome channeldb.ResolverOutcome + spendTx *chainhash.Hash + ) + + select { + case sweepRes := <-c.sweepResultChan: + switch sweepRes.Err { + // Anchor was swept successfully. + case nil: + sweepTxID := sweepRes.Tx.TxHash() + + spendTx = &sweepTxID + outcome = channeldb.ResolverOutcomeClaimed + + // Anchor was swept by someone else. This is possible after the + // 16 block csv lock. + case sweep.ErrRemoteSpend: + c.log.Warnf("our anchor spent by someone else") + outcome = channeldb.ResolverOutcomeUnclaimed + + // An unexpected error occurred. + default: + c.log.Errorf("unable to sweep anchor: %v", sweepRes.Err) + + return nil, sweepRes.Err + } + + case <-c.quit: + return nil, errResolverShuttingDown + } + + c.log.Infof("resolved in tx %v", spendTx) + + // Update report to reflect that funds are no longer in limbo. + c.reportLock.Lock() + if outcome == channeldb.ResolverOutcomeClaimed { + c.currentReport.RecoveredBalance = c.currentReport.LimboBalance + } + c.currentReport.LimboBalance = 0 + report := c.currentReport.resolverReport( + spendTx, channeldb.ResolverTypeAnchor, outcome, + ) + c.reportLock.Unlock() + + c.resolved = true + return nil, c.PutResolverReport(nil, report) +} + +// Stop signals the resolver to cancel any current resolution processes, and +// suspend. +// +// NOTE: Part of the ContractResolver interface. +func (c *anchorResolver) Stop() { + c.log.Debugf("stopping...") + defer c.log.Debugf("stopped") + + close(c.quit) +} + +// IsResolved returns true if the stored state in the resolve is fully +// resolved. In this case the target output can be forgotten. +// +// NOTE: Part of the ContractResolver interface. +func (c *anchorResolver) IsResolved() bool { + return c.resolved +} + +// SupplementState allows the user of a ContractResolver to supplement it with +// state required for the proper resolution of a contract. +// +// NOTE: Part of the ContractResolver interface. +func (c *anchorResolver) SupplementState(state *channeldb.OpenChannel) { + c.chanType = state.ChanType +} + +// report returns a report on the resolution state of the contract. +func (c *anchorResolver) report() *ContractReport { + c.reportLock.Lock() + defer c.reportLock.Unlock() + + reportCopy := c.currentReport + return &reportCopy +} + +func (c *anchorResolver) Encode(w io.Writer) error { + return errors.New("serialization not supported") +} + +// A compile time assertion to ensure anchorResolver meets the +// ContractResolver interface. +var _ ContractResolver = (*anchorResolver)(nil) + +// Launch offers the anchor output to the sweeper. +func (c *anchorResolver) Launch() error { + if c.launched { + c.log.Tracef("already launched") + return nil + } + + c.log.Debugf("launching resolver...") + c.launched = true + + // If we're already resolved, then we can exit early. + if c.resolved { + c.log.Errorf("already resolved") + return nil + } + // 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 @@ -125,94 +242,12 @@ func (c *anchorResolver) Resolve() (ContractResolver, error) { DeadlineHeight: fn.None[int32](), }, ) + if err != nil { - return nil, err + return err } - var ( - outcome channeldb.ResolverOutcome - spendTx *chainhash.Hash - ) + c.sweepResultChan = resultChan - select { - case sweepRes := <-resultChan: - switch sweepRes.Err { - // Anchor was swept successfully. - case nil: - sweepTxID := sweepRes.Tx.TxHash() - - spendTx = &sweepTxID - outcome = channeldb.ResolverOutcomeClaimed - - // Anchor was swept by someone else. This is possible after the - // 16 block csv lock. - case sweep.ErrRemoteSpend: - c.log.Warnf("our anchor spent by someone else") - outcome = channeldb.ResolverOutcomeUnclaimed - - // An unexpected error occurred. - default: - c.log.Errorf("unable to sweep anchor: %v", sweepRes.Err) - - return nil, sweepRes.Err - } - - case <-c.quit: - return nil, errResolverShuttingDown - } - - // Update report to reflect that funds are no longer in limbo. - c.reportLock.Lock() - if outcome == channeldb.ResolverOutcomeClaimed { - c.currentReport.RecoveredBalance = c.currentReport.LimboBalance - } - c.currentReport.LimboBalance = 0 - report := c.currentReport.resolverReport( - spendTx, channeldb.ResolverTypeAnchor, outcome, - ) - c.reportLock.Unlock() - - c.resolved = true - return nil, c.PutResolverReport(nil, report) + return nil } - -// Stop signals the resolver to cancel any current resolution processes, and -// suspend. -// -// NOTE: Part of the ContractResolver interface. -func (c *anchorResolver) Stop() { - close(c.quit) -} - -// IsResolved returns true if the stored state in the resolve is fully -// resolved. In this case the target output can be forgotten. -// -// NOTE: Part of the ContractResolver interface. -func (c *anchorResolver) IsResolved() bool { - return c.resolved -} - -// SupplementState allows the user of a ContractResolver to supplement it with -// state required for the proper resolution of a contract. -// -// NOTE: Part of the ContractResolver interface. -func (c *anchorResolver) SupplementState(state *channeldb.OpenChannel) { - c.chanType = state.ChanType -} - -// report returns a report on the resolution state of the contract. -func (c *anchorResolver) report() *ContractReport { - c.reportLock.Lock() - defer c.reportLock.Unlock() - - reportCopy := c.currentReport - return &reportCopy -} - -func (c *anchorResolver) Encode(w io.Writer) error { - return errors.New("serialization not supported") -} - -// A compile time assertion to ensure anchorResolver meets the -// ContractResolver interface. -var _ ContractResolver = (*anchorResolver)(nil) diff --git a/contractcourt/breach_resolver.go b/contractcourt/breach_resolver.go index 9a5f4bbe0..1f74924d3 100644 --- a/contractcourt/breach_resolver.go +++ b/contractcourt/breach_resolver.go @@ -47,6 +47,8 @@ func (b *breachResolver) ResolverKey() []byte { // Resolve queries the BreachArbitrator to see if the justice transaction has // been broadcast. // +// NOTE: Part of the ContractResolver interface. +// // TODO(yy): let sweeper handle the breach inputs. func (b *breachResolver) Resolve() (ContractResolver, error) { if !b.subscribed { @@ -83,6 +85,7 @@ func (b *breachResolver) Resolve() (ContractResolver, error) { // Stop signals the breachResolver to stop. func (b *breachResolver) Stop() { + b.log.Debugf("stopping...") close(b.quit) } @@ -123,3 +126,21 @@ func newBreachResolverFromReader(r io.Reader, resCfg ResolverConfig) ( // A compile time assertion to ensure breachResolver meets the ContractResolver // interface. var _ ContractResolver = (*breachResolver)(nil) + +// Launch offers the breach outputs to the sweeper - currently it's a NOOP as +// the outputs here are not offered to the sweeper. +// +// NOTE: Part of the ContractResolver interface. +// +// TODO(yy): implement it once the outputs are offered to the sweeper. +func (b *breachResolver) Launch() error { + if b.launched { + b.log.Tracef("already launched") + return nil + } + + b.log.Debugf("launching resolver...") + b.launched = true + + return nil +} diff --git a/contractcourt/contract_resolver.go b/contractcourt/contract_resolver.go index ff52ce976..814c02ff5 100644 --- a/contractcourt/contract_resolver.go +++ b/contractcourt/contract_resolver.go @@ -10,6 +10,7 @@ import ( "github.com/btcsuite/btclog/v2" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/fn/v2" + "github.com/lightningnetwork/lnd/sweep" ) var ( @@ -109,6 +110,15 @@ type contractResolverKit struct { log btclog.Logger quit chan struct{} + + // sweepResultChan is the result chan returned from calling + // `SweepInput`. It should be mounted to the specific resolver once the + // input has been offered to the sweeper. + sweepResultChan chan sweep.Result + + // launched specifies whether the resolver has been launched. Calling + // `Launch` will be a no-op if this is true. + launched bool } // newContractResolverKit instantiates the mix-in struct.