Skip to content

Commit

Permalink
multi: introduce an option for resoltions
Browse files Browse the repository at this point in the history
We don't always need the resolutions in the local force close
summary so we make it an option.
  • Loading branch information
ziggie1984 committed Nov 13, 2024
1 parent 4f6b510 commit a7e3ef6
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 69 deletions.
15 changes: 11 additions & 4 deletions contractcourt/chain_arbitrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,11 +335,10 @@ func (a *arbChannel) NewAnchorResolutions() (*lnwallet.AnchorResolutions,
// ForceCloseChan should force close the contract that this attendant is
// watching over. We'll use this when we decide that we need to go to chain. It
// should in addition tell the switch to remove the corresponding link, such
// that we won't accept any new updates. The returned summary contains all items
// needed to eventually resolve all outputs on chain.
// that we won't accept any new updates.
//
// NOTE: Part of the ArbChannel interface.
func (a *arbChannel) ForceCloseChan() (*lnwallet.LocalForceCloseSummary, error) {
func (a *arbChannel) ForceCloseChan() (*wire.MsgTx, error) {
// First, we mark the channel as borked, this ensure
// that no new state transitions can happen, and also
// that the link won't be loaded into the switch.
Expand Down Expand Up @@ -386,7 +385,15 @@ func (a *arbChannel) ForceCloseChan() (*lnwallet.LocalForceCloseSummary, error)
if err != nil {
return nil, err
}
return chanMachine.ForceClose()

closeSummary, err := chanMachine.ForceClose(
lnwallet.WithSkipResolution(),
)
if err != nil {
return nil, err
}

return closeSummary.CloseTx, nil
}

// newActiveChannelArbitrator creates a new instance of an active channel
Expand Down
25 changes: 19 additions & 6 deletions contractcourt/chain_watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -1174,16 +1174,29 @@ func (c *chainWatcher) dispatchLocalForceClose(
LocalChanConfig: c.cfg.chanState.LocalChanCfg,
}

resolutions, err := forceClose.Resolutions.UnwrapOrErr(
fmt.Errorf("resolutions not found"),
)
if err != nil {
return err
}

// If our commitment output isn't dust or we have active HTLC's on the
// commitment transaction, then we'll populate the balances on the
// close channel summary.
if forceClose.CommitResolution != nil {
closeSummary.SettledBalance = chanSnapshot.LocalBalance.ToSatoshis()
closeSummary.TimeLockedBalance = chanSnapshot.LocalBalance.ToSatoshis()
if resolutions.CommitResolution != nil {
localBalance := chanSnapshot.LocalBalance.ToSatoshis()
closeSummary.SettledBalance = localBalance
closeSummary.TimeLockedBalance = localBalance
}
for _, htlc := range forceClose.HtlcResolutions.OutgoingHTLCs {
htlcValue := btcutil.Amount(htlc.SweepSignDesc.Output.Value)
closeSummary.TimeLockedBalance += htlcValue

if resolutions.HtlcResolutions != nil {
for _, htlc := range resolutions.HtlcResolutions.OutgoingHTLCs {
htlcValue := btcutil.Amount(
htlc.SweepSignDesc.Output.Value,
)
closeSummary.TimeLockedBalance += htlcValue
}
}

// Attempt to add a channel sync message to the close summary.
Expand Down
12 changes: 10 additions & 2 deletions contractcourt/chain_watcher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,14 +504,22 @@ func TestChainWatcherLocalForceCloseDetect(t *testing.T) {
// outputs.
select {
case summary := <-chanEvents.LocalUnilateralClosure:
resOpt := summary.LocalForceCloseSummary.Resolutions
resolutions, err := resOpt.UnwrapOrErr(
fmt.Errorf("resolutions not found"),
)
if err != nil {
t.Fatalf("unable to get resolutions: %v", err)
}

// Make sure we correctly extracted the commit
// resolution if we had a local output.
if remoteOutputOnly {
if summary.CommitResolution != nil {
if resolutions.CommitResolution != nil {
t.Fatalf("expected no commit resolution")
}
} else {
if summary.CommitResolution == nil {
if resolutions.CommitResolution == nil {
t.Fatalf("expected commit resolution")
}
}
Expand Down
37 changes: 30 additions & 7 deletions contractcourt/channel_arbitrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ type ArbChannel interface {
// corresponding link, such that we won't accept any new updates. The
// returned summary contains all items needed to eventually resolve all
// outputs on chain.
ForceCloseChan() (*lnwallet.LocalForceCloseSummary, error)
ForceCloseChan() (*wire.MsgTx, error)

// NewAnchorResolutions returns the anchor resolutions for currently
// valid commitment transactions.
Expand Down Expand Up @@ -1098,7 +1098,7 @@ func (c *ChannelArbitrator) stateStep(
// We'll tell the switch that it should remove the link for
// this channel, in addition to fetching the force close
// summary needed to close this channel on chain.
closeSummary, err := c.cfg.Channel.ForceCloseChan()
closeTx, err := c.cfg.Channel.ForceCloseChan()
if err != nil {
log.Errorf("ChannelArbitrator(%v): unable to "+
"force close: %v", c.cfg.ChanPoint, err)
Expand All @@ -1118,7 +1118,6 @@ func (c *ChannelArbitrator) stateStep(

return StateError, closeTx, err
}
closeTx = closeSummary.CloseTx

// Before publishing the transaction, we store it to the
// database, such that we can re-publish later in case it
Expand Down Expand Up @@ -2812,11 +2811,35 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) {
}
closeTx := closeInfo.CloseTx

resolutions, err := closeInfo.Resolutions.UnwrapOrErr(
fmt.Errorf("resolutions not found"),
)
if err != nil {
log.Errorf("ChannelArbitrator(%v): unable to "+
"get resolutions: %v", c.cfg.ChanPoint,
err)

return
}

// We make sure that the htlc resolutions are present
// otherwise we would panic dereferencing the pointer.
//
// TODO(ziggie): Refactor ContractResolutions to use
// options.
if resolutions.HtlcResolutions == nil {
log.Errorf("ChannelArbitrator(%v): htlc "+
"resolutions not found",
c.cfg.ChanPoint)

return
}

contractRes := &ContractResolutions{
CommitHash: closeTx.TxHash(),
CommitResolution: closeInfo.CommitResolution,
HtlcResolutions: *closeInfo.HtlcResolutions,
AnchorResolution: closeInfo.AnchorResolution,
CommitResolution: resolutions.CommitResolution,
HtlcResolutions: *resolutions.HtlcResolutions,
AnchorResolution: resolutions.AnchorResolution,
}

// When processing a unilateral close event, we'll
Expand All @@ -2825,7 +2848,7 @@ func (c *ChannelArbitrator) channelAttendant(bestHeight int32) {
// available to fetch in that state, we'll also write
// the commit set so we can reconstruct our chain
// actions on restart.
err := c.log.LogContractResolutions(contractRes)
err = c.log.LogContractResolutions(contractRes)
if err != nil {
log.Errorf("Unable to write resolutions: %v",
err)
Expand Down
48 changes: 29 additions & 19 deletions contractcourt/channel_arbitrator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -696,8 +696,10 @@ func TestChannelArbitratorLocalForceClose(t *testing.T) {
chanArb.cfg.ChainEvents.LocalUnilateralClosure <- &LocalUnilateralCloseInfo{
SpendDetail: &chainntnfs.SpendDetail{},
LocalForceCloseSummary: &lnwallet.LocalForceCloseSummary{
CloseTx: &wire.MsgTx{},
HtlcResolutions: &lnwallet.HtlcResolutions{},
CloseTx: &wire.MsgTx{},
Resolutions: fn.Some(lnwallet.Resolutions{
HtlcResolutions: &lnwallet.HtlcResolutions{},
}),
},
ChannelCloseSummary: &channeldb.ChannelCloseSummary{},
}
Expand Down Expand Up @@ -987,15 +989,18 @@ func TestChannelArbitratorLocalForceClosePendingHtlc(t *testing.T) {
},
}

//nolint:lll
chanArb.cfg.ChainEvents.LocalUnilateralClosure <- &LocalUnilateralCloseInfo{
SpendDetail: &chainntnfs.SpendDetail{},
LocalForceCloseSummary: &lnwallet.LocalForceCloseSummary{
CloseTx: closeTx,
HtlcResolutions: &lnwallet.HtlcResolutions{
OutgoingHTLCs: []lnwallet.OutgoingHtlcResolution{
outgoingRes,
Resolutions: fn.Some(lnwallet.Resolutions{
HtlcResolutions: &lnwallet.HtlcResolutions{
OutgoingHTLCs: []lnwallet.OutgoingHtlcResolution{
outgoingRes,
},
},
},
}),
},
ChannelCloseSummary: &channeldb.ChannelCloseSummary{},
CommitSet: CommitSet{
Expand Down Expand Up @@ -1613,12 +1618,15 @@ func TestChannelArbitratorCommitFailure(t *testing.T) {
},
{
closeType: channeldb.LocalForceClose,
//nolint:lll
sendEvent: func(chanArb *ChannelArbitrator) {
chanArb.cfg.ChainEvents.LocalUnilateralClosure <- &LocalUnilateralCloseInfo{
SpendDetail: &chainntnfs.SpendDetail{},
LocalForceCloseSummary: &lnwallet.LocalForceCloseSummary{
CloseTx: &wire.MsgTx{},
HtlcResolutions: &lnwallet.HtlcResolutions{},
CloseTx: &wire.MsgTx{},
Resolutions: fn.Some(lnwallet.Resolutions{
HtlcResolutions: &lnwallet.HtlcResolutions{},
}),
},
ChannelCloseSummary: &channeldb.ChannelCloseSummary{},
}
Expand Down Expand Up @@ -1946,11 +1954,15 @@ func TestChannelArbitratorDanglingCommitForceClose(t *testing.T) {
// being canalled back. Also note that there're no HTLC
// resolutions sent since we have none on our
// commitment transaction.
//
//nolint:lll
uniCloseInfo := &LocalUnilateralCloseInfo{
SpendDetail: &chainntnfs.SpendDetail{},
LocalForceCloseSummary: &lnwallet.LocalForceCloseSummary{
CloseTx: closeTx,
HtlcResolutions: &lnwallet.HtlcResolutions{},
CloseTx: closeTx,
Resolutions: fn.Some(lnwallet.Resolutions{
HtlcResolutions: &lnwallet.HtlcResolutions{},
}),
},
ChannelCloseSummary: &channeldb.ChannelCloseSummary{},
CommitSet: CommitSet{
Expand Down Expand Up @@ -2873,9 +2885,11 @@ func TestChannelArbitratorAnchors(t *testing.T) {
chanArb.cfg.ChainEvents.LocalUnilateralClosure <- &LocalUnilateralCloseInfo{
SpendDetail: &chainntnfs.SpendDetail{},
LocalForceCloseSummary: &lnwallet.LocalForceCloseSummary{
CloseTx: closeTx,
HtlcResolutions: &lnwallet.HtlcResolutions{},
AnchorResolution: anchorResolution,
CloseTx: closeTx,
Resolutions: fn.Some(lnwallet.Resolutions{
HtlcResolutions: &lnwallet.HtlcResolutions{},
AnchorResolution: anchorResolution,
}),
},
ChannelCloseSummary: &channeldb.ChannelCloseSummary{},
CommitSet: CommitSet{
Expand Down Expand Up @@ -3109,14 +3123,10 @@ func (m *mockChannel) NewAnchorResolutions() (*lnwallet.AnchorResolutions,
return &lnwallet.AnchorResolutions{}, nil
}

func (m *mockChannel) ForceCloseChan() (*lnwallet.LocalForceCloseSummary, error) {
func (m *mockChannel) ForceCloseChan() (*wire.MsgTx, error) {
if m.forceCloseErr != nil {
return nil, m.forceCloseErr
}

summary := &lnwallet.LocalForceCloseSummary{
CloseTx: &wire.MsgTx{},
HtlcResolutions: &lnwallet.HtlcResolutions{},
}
return summary, nil
return &wire.MsgTx{}, nil
}
76 changes: 62 additions & 14 deletions lnwallet/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -7667,6 +7667,18 @@ type LocalForceCloseSummary struct {
// commitment state.
CloseTx *wire.MsgTx

// ChanSnapshot is a snapshot of the final state of the channel at the
// time the summary was created.
ChanSnapshot channeldb.ChannelSnapshot

// Resolutions contains all the data required for resolving the
// different output types of a commitment transaction.
Resolutions fn.Option[Resolutions]
}

// Resolutions contains describes all the data required for resolving the
// different output types of a commitment transaction.
type Resolutions struct {
// CommitResolution contains all the data required to sweep the output
// to ourselves. Since this is our commitment transaction, we'll need
// to wait a time delay before we can sweep the output.
Expand All @@ -7675,19 +7687,38 @@ type LocalForceCloseSummary struct {
// then this will be nil.
CommitResolution *CommitOutputResolution

// AnchorResolution contains the data required to sweep the anchor
// output. If the channel type doesn't include anchors, the value of
// this field will be nil.
AnchorResolution *AnchorResolution

// HtlcResolutions contains all the data required to sweep any outgoing
// HTLC's and incoming HTLc's we know the preimage to. For each of these
// HTLC's, we'll need to go to the second level to sweep them fully.
HtlcResolutions *HtlcResolutions
}

// ChanSnapshot is a snapshot of the final state of the channel at the
// time the summary was created.
ChanSnapshot channeldb.ChannelSnapshot
// ForceCloseOpt is a functional option argument for the ForceClose method.
type ForceCloseOpt func(*forceCloseConfig)

// AnchorResolution contains the data required to sweep the anchor
// output. If the channel type doesn't include anchors, the value of
// this field will be nil.
AnchorResolution *AnchorResolution
// forceCloseConfig holds the configuration options for force closing a channel.
type forceCloseConfig struct {
// skipResolution if true will exclude the resolutions from the
// returned summary.
skipResolution bool
}

// defaultForceCloseConfig returns the default force close configuration.
func defaultForceCloseConfig() *forceCloseConfig {
return &forceCloseConfig{}
}

// WithSkipResolution creates an option to exclude the force close
// resolutions from the returned summary.
func WithSkipResolution() ForceCloseOpt {
return func(cfg *forceCloseConfig) {
cfg.skipResolution = true
}
}

// ForceClose executes a unilateral closure of the transaction at the current
Expand All @@ -7698,10 +7729,17 @@ type LocalForceCloseSummary struct {
// outputs within the commitment transaction.
//
// TODO(roasbeef): all methods need to abort if in dispute state
func (lc *LightningChannel) ForceClose() (*LocalForceCloseSummary, error) {
func (lc *LightningChannel) ForceClose(opts ...ForceCloseOpt) (
*LocalForceCloseSummary, error) {

lc.Lock()
defer lc.Unlock()

cfg := defaultForceCloseConfig()
for _, opt := range opts {
opt(cfg)
}

// If we've detected local data loss for this channel, then we won't
// allow a force close, as it may be the case that we have a dated
// version of the commitment, or this is actually a channel shell.
Expand All @@ -7716,6 +7754,14 @@ func (lc *LightningChannel) ForceClose() (*LocalForceCloseSummary, error) {
return nil, err
}

if cfg.skipResolution {
return &LocalForceCloseSummary{
ChanPoint: lc.channelState.FundingOutpoint,
ChanSnapshot: *lc.channelState.Snapshot(),
CloseTx: commitTx,
}, nil
}

localCommitment := lc.channelState.LocalCommitment
summary, err := NewLocalForceCloseSummary(
lc.channelState, lc.Signer, commitTx,
Expand Down Expand Up @@ -7919,12 +7965,14 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel,
}

return &LocalForceCloseSummary{
ChanPoint: chanState.FundingOutpoint,
CloseTx: commitTx,
CommitResolution: commitResolution,
HtlcResolutions: htlcResolutions,
ChanSnapshot: *chanState.Snapshot(),
AnchorResolution: anchorResolution,
ChanPoint: chanState.FundingOutpoint,
CloseTx: commitTx,
ChanSnapshot: *chanState.Snapshot(),
Resolutions: fn.Some(Resolutions{
CommitResolution: commitResolution,
HtlcResolutions: htlcResolutions,
AnchorResolution: anchorResolution,
}),
}, nil
}

Expand Down
Loading

0 comments on commit a7e3ef6

Please sign in to comment.