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.
This commit is contained in:
yyforyongyu 2024-06-24 21:49:21 +08:00
parent 730b605ed4
commit a98763494f
No known key found for this signature in database
GPG Key ID: 9BCD95C4FF296868
3 changed files with 153 additions and 87 deletions

View File

@ -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)

View File

@ -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
}

View File

@ -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.