lnwallet+channeldb: add anchor resolutions

Co-authored-by: Joost Jager <joost.jager@gmail.com>
This commit is contained in:
Johan T. Halseth 2019-12-13 11:14:22 +01:00 committed by Joost Jager
parent 30fc03d84d
commit dc6c4637b6
No known key found for this signature in database
GPG Key ID: A61B9D4C393C59C7
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
// broadcast.
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
@ -5229,12 +5234,20 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
closeSummary.LastChanSyncMsg = chanSync
}
anchorResolution, err := NewAnchorResolution(
chanState, commitTxBroadcast,
)
if err != nil {
return nil, err
}
return &UnilateralCloseSummary{
SpendDetail: commitSpend,
ChannelCloseSummary: closeSummary,
CommitResolution: commitResolution,
HtlcResolutions: htlcResolutions,
RemoteCommit: remoteCommit,
AnchorResolution: anchorResolution,
}, nil
}
@ -5685,6 +5698,16 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool,
}, 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
// 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
@ -5717,6 +5740,11 @@ type LocalForceCloseSummary struct {
// ChanSnapshot is a snapshot of the final state of the channel at the
// time the summary was created.
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
@ -5855,12 +5883,20 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
return nil, err
}
anchorResolution, err := NewAnchorResolution(
chanState, commitTx,
)
if err != nil {
return nil, err
}
return &LocalForceCloseSummary{
ChanPoint: chanState.FundingOutpoint,
CloseTx: commitTx,
CommitResolution: commitResolution,
HtlcResolutions: htlcResolutions,
ChanSnapshot: *chanState.Snapshot(),
AnchorResolution: anchorResolution,
}, nil
}
@ -6019,6 +6055,109 @@ func (lc *LightningChannel) CompleteCooperativeClose(localSig, remoteSig []byte,
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
// 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

@ -526,13 +526,36 @@ func TestCooperativeChannelClosure(t *testing.T) {
// force close generates HTLC resolutions that are capable of sweeping both
// incoming and outgoing HTLC's.
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()
// 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,
// and Bob having 5 BTC.
aliceChannel, bobChannel, cleanUp, err := CreateTestChannels(
channeldb.SingleFunderTweaklessBit,
testCase.chanType,
)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
@ -594,6 +617,36 @@ func TestForceClose(t *testing.T) {
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
// non-dust.
aliceCommitResolution := closeSummary.CommitResolution
@ -612,12 +665,16 @@ func TestForceClose(t *testing.T) {
// Factoring in the fee rate, Alice's amount should properly reflect
// 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(
aliceChannel.channelState.LocalCommitment.FeePerKw,
)
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) {
t.Fatalf("alice incorrect output value in SelfOutputSignDesc, "+
"expected %v, got %v", int64(expectedAmount),