diff --git a/contractcourt/channel_arbitrator.go b/contractcourt/channel_arbitrator.go index 3bd669027b..e05dc3506b 100644 --- a/contractcourt/channel_arbitrator.go +++ b/contractcourt/channel_arbitrator.go @@ -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 ( @@ -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 @@ -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() diff --git a/lnwallet/channel.go b/lnwallet/channel.go index f9d0778750..5b5eb6a002 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -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. @@ -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 @@ -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 @@ -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. @@ -5710,6 +5728,17 @@ 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. @@ -5717,16 +5746,28 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, signer input.Si 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 @@ -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 }