lnwallet+channeldb: add anchor resolutions
Co-authored-by: Joost Jager <joost.jager@gmail.com>
This commit is contained in:
parent
30fc03d84d
commit
dc6c4637b6
@ -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),
|
||||
|
Loading…
Reference in New Issue
Block a user