Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
8ea149a
contractcourt: add verbose logging in resolvers
yyforyongyu Jun 20, 2024
5aae4f5
contractcourt: add spend path helpers in timeout/success resolver
yyforyongyu Nov 13, 2024
5df5904
contractcourt: add sweep senders in `htlcSuccessResolver`
yyforyongyu Nov 14, 2024
3e174dd
contractcourt: add resolver handlers in `htlcSuccessResolver`
yyforyongyu Nov 14, 2024
e87666e
contractcourt: remove redundant return value in `claimCleanUp`
yyforyongyu Nov 14, 2024
b562318
contractcourt: add sweep senders in `htlcTimeoutResolver`
yyforyongyu Nov 14, 2024
cafba99
contractcourt: add methods to checkpoint states
yyforyongyu Jul 16, 2024
0a63a71
contractcourt: add resolve handlers in `htlcTimeoutResolver`
yyforyongyu Jul 16, 2024
323aea9
contractcourt: add `Launch` method to anchor/breach resolver
yyforyongyu Jun 24, 2024
cf825d2
contractcourt: add `Launch` method to commit resolver
yyforyongyu Jun 20, 2024
53e5d51
contractcourt: add `Launch` method to htlc success resolver
yyforyongyu Jul 15, 2024
8ddda6d
contractcourt: add `Launch` method to htlc timeout resolver
yyforyongyu Jul 16, 2024
c6a646f
invoices: exit early when the subscriber chan is nil
yyforyongyu Nov 17, 2024
040c476
contractcourt: add `Launch` method to incoming contest resolver
yyforyongyu Nov 17, 2024
421e8a0
contractcourt: add `Launch` method to outgoing contest resolver
yyforyongyu Jun 20, 2024
86e4738
contractcourt: fix concurrent access to `resolved`
yyforyongyu Jul 10, 2024
63d23d1
contractcourt: fix concurrent access to `launched`
yyforyongyu Jul 11, 2024
ac61f95
contractcourt: break `launchResolvers` into two steps
yyforyongyu Jun 25, 2024
f9deab4
contractcourt: fix race access to `c.activeResolvers`
yyforyongyu Jul 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 79 additions & 56 deletions contractcourt/anchor_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package contractcourt

import (
"errors"
"fmt"
"io"
"sync"

Expand All @@ -23,9 +24,6 @@ type anchorResolver struct {
// anchor is the outpoint on the commitment transaction.
anchor wire.OutPoint

// resolved reflects if the contract has been fully resolved or not.
resolved bool

// broadcastHeight is the height that the original contract was
// broadcast to the main-chain at. We'll use this value to bound any
// historical queries to the chain for spends/confirmations.
Expand Down Expand Up @@ -71,7 +69,7 @@ func newAnchorResolver(anchorSignDescriptor input.SignDescriptor,
currentReport: report,
}

r.initLogger(r)
r.initLogger(fmt.Sprintf("%T(%v)", r, r.anchor))

return r
}
Expand All @@ -83,49 +81,12 @@ 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.
func (c *anchorResolver) Resolve() (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
// an output that we want to sweep only if it is economical to do so.
//
// An exclusive group is not necessary anymore, because we know that
// this is the only anchor that can be swept.
//
// We also clear the parent tx information for cpfp, because the
// commitment tx is confirmed.
//
// After a restart or when the remote force closes, the sweeper is not
// yet aware of the anchor. In that case, it will be added as new input
// to the sweeper.
witnessType := input.CommitmentAnchor

// For taproot channels, we need to use the proper witness type.
if c.chanType.IsTaproot() {
witnessType = input.TaprootAnchorSweepSpend
}

anchorInput := input.MakeBaseInput(
&c.anchor, witnessType, &c.anchorSignDescriptor,
c.broadcastHeight, nil,
)

resultChan, err := c.Sweeper.SweepInput(
&anchorInput,
sweep.Params{
// For normal anchor sweeping, the budget is 330 sats.
Budget: btcutil.Amount(
anchorInput.SignDesc().Output.Value,
),

// There's no rush to sweep the anchor, so we use a nil
// deadline here.
DeadlineHeight: fn.None[int32](),
},
)
if err != nil {
return nil, err
// If we're already resolved, then we can exit early.
if c.IsResolved() {
c.log.Errorf("already resolved")
return nil, nil
}

var (
Expand All @@ -134,7 +95,7 @@ func (c *anchorResolver) Resolve() (ContractResolver, error) {
)

select {
case sweepRes := <-resultChan:
case sweepRes := <-c.sweepResultChan:
switch sweepRes.Err {
// Anchor was swept successfully.
case nil:
Expand All @@ -160,6 +121,8 @@ func (c *anchorResolver) Resolve() (ContractResolver, error) {
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 {
Expand All @@ -171,7 +134,7 @@ func (c *anchorResolver) Resolve() (ContractResolver, error) {
)
c.reportLock.Unlock()

c.resolved = true
c.resolved.Store(true)
return nil, c.PutResolverReport(nil, report)
}

Expand All @@ -180,15 +143,10 @@ func (c *anchorResolver) Resolve() (ContractResolver, error) {
//
// NOTE: Part of the ContractResolver interface.
func (c *anchorResolver) Stop() {
close(c.quit)
}
c.log.Debugf("stopping...")
defer c.log.Debugf("stopped")

// 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
close(c.quit)
}

// SupplementState allows the user of a ContractResolver to supplement it with
Expand All @@ -215,3 +173,68 @@ func (c *anchorResolver) Encode(w io.Writer) error {
// 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.Load() {
c.log.Tracef("already launched")
return nil
}

c.log.Debugf("launching resolver...")
c.launched.Store(true)

// If we're already resolved, then we can exit early.
if c.IsResolved() {
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
// an output that we want to sweep only if it is economical to do so.
//
// An exclusive group is not necessary anymore, because we know that
// this is the only anchor that can be swept.
//
// We also clear the parent tx information for cpfp, because the
// commitment tx is confirmed.
//
// After a restart or when the remote force closes, the sweeper is not
// yet aware of the anchor. In that case, it will be added as new input
// to the sweeper.
witnessType := input.CommitmentAnchor

// For taproot channels, we need to use the proper witness type.
if c.chanType.IsTaproot() {
witnessType = input.TaprootAnchorSweepSpend
}

anchorInput := input.MakeBaseInput(
&c.anchor, witnessType, &c.anchorSignDescriptor,
c.broadcastHeight, nil,
)

resultChan, err := c.Sweeper.SweepInput(
&anchorInput,
sweep.Params{
// For normal anchor sweeping, the budget is 330 sats.
Budget: btcutil.Amount(
anchorInput.SignDesc().Output.Value,
),

// There's no rush to sweep the anchor, so we use a nil
// deadline here.
DeadlineHeight: fn.None[int32](),
},
)

if err != nil {
return err
}

c.sweepResultChan = resultChan

return nil
}
34 changes: 24 additions & 10 deletions contractcourt/breach_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package contractcourt

import (
"encoding/binary"
"fmt"
"io"

"github.com/lightningnetwork/lnd/channeldb"
Expand All @@ -11,9 +12,6 @@ import (
// future, this will likely take over the duties the current BreachArbitrator
// has.
type breachResolver struct {
// resolved reflects if the contract has been fully resolved or not.
resolved bool

// subscribed denotes whether or not the breach resolver has subscribed
// to the BreachArbitrator for breach resolution.
subscribed bool
Expand All @@ -32,7 +30,7 @@ func newBreachResolver(resCfg ResolverConfig) *breachResolver {
replyChan: make(chan struct{}),
}

r.initLogger(r)
r.initLogger(fmt.Sprintf("%T(%v)", r, r.ChanPoint))

return r
}
Expand All @@ -59,7 +57,7 @@ func (b *breachResolver) Resolve() (ContractResolver, error) {
// If the breach resolution process is already complete, then
// we can cleanup and checkpoint the resolved state.
if complete {
b.resolved = true
b.resolved.Store(true)
return nil, b.Checkpoint(b)
}

Expand All @@ -72,7 +70,7 @@ func (b *breachResolver) Resolve() (ContractResolver, error) {
// The replyChan has been closed, signalling that the breach
// has been fully resolved. Checkpoint the resolved state and
// exit.
b.resolved = true
b.resolved.Store(true)
return nil, b.Checkpoint(b)
case <-b.quit:
}
Expand All @@ -82,13 +80,14 @@ func (b *breachResolver) Resolve() (ContractResolver, error) {

// Stop signals the breachResolver to stop.
func (b *breachResolver) Stop() {
b.log.Debugf("stopping...")
close(b.quit)
}

// IsResolved returns true if the breachResolver is fully resolved and cleanup
// can occur.
func (b *breachResolver) IsResolved() bool {
return b.resolved
return b.resolved.Load()
}

// SupplementState adds additional state to the breachResolver.
Expand All @@ -97,7 +96,7 @@ func (b *breachResolver) SupplementState(_ *channeldb.OpenChannel) {

// Encode encodes the breachResolver to the passed writer.
func (b *breachResolver) Encode(w io.Writer) error {
return binary.Write(w, endian, b.resolved)
return binary.Write(w, endian, b.resolved.Load())
}

// newBreachResolverFromReader attempts to decode an encoded breachResolver
Expand All @@ -110,15 +109,30 @@ func newBreachResolverFromReader(r io.Reader, resCfg ResolverConfig) (
replyChan: make(chan struct{}),
}

if err := binary.Read(r, endian, &b.resolved); err != nil {
var resolved bool
if err := binary.Read(r, endian, &resolved); err != nil {
return nil, err
}
b.resolved.Store(resolved)

b.initLogger(b)
b.initLogger(fmt.Sprintf("%T(%v)", b, b.ChanPoint))

return b, nil
}

// A compile time assertion to ensure breachResolver meets the ContractResolver
// interface.
var _ ContractResolver = (*breachResolver)(nil)

// TODO(yy): implement it once the outputs are offered to the sweeper.
func (b *breachResolver) Launch() error {
if b.launched.Load() {
b.log.Tracef("already launched")
return nil
}

b.log.Debugf("launching resolver...")
b.launched.Store(true)

return nil
}
Loading