mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-04 09:48:19 +01:00
lnwallet+channeldb: add anchor resolutions
Co-authored-by: Joost Jager <joost.jager@gmail.com>
This commit is contained in:
parent
30fc03d84d
commit
dc6c4637b6
2 changed files with 199 additions and 3 deletions
|
@ -5115,6 +5115,11 @@ type UnilateralCloseSummary struct {
|
||||||
// RemoteCommit is the exact commitment state that the remote party
|
// RemoteCommit is the exact commitment state that the remote party
|
||||||
// broadcast.
|
// broadcast.
|
||||||
RemoteCommit channeldb.ChannelCommitment
|
RemoteCommit channeldb.ChannelCommitment
|
||||||
|
|
||||||
|
// AnchorResolution contains the data required to sweep our anchor
|
||||||
|
// output. If the channel type doesn't include anchors, the value of
|
||||||
|
// this field will be nil.
|
||||||
|
AnchorResolution *AnchorResolution
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUnilateralCloseSummary creates a new summary that provides the caller
|
// NewUnilateralCloseSummary creates a new summary that provides the caller
|
||||||
|
@ -5229,12 +5234,20 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
|
||||||
closeSummary.LastChanSyncMsg = chanSync
|
closeSummary.LastChanSyncMsg = chanSync
|
||||||
}
|
}
|
||||||
|
|
||||||
|
anchorResolution, err := NewAnchorResolution(
|
||||||
|
chanState, commitTxBroadcast,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &UnilateralCloseSummary{
|
return &UnilateralCloseSummary{
|
||||||
SpendDetail: commitSpend,
|
SpendDetail: commitSpend,
|
||||||
ChannelCloseSummary: closeSummary,
|
ChannelCloseSummary: closeSummary,
|
||||||
CommitResolution: commitResolution,
|
CommitResolution: commitResolution,
|
||||||
HtlcResolutions: htlcResolutions,
|
HtlcResolutions: htlcResolutions,
|
||||||
RemoteCommit: remoteCommit,
|
RemoteCommit: remoteCommit,
|
||||||
|
AnchorResolution: anchorResolution,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5685,6 +5698,16 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AnchorResolution holds the information necessary to spend our commitment tx
|
||||||
|
// anchor.
|
||||||
|
type AnchorResolution struct {
|
||||||
|
// AnchorSignDescriptor is the sign descriptor for our anchor.
|
||||||
|
AnchorSignDescriptor input.SignDescriptor
|
||||||
|
|
||||||
|
// CommitAnchor is the anchor outpoint on the commit tx.
|
||||||
|
CommitAnchor wire.OutPoint
|
||||||
|
}
|
||||||
|
|
||||||
// LocalForceCloseSummary describes the final commitment state before the
|
// LocalForceCloseSummary describes the final commitment state before the
|
||||||
// channel is locked-down to initiate a force closure by broadcasting the
|
// channel is locked-down to initiate a force closure by broadcasting the
|
||||||
// latest state on-chain. If we intend to broadcast this this state, the
|
// latest state on-chain. If we intend to broadcast this this state, the
|
||||||
|
@ -5717,6 +5740,11 @@ type LocalForceCloseSummary struct {
|
||||||
// ChanSnapshot is a snapshot of the final state of the channel at the
|
// ChanSnapshot is a snapshot of the final state of the channel at the
|
||||||
// time the summary was created.
|
// time the summary was created.
|
||||||
ChanSnapshot channeldb.ChannelSnapshot
|
ChanSnapshot channeldb.ChannelSnapshot
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForceClose executes a unilateral closure of the transaction at the current
|
// ForceClose executes a unilateral closure of the transaction at the current
|
||||||
|
@ -5855,12 +5883,20 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
anchorResolution, err := NewAnchorResolution(
|
||||||
|
chanState, commitTx,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return &LocalForceCloseSummary{
|
return &LocalForceCloseSummary{
|
||||||
ChanPoint: chanState.FundingOutpoint,
|
ChanPoint: chanState.FundingOutpoint,
|
||||||
CloseTx: commitTx,
|
CloseTx: commitTx,
|
||||||
CommitResolution: commitResolution,
|
CommitResolution: commitResolution,
|
||||||
HtlcResolutions: htlcResolutions,
|
HtlcResolutions: htlcResolutions,
|
||||||
ChanSnapshot: *chanState.Snapshot(),
|
ChanSnapshot: *chanState.Snapshot(),
|
||||||
|
AnchorResolution: anchorResolution,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6019,6 +6055,109 @@ func (lc *LightningChannel) CompleteCooperativeClose(localSig, remoteSig []byte,
|
||||||
return closeTx, ourBalance, nil
|
return closeTx, ourBalance, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewAnchorResolutions returns the anchor resolutions for all currently valid
|
||||||
|
// commitment transactions. Because we have no view on the mempool, we can only
|
||||||
|
// blindly anchor all of these txes down.
|
||||||
|
func (lc *LightningChannel) NewAnchorResolutions() ([]*AnchorResolution,
|
||||||
|
error) {
|
||||||
|
|
||||||
|
lc.Lock()
|
||||||
|
defer lc.Unlock()
|
||||||
|
|
||||||
|
var resolutions []*AnchorResolution
|
||||||
|
|
||||||
|
// Add anchor for local commitment tx, if any.
|
||||||
|
localRes, err := NewAnchorResolution(
|
||||||
|
lc.channelState, lc.channelState.LocalCommitment.CommitTx,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if localRes != nil {
|
||||||
|
resolutions = append(resolutions, localRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add anchor for remote commitment tx, if any.
|
||||||
|
remoteRes, err := NewAnchorResolution(
|
||||||
|
lc.channelState, lc.channelState.RemoteCommitment.CommitTx,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if remoteRes != nil {
|
||||||
|
resolutions = append(resolutions, remoteRes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add anchor for remote pending commitment tx, if any.
|
||||||
|
remotePendingCommit, err := lc.channelState.RemoteCommitChainTip()
|
||||||
|
if err != nil && err != channeldb.ErrNoPendingCommit {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if remotePendingCommit != nil {
|
||||||
|
remotePendingRes, err := NewAnchorResolution(
|
||||||
|
lc.channelState,
|
||||||
|
remotePendingCommit.Commitment.CommitTx,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if remotePendingRes != nil {
|
||||||
|
resolutions = append(resolutions, remotePendingRes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolutions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAnchorResolution returns the information that is required to sweep the
|
||||||
|
// local anchor.
|
||||||
|
func NewAnchorResolution(chanState *channeldb.OpenChannel,
|
||||||
|
commitTx *wire.MsgTx) (*AnchorResolution, error) {
|
||||||
|
|
||||||
|
// Return nil resolution if the channel has no anchors.
|
||||||
|
if !chanState.ChanType.HasAnchors() {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive our local anchor script.
|
||||||
|
localAnchor, _, err := CommitScriptAnchors(
|
||||||
|
&chanState.LocalChanCfg, &chanState.RemoteChanCfg,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up the script on the commitment transaction. It may not be
|
||||||
|
// present if there is no output paying to us.
|
||||||
|
found, index := input.FindScriptOutputIndex(commitTx, localAnchor.PkScript)
|
||||||
|
if !found {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
outPoint := &wire.OutPoint{
|
||||||
|
Hash: commitTx.TxHash(),
|
||||||
|
Index: index,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instantiate the sign descriptor that allows sweeping of the anchor.
|
||||||
|
signDesc := &input.SignDescriptor{
|
||||||
|
KeyDesc: chanState.LocalChanCfg.MultiSigKey,
|
||||||
|
WitnessScript: localAnchor.WitnessScript,
|
||||||
|
Output: &wire.TxOut{
|
||||||
|
PkScript: localAnchor.PkScript,
|
||||||
|
Value: int64(anchorSize),
|
||||||
|
},
|
||||||
|
HashType: txscript.SigHashAll,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &AnchorResolution{
|
||||||
|
CommitAnchor: *outPoint,
|
||||||
|
AnchorSignDescriptor: *signDesc,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// AvailableBalance returns the current balance available for sending within
|
// AvailableBalance returns the current balance available for sending within
|
||||||
// the channel. By available balance, we mean that if at this very instance a
|
// the channel. By available balance, we mean that if at this very instance a
|
||||||
// new commitment were to be created which evals all the log entries, what
|
// new commitment were to be created which evals all the log entries, what
|
||||||
|
|
|
@ -526,13 +526,36 @@ func TestCooperativeChannelClosure(t *testing.T) {
|
||||||
// force close generates HTLC resolutions that are capable of sweeping both
|
// force close generates HTLC resolutions that are capable of sweeping both
|
||||||
// incoming and outgoing HTLC's.
|
// incoming and outgoing HTLC's.
|
||||||
func TestForceClose(t *testing.T) {
|
func TestForceClose(t *testing.T) {
|
||||||
|
t.Run("tweakless", func(t *testing.T) {
|
||||||
|
testForceClose(t, &forceCloseTestCase{
|
||||||
|
chanType: channeldb.SingleFunderTweaklessBit,
|
||||||
|
expectedCommitWeight: input.CommitWeight,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("anchors", func(t *testing.T) {
|
||||||
|
testForceClose(t, &forceCloseTestCase{
|
||||||
|
chanType: channeldb.SingleFunderTweaklessBit |
|
||||||
|
channeldb.AnchorOutputsBit,
|
||||||
|
expectedCommitWeight: input.AnchorCommitWeight,
|
||||||
|
anchorAmt: anchorSize * 2,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type forceCloseTestCase struct {
|
||||||
|
chanType channeldb.ChannelType
|
||||||
|
expectedCommitWeight int64
|
||||||
|
anchorAmt btcutil.Amount
|
||||||
|
}
|
||||||
|
|
||||||
|
func testForceClose(t *testing.T, testCase *forceCloseTestCase) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
// Create a test channel which will be used for the duration of this
|
// Create a test channel which will be used for the duration of this
|
||||||
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
// unittest. The channel will be funded evenly with Alice having 5 BTC,
|
||||||
// and Bob having 5 BTC.
|
// and Bob having 5 BTC.
|
||||||
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(
|
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(
|
||||||
channeldb.SingleFunderTweaklessBit,
|
testCase.chanType,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to create test channels: %v", err)
|
t.Fatalf("unable to create test channels: %v", err)
|
||||||
|
@ -594,6 +617,36 @@ func TestForceClose(t *testing.T) {
|
||||||
1, len(closeSummary.HtlcResolutions.IncomingHTLCs))
|
1, len(closeSummary.HtlcResolutions.IncomingHTLCs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify the anchor resolutions for the anchor commitment format.
|
||||||
|
if testCase.chanType.HasAnchors() {
|
||||||
|
// Check the close summary resolution.
|
||||||
|
anchorRes := closeSummary.AnchorResolution
|
||||||
|
if anchorRes == nil {
|
||||||
|
t.Fatal("expected anchor resolution")
|
||||||
|
}
|
||||||
|
if anchorRes.CommitAnchor.Hash != closeSummary.CloseTx.TxHash() {
|
||||||
|
t.Fatal("commit tx not referenced by anchor res")
|
||||||
|
}
|
||||||
|
if anchorRes.AnchorSignDescriptor.Output.Value !=
|
||||||
|
int64(anchorSize) {
|
||||||
|
|
||||||
|
t.Fatal("unexpected anchor size")
|
||||||
|
}
|
||||||
|
if anchorRes.AnchorSignDescriptor.WitnessScript == nil {
|
||||||
|
t.Fatal("expected anchor witness script")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the pre-confirmation resolutions.
|
||||||
|
resList, err := aliceChannel.NewAnchorResolutions()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("pre-confirmation resolution error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resList) != 2 {
|
||||||
|
t.Fatal("expected two resolutions")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// The SelfOutputSignDesc should be non-nil since the output to-self is
|
// The SelfOutputSignDesc should be non-nil since the output to-self is
|
||||||
// non-dust.
|
// non-dust.
|
||||||
aliceCommitResolution := closeSummary.CommitResolution
|
aliceCommitResolution := closeSummary.CommitResolution
|
||||||
|
@ -612,12 +665,16 @@ func TestForceClose(t *testing.T) {
|
||||||
|
|
||||||
// Factoring in the fee rate, Alice's amount should properly reflect
|
// Factoring in the fee rate, Alice's amount should properly reflect
|
||||||
// that we've added two additional HTLC to the commitment transaction.
|
// that we've added two additional HTLC to the commitment transaction.
|
||||||
totalCommitWeight := int64(input.CommitWeight + (input.HTLCWeight * 2))
|
totalCommitWeight := testCase.expectedCommitWeight +
|
||||||
|
(input.HTLCWeight * 2)
|
||||||
feePerKw := chainfee.SatPerKWeight(
|
feePerKw := chainfee.SatPerKWeight(
|
||||||
aliceChannel.channelState.LocalCommitment.FeePerKw,
|
aliceChannel.channelState.LocalCommitment.FeePerKw,
|
||||||
)
|
)
|
||||||
commitFee := feePerKw.FeeForWeight(totalCommitWeight)
|
commitFee := feePerKw.FeeForWeight(totalCommitWeight)
|
||||||
expectedAmount := (aliceChannel.Capacity / 2) - htlcAmount.ToSatoshis() - commitFee
|
|
||||||
|
expectedAmount := (aliceChannel.Capacity / 2) -
|
||||||
|
htlcAmount.ToSatoshis() - commitFee - testCase.anchorAmt
|
||||||
|
|
||||||
if aliceCommitResolution.SelfOutputSignDesc.Output.Value != int64(expectedAmount) {
|
if aliceCommitResolution.SelfOutputSignDesc.Output.Value != int64(expectedAmount) {
|
||||||
t.Fatalf("alice incorrect output value in SelfOutputSignDesc, "+
|
t.Fatalf("alice incorrect output value in SelfOutputSignDesc, "+
|
||||||
"expected %v, got %v", int64(expectedAmount),
|
"expected %v, got %v", int64(expectedAmount),
|
||||||
|
|
Loading…
Add table
Reference in a new issue