Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 42 additions & 0 deletions contractcourt/channel_arbitrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/sweep"
)

var (
Expand Down Expand Up @@ -871,6 +873,43 @@ func (c *ChannelArbitrator) stateStep(
if err != lnwallet.ErrDoubleSpend {
return StateError, closeTx, err
}

// We don't know whether the double spend is caused by
// our own commit tx in the mempool (previous run), or
// the remote tx.
}

// Try to sweep all possible anchors. Even if we succeed in
// publishing, it may still be that the remote tx "wins the
// mempool" on the network. Maybe not in our mempool, but our
// mempool isn't necessarily fixed either (multiple neutrino
// peers). Or possibly in the future, our mempool tx may get
// replaced by the remote tx if it is packaged with a child tx.
for _, anchor := range closeSummary.AnchorResolutions {
// Prepare anchor output for sweeping.
anchorInput := input.MakeBaseInput(
&anchor.SelfOutPoint,
input.CommitmentNoDelay,
&anchor.SelfOutputSignDesc,
triggerHeight,
)

// Sweep anchor output with default sweep conf target.
//
// TODO: More active miner fee decision making when
// deadline approaches. (out of scope for poc)
//
// TODO: Add exclusive groups to prevent any of these
// anchors to end up in the same sweep tx.
_, err = c.cfg.Sweeper.SweepInput(
&anchorInput,
sweep.FeePreference{
ConfTarget: sweepConfTarget,
},
)
if err != nil {
return StateError, closeTx, err
}
}

// We go to the StateCommitmentBroadcasted state, where we'll
Expand Down Expand Up @@ -911,6 +950,9 @@ func (c *ChannelArbitrator) stateStep(
// outside sub-systems, so we'll process the prior set of on-chain
// contract actions and launch a set of resolvers.
case StateContractClosed:
// TODO: Cancel two remote anchor sweeps or one local anchor
// sweep in the sweeper, now that we know which tx confirmed.

// First, we'll fetch our chain actions, and both sets of
// resolutions so we can process them.
contractResolutions, err := c.log.FetchContractResolutions()
Expand Down
64 changes: 53 additions & 11 deletions lnwallet/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,11 @@ type CommitmentKeyRing struct {
// commitment transaction.
NoDelayKey *btcec.PublicKey

// LocalNoDelayKey is the tx owner's payment key in the commitment tx.
// This is the key used to generate the unencumbered output within the
// commitment transaction.
LocalNoDelayKey *btcec.PublicKey

// RevocationKey is the key that can be used by the other party to
// redeem outputs from a revoked commitment transaction if it were to
// be published.
Expand Down Expand Up @@ -1007,6 +1012,7 @@ func DeriveCommitmentKeys(commitPoint *btcec.PublicKey,
RemoteHtlcKey: input.TweakPubKey(
remoteChanCfg.HtlcBasePoint.PubKey, commitPoint,
),
LocalNoDelayKey: localChanCfg.PaymentBasePoint.PubKey,
}

// We'll now compute the delay, no delay, and revocation key based on
Expand Down Expand Up @@ -4986,6 +4992,16 @@ type CommitOutputResolution struct {
MaturityDelay uint32
}

type AnchorOutputResolution struct {
// SelfOutPoint is the full outpoint that points to our anchor output
// within the closing commitment transaction.
SelfOutPoint wire.OutPoint

// SelfOutputSignDesc is a fully populated sign descriptor capable of
// generating a valid signature to sweep the output paying to us.
SelfOutputSignDesc input.SignDescriptor
}

// UnilateralCloseSummary describes the details of a detected unilateral
// channel closure. This includes the information about with which
// transactions, and block the channel was unilaterally closed, as well as
Expand Down Expand Up @@ -5624,6 +5640,8 @@ type LocalForceCloseSummary struct {
// then this will be nil.
CommitResolution *CommitOutputResolution

AnchorResolutions []*AnchorOutputResolution

// 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.
Expand Down Expand Up @@ -5710,23 +5728,46 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
return nil, err
}

anchorScript, err := input.CommitScriptUnencumbered(
keyRing.LocalNoDelayKey,
)
if err != nil {
return nil, err
}
anchorScriptHash, err := input.WitnessScriptHash(anchorScript)
if err != nil {
return nil, err
}

// Locate the output index of the delayed commitment output back to us.
// We'll return the details of this output to the caller so they can
// sweep it once it's mature.
var (
delayIndex uint32
delayScript []byte
)

anchors := []*AnchorOutputResolution{}

for i, txOut := range commitTx.TxOut {
if !bytes.Equal(payToUsScriptHash, txOut.PkScript) {
continue
switch {
case bytes.Equal(payToUsScriptHash, txOut.PkScript):
delayIndex = uint32(i)
delayScript = txOut.PkScript
case bytes.Equal(anchorScriptHash, txOut.PkScript):
anchors = append(anchors, &AnchorOutputResolution{
SelfOutPoint: wire.OutPoint{
Hash: commitTx.TxHash(),
Index: uint32(i),
},
// Set sign descriptor
})
}

delayIndex = uint32(i)
delayScript = txOut.PkScript
break
}

// TODO: Add the two possible remote commit tx to_remote outputs to the
// anchors set.

// With the necessary information gathered above, create a new sign
// descriptor which is capable of generating the signature the caller
// needs to sweep this output. The hash cache, and input index are not
Expand Down Expand Up @@ -5772,11 +5813,12 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
}

return &LocalForceCloseSummary{
ChanPoint: chanState.FundingOutpoint,
CloseTx: commitTx,
CommitResolution: commitResolution,
HtlcResolutions: htlcResolutions,
ChanSnapshot: *chanState.Snapshot(),
ChanPoint: chanState.FundingOutpoint,
CloseTx: commitTx,
CommitResolution: commitResolution,
AnchorResolutions: anchors,
HtlcResolutions: htlcResolutions,
ChanSnapshot: *chanState.Snapshot(),
}, nil
}

Expand Down