Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Beat [3/4]: prepare resolvers to handle the blockbeat #9276

Merged
merged 19 commits into from
Dec 12, 2024
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
82d77bb
contractcourt: add verbose logging in resolvers
yyforyongyu Jun 20, 2024
3cb48a0
contractcourt: add spend path helpers in timeout/success resolver
yyforyongyu Nov 13, 2024
10d9544
contractcourt: add sweep senders in `htlcSuccessResolver`
yyforyongyu Nov 14, 2024
693287f
contractcourt: add resolver handlers in `htlcSuccessResolver`
yyforyongyu Nov 14, 2024
fe29961
contractcourt: remove redundant return value in `claimCleanUp`
yyforyongyu Nov 14, 2024
7e0623c
contractcourt: add sweep senders in `htlcTimeoutResolver`
yyforyongyu Nov 14, 2024
066d351
contractcourt: add methods to checkpoint states
yyforyongyu Jul 16, 2024
c108487
contractcourt: add resolve handlers in `htlcTimeoutResolver`
yyforyongyu Jul 16, 2024
d78fd7a
contractcourt: add `Launch` method to anchor/breach resolver
yyforyongyu Jun 24, 2024
629c037
contractcourt: add `Launch` method to commit resolver
yyforyongyu Jun 20, 2024
406f443
contractcourt: add `Launch` method to htlc success resolver
yyforyongyu Jul 15, 2024
dcf7144
contractcourt: add `Launch` method to htlc timeout resolver
yyforyongyu Jul 16, 2024
dfaff63
invoices: exit early when the subscriber chan is nil
yyforyongyu Nov 17, 2024
fe4ab4e
contractcourt: add `Launch` method to incoming contest resolver
yyforyongyu Nov 17, 2024
dbfe5df
contractcourt: add `Launch` method to outgoing contest resolver
yyforyongyu Jun 20, 2024
1c8cbc8
contractcourt: fix concurrent access to `resolved`
yyforyongyu Jul 10, 2024
b6c4dd9
contractcourt: fix concurrent access to `launched`
yyforyongyu Jul 11, 2024
b3cec2c
contractcourt: break `launchResolvers` into two steps
yyforyongyu Jun 25, 2024
7751b1b
contractcourt: offer outgoing htlc one block earlier before its expiry
yyforyongyu Nov 25, 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
137 changes: 81 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 (
ziggie1984 marked this conversation as resolved.
Show resolved Hide resolved
"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,14 @@ 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.
ziggie1984 marked this conversation as resolved.
Show resolved Hide resolved
//
// NOTE: Part of the ContractResolver interface.
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 +97,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 +123,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 +136,7 @@ func (c *anchorResolver) Resolve() (ContractResolver, error) {
)
c.reportLock.Unlock()

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

Expand All @@ -180,15 +145,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 +175,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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should only launch the anchor resolver only in 2 cases:

  • when we relaunchResolvers
  • when we confirmed a Commitment Tx => stateStep

could you elaborate why we launch the Anchor also here: handleBlockbeat

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's what the launched bool is for.

if c.isLaunched() {
c.log.Tracef("already launched")
ziggie1984 marked this conversation as resolved.
Show resolved Hide resolved
return nil
}

c.log.Debugf("launching resolver...")
c.markLaunched()

// 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
}
48 changes: 33 additions & 15 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 @@ -46,6 +44,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 {
Expand All @@ -59,7 +59,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.markResolved()
return nil, b.Checkpoint(b)
}

Expand All @@ -72,8 +72,9 @@ 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.markResolved()
return nil, b.Checkpoint(b)

case <-b.quit:
}

Expand All @@ -82,22 +83,17 @@ 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
}

// SupplementState adds additional state to the breachResolver.
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.IsResolved())
}

// newBreachResolverFromReader attempts to decode an encoded breachResolver
Expand All @@ -110,15 +106,37 @@ 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
}
if resolved {
b.markResolved()
}

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)

// 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.
Roasbeef marked this conversation as resolved.
Show resolved Hide resolved
func (b *breachResolver) Launch() error {
if b.isLaunched() {
b.log.Tracef("already launched")
return nil
}

b.log.Debugf("launching resolver...")
b.markLaunched()

return nil
}
Loading
Loading