Merge pull request #4558 from halseth/anchors-spec
[anchor commitment] Make the anchor commitment type spec compliant
This commit is contained in:
commit
c704c5747a
@ -206,8 +206,7 @@ const (
|
|||||||
|
|
||||||
// AnchorOutputsBit indicates that the channel makes use of anchor
|
// AnchorOutputsBit indicates that the channel makes use of anchor
|
||||||
// outputs to bump the commitment transaction's effective feerate. This
|
// outputs to bump the commitment transaction's effective feerate. This
|
||||||
// channel type also uses a delayed to_remote output script. If bit is
|
// channel type also uses a delayed to_remote output script.
|
||||||
// set, we'll find the size of the anchor outputs in the database.
|
|
||||||
AnchorOutputsBit ChannelType = 1 << 3
|
AnchorOutputsBit ChannelType = 1 << 3
|
||||||
|
|
||||||
// FrozenBit indicates that the channel is a frozen channel, meaning
|
// FrozenBit indicates that the channel is a frozen channel, meaning
|
||||||
|
@ -6179,20 +6179,15 @@ func (lc *LightningChannel) CreateCloseProposal(proposedFee btcutil.Amount,
|
|||||||
return nil, nil, 0, ErrChanClosing
|
return nil, nil, 0, ErrChanClosing
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subtract the proposed fee from the appropriate balance, taking care
|
// Get the final balances after subtracting the proposed fee, taking
|
||||||
// not to persist the adjusted balance, as the feeRate may change
|
// care not to persist the adjusted balance, as the feeRate may change
|
||||||
// during the channel closing process.
|
// during the channel closing process.
|
||||||
localCommit := lc.channelState.LocalCommitment
|
ourBalance, theirBalance, err := CoopCloseBalance(
|
||||||
ourBalance := localCommit.LocalBalance.ToSatoshis()
|
lc.channelState.ChanType, lc.channelState.IsInitiator,
|
||||||
theirBalance := localCommit.RemoteBalance.ToSatoshis()
|
proposedFee, lc.channelState.LocalCommitment,
|
||||||
|
)
|
||||||
// We'll make sure we account for the complete balance by adding the
|
if err != nil {
|
||||||
// current dangling commitment fee to the balance of the initiator.
|
return nil, nil, 0, err
|
||||||
commitFee := localCommit.CommitFee
|
|
||||||
if lc.channelState.IsInitiator {
|
|
||||||
ourBalance = ourBalance - proposedFee + commitFee
|
|
||||||
} else {
|
|
||||||
theirBalance = theirBalance - proposedFee + commitFee
|
|
||||||
}
|
}
|
||||||
|
|
||||||
closeTx := CreateCooperativeCloseTx(
|
closeTx := CreateCooperativeCloseTx(
|
||||||
@ -6248,20 +6243,13 @@ func (lc *LightningChannel) CompleteCooperativeClose(
|
|||||||
return nil, 0, ErrChanClosing
|
return nil, 0, ErrChanClosing
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subtract the proposed fee from the appropriate balance, taking care
|
// Get the final balances after subtracting the proposed fee.
|
||||||
// not to persist the adjusted balance, as the feeRate may change
|
ourBalance, theirBalance, err := CoopCloseBalance(
|
||||||
// during the channel closing process.
|
lc.channelState.ChanType, lc.channelState.IsInitiator,
|
||||||
localCommit := lc.channelState.LocalCommitment
|
proposedFee, lc.channelState.LocalCommitment,
|
||||||
ourBalance := localCommit.LocalBalance.ToSatoshis()
|
)
|
||||||
theirBalance := localCommit.RemoteBalance.ToSatoshis()
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
// We'll make sure we account for the complete balance by adding the
|
|
||||||
// current dangling commitment fee to the balance of the initiator.
|
|
||||||
commitFee := localCommit.CommitFee
|
|
||||||
if lc.channelState.IsInitiator {
|
|
||||||
ourBalance = ourBalance - proposedFee + commitFee
|
|
||||||
} else {
|
|
||||||
theirBalance = theirBalance - proposedFee + commitFee
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the transaction used to return the current settled balance
|
// Create the transaction used to return the current settled balance
|
||||||
|
@ -683,14 +683,37 @@ func testCommitHTLCSigTieBreak(t *testing.T, restart bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCooperativeChannelClosure checks that the coop close process finishes
|
||||||
|
// with an agreement from both parties, and that the final balances of the
|
||||||
|
// close tx check out.
|
||||||
func TestCooperativeChannelClosure(t *testing.T) {
|
func TestCooperativeChannelClosure(t *testing.T) {
|
||||||
|
t.Run("tweakless", func(t *testing.T) {
|
||||||
|
testCoopClose(t, &coopCloseTestCase{
|
||||||
|
chanType: channeldb.SingleFunderTweaklessBit,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("anchors", func(t *testing.T) {
|
||||||
|
testCoopClose(t, &coopCloseTestCase{
|
||||||
|
chanType: channeldb.SingleFunderTweaklessBit |
|
||||||
|
channeldb.AnchorOutputsBit,
|
||||||
|
anchorAmt: anchorSize * 2,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type coopCloseTestCase struct {
|
||||||
|
chanType channeldb.ChannelType
|
||||||
|
anchorAmt btcutil.Amount
|
||||||
|
}
|
||||||
|
|
||||||
|
func testCoopClose(t *testing.T, testCase *coopCloseTestCase) {
|
||||||
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)
|
||||||
@ -707,7 +730,7 @@ func TestCooperativeChannelClosure(t *testing.T) {
|
|||||||
bobChannel.channelState.LocalCommitment.FeePerKw,
|
bobChannel.channelState.LocalCommitment.FeePerKw,
|
||||||
)
|
)
|
||||||
|
|
||||||
// We'll store with both Alice and Bob creating a new close proposal
|
// We'll start with both Alice and Bob creating a new close proposal
|
||||||
// with the same fee.
|
// with the same fee.
|
||||||
aliceFee := aliceChannel.CalcFee(aliceFeeRate)
|
aliceFee := aliceChannel.CalcFee(aliceFeeRate)
|
||||||
aliceSig, _, _, err := aliceChannel.CreateCloseProposal(
|
aliceSig, _, _, err := aliceChannel.CreateCloseProposal(
|
||||||
@ -728,7 +751,7 @@ func TestCooperativeChannelClosure(t *testing.T) {
|
|||||||
// With the proposals created, both sides should be able to properly
|
// With the proposals created, both sides should be able to properly
|
||||||
// process the other party's signature. This indicates that the
|
// process the other party's signature. This indicates that the
|
||||||
// transaction is well formed, and the signatures verify.
|
// transaction is well formed, and the signatures verify.
|
||||||
aliceCloseTx, _, err := bobChannel.CompleteCooperativeClose(
|
aliceCloseTx, bobTxBalance, err := bobChannel.CompleteCooperativeClose(
|
||||||
bobSig, aliceSig, bobDeliveryScript, aliceDeliveryScript,
|
bobSig, aliceSig, bobDeliveryScript, aliceDeliveryScript,
|
||||||
bobFee,
|
bobFee,
|
||||||
)
|
)
|
||||||
@ -737,7 +760,7 @@ func TestCooperativeChannelClosure(t *testing.T) {
|
|||||||
}
|
}
|
||||||
bobCloseSha := aliceCloseTx.TxHash()
|
bobCloseSha := aliceCloseTx.TxHash()
|
||||||
|
|
||||||
bobCloseTx, _, err := aliceChannel.CompleteCooperativeClose(
|
bobCloseTx, aliceTxBalance, err := aliceChannel.CompleteCooperativeClose(
|
||||||
aliceSig, bobSig, aliceDeliveryScript, bobDeliveryScript,
|
aliceSig, bobSig, aliceDeliveryScript, bobDeliveryScript,
|
||||||
aliceFee,
|
aliceFee,
|
||||||
)
|
)
|
||||||
@ -749,6 +772,32 @@ func TestCooperativeChannelClosure(t *testing.T) {
|
|||||||
if bobCloseSha != aliceCloseSha {
|
if bobCloseSha != aliceCloseSha {
|
||||||
t.Fatalf("alice and bob close transactions don't match: %v", err)
|
t.Fatalf("alice and bob close transactions don't match: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finally, make sure the final balances are correct from both's
|
||||||
|
// perspective.
|
||||||
|
aliceBalance := aliceChannel.channelState.LocalCommitment.
|
||||||
|
LocalBalance.ToSatoshis()
|
||||||
|
|
||||||
|
// The commit balance have had the initiator's (Alice) commitfee and
|
||||||
|
// any anchors subtracted, so add that back to the final expected
|
||||||
|
// balance. Alice also pays the coop close fee, so that must be
|
||||||
|
// subtracted.
|
||||||
|
commitFee := aliceChannel.channelState.LocalCommitment.CommitFee
|
||||||
|
expBalanceAlice := aliceBalance + commitFee +
|
||||||
|
testCase.anchorAmt - bobFee
|
||||||
|
if aliceTxBalance != expBalanceAlice {
|
||||||
|
t.Fatalf("expected balance %v got %v", expBalanceAlice,
|
||||||
|
aliceTxBalance)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bob is not the initiator, so his final balance should simply be
|
||||||
|
// equal to the latest commitment balance.
|
||||||
|
expBalanceBob := bobChannel.channelState.LocalCommitment.
|
||||||
|
LocalBalance.ToSatoshis()
|
||||||
|
if bobTxBalance != expBalanceBob {
|
||||||
|
t.Fatalf("expected bob's balance to be %v got %v",
|
||||||
|
expBalanceBob, bobTxBalance)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestForceClose checks that the resulting ForceCloseSummary is correct when a
|
// TestForceClose checks that the resulting ForceCloseSummary is correct when a
|
||||||
@ -2181,11 +2230,11 @@ func TestCooperativeCloseDustAdherence(t *testing.T) {
|
|||||||
"got %v", 2, len(closeTx.TxOut))
|
"got %v", 2, len(closeTx.TxOut))
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll reset the channel states before proceeding to our nest test.
|
// We'll reset the channel states before proceeding to our next test.
|
||||||
resetChannelState()
|
resetChannelState()
|
||||||
|
|
||||||
// Next we'll modify the current balances and dust limits such that
|
// Next we'll modify the current balances and dust limits such that
|
||||||
// Bob's current balance is above _below_ his dust limit.
|
// Bob's current balance is _below_ his dust limit.
|
||||||
aliceBal := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
aliceBal := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
||||||
bobBal := lnwire.NewMSatFromSatoshis(250)
|
bobBal := lnwire.NewMSatFromSatoshis(250)
|
||||||
setBalances(aliceBal, bobBal)
|
setBalances(aliceBal, bobBal)
|
||||||
@ -2228,11 +2277,26 @@ func TestCooperativeCloseDustAdherence(t *testing.T) {
|
|||||||
int64(closeTx.TxOut[0].Value))
|
int64(closeTx.TxOut[0].Value))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, we'll modify the current balances and dust limits such that
|
// We'll modify the current balances and dust limits such that
|
||||||
// Alice's current balance is _below_ his her limit.
|
// Alice's current balance is too low to pay the proposed fee.
|
||||||
setBalances(bobBal, aliceBal)
|
setBalances(bobBal, aliceBal)
|
||||||
resetChannelState()
|
resetChannelState()
|
||||||
|
|
||||||
|
// Attempting to close with this fee now should fail, since Alice
|
||||||
|
// cannot afford it.
|
||||||
|
_, _, _, err = aliceChannel.CreateCloseProposal(
|
||||||
|
aliceFee, aliceDeliveryScript, bobDeliveryScript,
|
||||||
|
)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, we'll modify the current balances and dust limits such that
|
||||||
|
// Alice's balance after paying the coop fee is _below_ her dust limit.
|
||||||
|
lowBalance := lnwire.NewMSatFromSatoshis(aliceFee) + 1000
|
||||||
|
setBalances(lowBalance, aliceBal)
|
||||||
|
resetChannelState()
|
||||||
|
|
||||||
// Our final attempt at another cooperative channel closure. It should
|
// Our final attempt at another cooperative channel closure. It should
|
||||||
// succeed without any issues.
|
// succeed without any issues.
|
||||||
aliceSig, _, _, err = aliceChannel.CreateCloseProposal(
|
aliceSig, _, _, err = aliceChannel.CreateCloseProposal(
|
||||||
|
@ -663,6 +663,47 @@ func CreateCommitTx(chanType channeldb.ChannelType,
|
|||||||
return commitTx, nil
|
return commitTx, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CoopCloseBalance returns the final balances that should be used to create
|
||||||
|
// the cooperative close tx, given the channel type and transaction fee.
|
||||||
|
func CoopCloseBalance(chanType channeldb.ChannelType, isInitiator bool,
|
||||||
|
coopCloseFee btcutil.Amount, localCommit channeldb.ChannelCommitment) (
|
||||||
|
btcutil.Amount, btcutil.Amount, error) {
|
||||||
|
|
||||||
|
// Get both parties' balances from the latest commitment.
|
||||||
|
ourBalance := localCommit.LocalBalance.ToSatoshis()
|
||||||
|
theirBalance := localCommit.RemoteBalance.ToSatoshis()
|
||||||
|
|
||||||
|
// We'll make sure we account for the complete balance by adding the
|
||||||
|
// current dangling commitment fee to the balance of the initiator.
|
||||||
|
initiatorDelta := localCommit.CommitFee
|
||||||
|
|
||||||
|
// Since the initiator's balance also is stored after subtracting the
|
||||||
|
// anchor values, add that back in case this was an anchor commitment.
|
||||||
|
if chanType.HasAnchors() {
|
||||||
|
initiatorDelta += 2 * anchorSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// The initiator will pay the full coop close fee, subtract that value
|
||||||
|
// from their balance.
|
||||||
|
initiatorDelta -= coopCloseFee
|
||||||
|
|
||||||
|
if isInitiator {
|
||||||
|
ourBalance += initiatorDelta
|
||||||
|
} else {
|
||||||
|
theirBalance += initiatorDelta
|
||||||
|
}
|
||||||
|
|
||||||
|
// During fee negotiation it should always be verified that the
|
||||||
|
// initiator can pay the proposed fee, but we do a sanity check just to
|
||||||
|
// be sure here.
|
||||||
|
if ourBalance < 0 || theirBalance < 0 {
|
||||||
|
return 0, 0, fmt.Errorf("initiator cannot afford proposed " +
|
||||||
|
"coop close fee")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ourBalance, theirBalance, nil
|
||||||
|
}
|
||||||
|
|
||||||
// genHtlcScript generates the proper P2WSH public key scripts for the HTLC
|
// genHtlcScript generates the proper P2WSH public key scripts for the HTLC
|
||||||
// output modified by two-bits denoting if this is an incoming HTLC, and if the
|
// output modified by two-bits denoting if this is an incoming HTLC, and if the
|
||||||
// HTLC is being applied to their commitment transaction or ours.
|
// HTLC is being applied to their commitment transaction or ours.
|
||||||
|
@ -112,12 +112,12 @@ const (
|
|||||||
// AnchorsRequired is a required feature bit that signals that the node
|
// AnchorsRequired is a required feature bit that signals that the node
|
||||||
// requires channels to be made using commitments having anchor
|
// requires channels to be made using commitments having anchor
|
||||||
// outputs.
|
// outputs.
|
||||||
AnchorsRequired FeatureBit = 1336
|
AnchorsRequired FeatureBit = 20
|
||||||
|
|
||||||
// AnchorsRequired is an optional feature bit that signals that the
|
// AnchorsRequired is an optional feature bit that signals that the
|
||||||
// node supports channels to be made using commitments having anchor
|
// node supports channels to be made using commitments having anchor
|
||||||
// outputs.
|
// outputs.
|
||||||
AnchorsOptional FeatureBit = 1337
|
AnchorsOptional FeatureBit = 21
|
||||||
|
|
||||||
// maxAllowedSize is a maximum allowed size of feature vector.
|
// maxAllowedSize is a maximum allowed size of feature vector.
|
||||||
//
|
//
|
||||||
|
@ -2785,11 +2785,11 @@ func (p *Brontide) handleCloseMsg(msg *closeMsg) {
|
|||||||
func (p *Brontide) HandleLocalCloseChanReqs(req *htlcswitch.ChanClose) {
|
func (p *Brontide) HandleLocalCloseChanReqs(req *htlcswitch.ChanClose) {
|
||||||
select {
|
select {
|
||||||
case p.localCloseChanReqs <- req:
|
case p.localCloseChanReqs <- req:
|
||||||
peerLog.Infof("Local close channel request delivered to peer: %v",
|
peerLog.Infof("Local close channel request delivered to "+
|
||||||
p.PubKey())
|
"peer: %x", p.PubKey())
|
||||||
case <-p.quit:
|
case <-p.quit:
|
||||||
peerLog.Infof("Unable to deliver local close channel request to peer "+
|
peerLog.Infof("Unable to deliver local close channel request "+
|
||||||
"%x", p.PubKey())
|
"to peer %x", p.PubKey())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user