mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
403d37f426
In this commit, we fix an issue that would cause peers running lnd 0.12 to not be able to connect to existing peers due to a feature bit compatibility issue. In a recent PR we started to downgrade our required feature bit for static key from required to optional, if we had a legacy (non-tweakless) open with the peer then we would unset the required bit and set the optional bit to ensure we could still connect to them. The change implementing this new version of downgrade failed _also_ unset the bit (the required bit) in the "legacy global" feature bit section. This caused the `RawFeatureVector.Merge` method to fail as we would have the required bit set in the `GlobalFeatures` section, but the optional bit set in the `Features` section. The `Merge` method ensures that a required and optional bit can't be set in two different locations for the same feature. This PR fixes this issue by also unsetting the bit in the `GlobalFeatures` field in the init message. Fixes #4871
1006 lines
28 KiB
Go
1006 lines
28 KiB
Go
package peer
|
|
|
|
import (
|
|
"bytes"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
|
"github.com/lightningnetwork/lnd/lntest/mock"
|
|
"github.com/lightningnetwork/lnd/lnwallet/chancloser"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/lightningnetwork/lnd/pool"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var (
|
|
// p2SHAddress is a valid pay to script hash address.
|
|
p2SHAddress = "2NBFNJTktNa7GZusGbDbGKRZTxdK9VVez3n"
|
|
|
|
// p2wshAddress is a valid pay to witness script hash address.
|
|
p2wshAddress = "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3"
|
|
)
|
|
|
|
// TestPeerChannelClosureAcceptFeeResponder tests the shutdown responder's
|
|
// behavior if we can agree on the fee immediately.
|
|
func TestPeerChannelClosureAcceptFeeResponder(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
notifier := &mock.ChainNotifier{
|
|
SpendChan: make(chan *chainntnfs.SpendDetail),
|
|
EpochChan: make(chan *chainntnfs.BlockEpoch),
|
|
ConfChan: make(chan *chainntnfs.TxConfirmation),
|
|
}
|
|
broadcastTxChan := make(chan *wire.MsgTx)
|
|
|
|
alicePeer, bobChan, cleanUp, err := createTestPeer(
|
|
notifier, broadcastTxChan, noUpdate,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
chanID := lnwire.NewChanIDFromOutPoint(bobChan.ChannelPoint())
|
|
|
|
// We send a shutdown request to Alice. She will now be the responding
|
|
// node in this shutdown procedure. We first expect Alice to answer
|
|
// this shutdown request with a Shutdown message.
|
|
alicePeer.chanCloseMsgs <- &closeMsg{
|
|
cid: chanID,
|
|
msg: lnwire.NewShutdown(chanID, dummyDeliveryScript),
|
|
}
|
|
|
|
var msg lnwire.Message
|
|
select {
|
|
case outMsg := <-alicePeer.outgoingQueue:
|
|
msg = outMsg.msg
|
|
case <-time.After(timeout):
|
|
t.Fatalf("did not receive shutdown message")
|
|
}
|
|
|
|
shutdownMsg, ok := msg.(*lnwire.Shutdown)
|
|
if !ok {
|
|
t.Fatalf("expected Shutdown message, got %T", msg)
|
|
}
|
|
|
|
respDeliveryScript := shutdownMsg.Address
|
|
|
|
// Alice will then send a ClosingSigned message, indicating her proposed
|
|
// closing transaction fee. Alice sends the ClosingSigned message as she is
|
|
// the initiator of the channel.
|
|
select {
|
|
case outMsg := <-alicePeer.outgoingQueue:
|
|
msg = outMsg.msg
|
|
case <-time.After(timeout):
|
|
t.Fatalf("did not receive ClosingSigned message")
|
|
}
|
|
|
|
respClosingSigned, ok := msg.(*lnwire.ClosingSigned)
|
|
if !ok {
|
|
t.Fatalf("expected ClosingSigned message, got %T", msg)
|
|
}
|
|
|
|
// We accept the fee, and send a ClosingSigned with the same fee back,
|
|
// so she knows we agreed.
|
|
aliceFee := respClosingSigned.FeeSatoshis
|
|
bobSig, _, _, err := bobChan.CreateCloseProposal(
|
|
aliceFee, dummyDeliveryScript, respDeliveryScript,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("error creating close proposal: %v", err)
|
|
}
|
|
|
|
parsedSig, err := lnwire.NewSigFromSignature(bobSig)
|
|
if err != nil {
|
|
t.Fatalf("error parsing signature: %v", err)
|
|
}
|
|
closingSigned := lnwire.NewClosingSigned(chanID, aliceFee, parsedSig)
|
|
alicePeer.chanCloseMsgs <- &closeMsg{
|
|
cid: chanID,
|
|
msg: closingSigned,
|
|
}
|
|
|
|
// Alice should now see that we agreed on the fee, and should broadcast the
|
|
// closing transaction.
|
|
select {
|
|
case <-broadcastTxChan:
|
|
case <-time.After(timeout):
|
|
t.Fatalf("closing tx not broadcast")
|
|
}
|
|
|
|
// Need to pull the remaining message off of Alice's outgoing queue.
|
|
select {
|
|
case outMsg := <-alicePeer.outgoingQueue:
|
|
msg = outMsg.msg
|
|
case <-time.After(timeout):
|
|
t.Fatalf("did not receive ClosingSigned message")
|
|
}
|
|
if _, ok := msg.(*lnwire.ClosingSigned); !ok {
|
|
t.Fatalf("expected ClosingSigned message, got %T", msg)
|
|
}
|
|
|
|
// Alice should be waiting in a goroutine for a confirmation.
|
|
notifier.ConfChan <- &chainntnfs.TxConfirmation{}
|
|
}
|
|
|
|
// TestPeerChannelClosureAcceptFeeInitiator tests the shutdown initiator's
|
|
// behavior if we can agree on the fee immediately.
|
|
func TestPeerChannelClosureAcceptFeeInitiator(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
notifier := &mock.ChainNotifier{
|
|
SpendChan: make(chan *chainntnfs.SpendDetail),
|
|
EpochChan: make(chan *chainntnfs.BlockEpoch),
|
|
ConfChan: make(chan *chainntnfs.TxConfirmation),
|
|
}
|
|
broadcastTxChan := make(chan *wire.MsgTx)
|
|
|
|
alicePeer, bobChan, cleanUp, err := createTestPeer(
|
|
notifier, broadcastTxChan, noUpdate,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// We make Alice send a shutdown request.
|
|
updateChan := make(chan interface{}, 1)
|
|
errChan := make(chan error, 1)
|
|
closeCommand := &htlcswitch.ChanClose{
|
|
CloseType: htlcswitch.CloseRegular,
|
|
ChanPoint: bobChan.ChannelPoint(),
|
|
Updates: updateChan,
|
|
TargetFeePerKw: 12500,
|
|
Err: errChan,
|
|
}
|
|
alicePeer.localCloseChanReqs <- closeCommand
|
|
|
|
// We can now pull a Shutdown message off of Alice's outgoingQueue.
|
|
var msg lnwire.Message
|
|
select {
|
|
case outMsg := <-alicePeer.outgoingQueue:
|
|
msg = outMsg.msg
|
|
case <-time.After(timeout):
|
|
t.Fatalf("did not receive shutdown request")
|
|
}
|
|
|
|
shutdownMsg, ok := msg.(*lnwire.Shutdown)
|
|
if !ok {
|
|
t.Fatalf("expected Shutdown message, got %T", msg)
|
|
}
|
|
|
|
aliceDeliveryScript := shutdownMsg.Address
|
|
|
|
// Bob will respond with his own Shutdown message.
|
|
chanID := shutdownMsg.ChannelID
|
|
alicePeer.chanCloseMsgs <- &closeMsg{
|
|
cid: chanID,
|
|
msg: lnwire.NewShutdown(chanID,
|
|
dummyDeliveryScript),
|
|
}
|
|
|
|
// Alice will reply with a ClosingSigned here.
|
|
select {
|
|
case outMsg := <-alicePeer.outgoingQueue:
|
|
msg = outMsg.msg
|
|
case <-time.After(timeout):
|
|
t.Fatalf("did not receive closing signed message")
|
|
}
|
|
closingSignedMsg, ok := msg.(*lnwire.ClosingSigned)
|
|
if !ok {
|
|
t.Fatalf("expected to receive closing signed message, got %T", msg)
|
|
}
|
|
|
|
// Bob should reply with the exact same fee in his next ClosingSigned
|
|
// message.
|
|
bobFee := closingSignedMsg.FeeSatoshis
|
|
bobSig, _, _, err := bobChan.CreateCloseProposal(
|
|
bobFee, dummyDeliveryScript, aliceDeliveryScript,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to create close proposal: %v", err)
|
|
}
|
|
parsedSig, err := lnwire.NewSigFromSignature(bobSig)
|
|
if err != nil {
|
|
t.Fatalf("unable to parse signature: %v", err)
|
|
}
|
|
|
|
closingSigned := lnwire.NewClosingSigned(shutdownMsg.ChannelID,
|
|
bobFee, parsedSig)
|
|
alicePeer.chanCloseMsgs <- &closeMsg{
|
|
cid: chanID,
|
|
msg: closingSigned,
|
|
}
|
|
|
|
// Alice should accept Bob's fee, broadcast the cooperative close tx, and
|
|
// send a ClosingSigned message back to Bob.
|
|
|
|
// Alice should now broadcast the closing transaction.
|
|
select {
|
|
case <-broadcastTxChan:
|
|
case <-time.After(timeout):
|
|
t.Fatalf("closing tx not broadcast")
|
|
}
|
|
|
|
// Alice should respond with the ClosingSigned they both agreed upon.
|
|
select {
|
|
case outMsg := <-alicePeer.outgoingQueue:
|
|
msg = outMsg.msg
|
|
case <-time.After(timeout):
|
|
t.Fatalf("did not receive closing signed message")
|
|
}
|
|
|
|
closingSignedMsg, ok = msg.(*lnwire.ClosingSigned)
|
|
if !ok {
|
|
t.Fatalf("expected ClosingSigned message, got %T", msg)
|
|
}
|
|
|
|
if closingSignedMsg.FeeSatoshis != bobFee {
|
|
t.Fatalf("expected ClosingSigned fee to be %v, instead got %v",
|
|
bobFee, closingSignedMsg.FeeSatoshis)
|
|
}
|
|
|
|
// Alice should be waiting on a single confirmation for the coop close tx.
|
|
notifier.ConfChan <- &chainntnfs.TxConfirmation{}
|
|
}
|
|
|
|
// TestPeerChannelClosureFeeNegotiationsResponder tests the shutdown
|
|
// responder's behavior in the case where we must do several rounds of fee
|
|
// negotiation before we agree on a fee.
|
|
func TestPeerChannelClosureFeeNegotiationsResponder(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
notifier := &mock.ChainNotifier{
|
|
SpendChan: make(chan *chainntnfs.SpendDetail),
|
|
EpochChan: make(chan *chainntnfs.BlockEpoch),
|
|
ConfChan: make(chan *chainntnfs.TxConfirmation),
|
|
}
|
|
broadcastTxChan := make(chan *wire.MsgTx)
|
|
|
|
alicePeer, bobChan, cleanUp, err := createTestPeer(
|
|
notifier, broadcastTxChan, noUpdate,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
chanID := lnwire.NewChanIDFromOutPoint(bobChan.ChannelPoint())
|
|
|
|
// Bob sends a shutdown request to Alice. She will now be the responding
|
|
// node in this shutdown procedure. We first expect Alice to answer this
|
|
// Shutdown request with a Shutdown message.
|
|
alicePeer.chanCloseMsgs <- &closeMsg{
|
|
cid: chanID,
|
|
msg: lnwire.NewShutdown(chanID,
|
|
dummyDeliveryScript),
|
|
}
|
|
|
|
var msg lnwire.Message
|
|
select {
|
|
case outMsg := <-alicePeer.outgoingQueue:
|
|
msg = outMsg.msg
|
|
case <-time.After(timeout):
|
|
t.Fatalf("did not receive shutdown message")
|
|
}
|
|
|
|
shutdownMsg, ok := msg.(*lnwire.Shutdown)
|
|
if !ok {
|
|
t.Fatalf("expected Shutdown message, got %T", msg)
|
|
}
|
|
|
|
aliceDeliveryScript := shutdownMsg.Address
|
|
|
|
// As Alice is the channel initiator, she will send her ClosingSigned
|
|
// message.
|
|
select {
|
|
case outMsg := <-alicePeer.outgoingQueue:
|
|
msg = outMsg.msg
|
|
case <-time.After(timeout):
|
|
t.Fatalf("did not receive closing signed message")
|
|
}
|
|
|
|
aliceClosingSigned, ok := msg.(*lnwire.ClosingSigned)
|
|
if !ok {
|
|
t.Fatalf("expected ClosingSigned message, got %T", msg)
|
|
}
|
|
|
|
// Bob doesn't agree with the fee and will send one back that's 2.5x.
|
|
preferredRespFee := aliceClosingSigned.FeeSatoshis
|
|
increasedFee := btcutil.Amount(float64(preferredRespFee) * 2.5)
|
|
bobSig, _, _, err := bobChan.CreateCloseProposal(
|
|
increasedFee, dummyDeliveryScript, aliceDeliveryScript,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("error creating close proposal: %v", err)
|
|
}
|
|
|
|
parsedSig, err := lnwire.NewSigFromSignature(bobSig)
|
|
if err != nil {
|
|
t.Fatalf("error parsing signature: %v", err)
|
|
}
|
|
closingSigned := lnwire.NewClosingSigned(chanID, increasedFee, parsedSig)
|
|
alicePeer.chanCloseMsgs <- &closeMsg{
|
|
cid: chanID,
|
|
msg: closingSigned,
|
|
}
|
|
|
|
// Alice will now see the new fee we propose, but with current settings it
|
|
// won't accept it immediately as it differs too much by its ideal fee. We
|
|
// should get a new proposal back, which should have the average fee rate
|
|
// proposed.
|
|
select {
|
|
case outMsg := <-alicePeer.outgoingQueue:
|
|
msg = outMsg.msg
|
|
case <-time.After(timeout):
|
|
t.Fatalf("did not receive closing signed message")
|
|
}
|
|
|
|
aliceClosingSigned, ok = msg.(*lnwire.ClosingSigned)
|
|
if !ok {
|
|
t.Fatalf("expected ClosingSigned message, got %T", msg)
|
|
}
|
|
|
|
// The fee sent by Alice should be less than the fee Bob just sent as Alice
|
|
// should attempt to compromise.
|
|
aliceFee := aliceClosingSigned.FeeSatoshis
|
|
if aliceFee > increasedFee {
|
|
t.Fatalf("new fee should be less than our fee: new=%v, "+
|
|
"prior=%v", aliceFee, increasedFee)
|
|
}
|
|
lastFeeResponder := aliceFee
|
|
|
|
// We try negotiating a 2.1x fee, which should also be rejected.
|
|
increasedFee = btcutil.Amount(float64(preferredRespFee) * 2.1)
|
|
bobSig, _, _, err = bobChan.CreateCloseProposal(
|
|
increasedFee, dummyDeliveryScript, aliceDeliveryScript,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("error creating close proposal: %v", err)
|
|
}
|
|
|
|
parsedSig, err = lnwire.NewSigFromSignature(bobSig)
|
|
if err != nil {
|
|
t.Fatalf("error parsing signature: %v", err)
|
|
}
|
|
closingSigned = lnwire.NewClosingSigned(chanID, increasedFee, parsedSig)
|
|
alicePeer.chanCloseMsgs <- &closeMsg{
|
|
cid: chanID,
|
|
msg: closingSigned,
|
|
}
|
|
|
|
// Bob's latest proposal still won't be accepted and Alice should send over
|
|
// a new ClosingSigned message. It should be the average of what Bob and
|
|
// Alice each proposed last time.
|
|
select {
|
|
case outMsg := <-alicePeer.outgoingQueue:
|
|
msg = outMsg.msg
|
|
case <-time.After(timeout):
|
|
t.Fatalf("did not receive closing signed message")
|
|
}
|
|
|
|
aliceClosingSigned, ok = msg.(*lnwire.ClosingSigned)
|
|
if !ok {
|
|
t.Fatalf("expected ClosingSigned message, got %T", msg)
|
|
}
|
|
|
|
// Alice should inch towards Bob's fee, in order to compromise.
|
|
// Additionally, this fee should be less than the fee Bob sent before.
|
|
aliceFee = aliceClosingSigned.FeeSatoshis
|
|
if aliceFee < lastFeeResponder {
|
|
t.Fatalf("new fee should be greater than prior: new=%v, "+
|
|
"prior=%v", aliceFee, lastFeeResponder)
|
|
}
|
|
if aliceFee > increasedFee {
|
|
t.Fatalf("new fee should be less than Bob's fee: new=%v, "+
|
|
"prior=%v", aliceFee, increasedFee)
|
|
}
|
|
|
|
// Finally, Bob will accept the fee by echoing back the same fee that Alice
|
|
// just sent over.
|
|
bobSig, _, _, err = bobChan.CreateCloseProposal(
|
|
aliceFee, dummyDeliveryScript, aliceDeliveryScript,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("error creating close proposal: %v", err)
|
|
}
|
|
|
|
parsedSig, err = lnwire.NewSigFromSignature(bobSig)
|
|
if err != nil {
|
|
t.Fatalf("error parsing signature: %v", err)
|
|
}
|
|
closingSigned = lnwire.NewClosingSigned(chanID, aliceFee, parsedSig)
|
|
alicePeer.chanCloseMsgs <- &closeMsg{
|
|
cid: chanID,
|
|
msg: closingSigned,
|
|
}
|
|
|
|
// Alice will now see that Bob agreed on the fee, and broadcast the coop
|
|
// close transaction.
|
|
select {
|
|
case <-broadcastTxChan:
|
|
case <-time.After(timeout):
|
|
t.Fatalf("closing tx not broadcast")
|
|
}
|
|
|
|
// Alice should respond with the ClosingSigned they both agreed upon.
|
|
select {
|
|
case outMsg := <-alicePeer.outgoingQueue:
|
|
msg = outMsg.msg
|
|
case <-time.After(timeout):
|
|
t.Fatalf("did not receive closing signed message")
|
|
}
|
|
if _, ok := msg.(*lnwire.ClosingSigned); !ok {
|
|
t.Fatalf("expected to receive closing signed message, got %T", msg)
|
|
}
|
|
|
|
// Alice should be waiting on a single confirmation for the coop close tx.
|
|
notifier.ConfChan <- &chainntnfs.TxConfirmation{}
|
|
}
|
|
|
|
// TestPeerChannelClosureFeeNegotiationsInitiator tests the shutdown
|
|
// initiator's behavior in the case where we must do several rounds of fee
|
|
// negotiation before we agree on a fee.
|
|
func TestPeerChannelClosureFeeNegotiationsInitiator(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
notifier := &mock.ChainNotifier{
|
|
SpendChan: make(chan *chainntnfs.SpendDetail),
|
|
EpochChan: make(chan *chainntnfs.BlockEpoch),
|
|
ConfChan: make(chan *chainntnfs.TxConfirmation),
|
|
}
|
|
broadcastTxChan := make(chan *wire.MsgTx)
|
|
|
|
alicePeer, bobChan, cleanUp, err := createTestPeer(
|
|
notifier, broadcastTxChan, noUpdate,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// We make the initiator send a shutdown request.
|
|
updateChan := make(chan interface{}, 1)
|
|
errChan := make(chan error, 1)
|
|
closeCommand := &htlcswitch.ChanClose{
|
|
CloseType: htlcswitch.CloseRegular,
|
|
ChanPoint: bobChan.ChannelPoint(),
|
|
Updates: updateChan,
|
|
TargetFeePerKw: 12500,
|
|
Err: errChan,
|
|
}
|
|
|
|
alicePeer.localCloseChanReqs <- closeCommand
|
|
|
|
// Alice should now send a Shutdown request to Bob.
|
|
var msg lnwire.Message
|
|
select {
|
|
case outMsg := <-alicePeer.outgoingQueue:
|
|
msg = outMsg.msg
|
|
case <-time.After(timeout):
|
|
t.Fatalf("did not receive shutdown request")
|
|
}
|
|
|
|
shutdownMsg, ok := msg.(*lnwire.Shutdown)
|
|
if !ok {
|
|
t.Fatalf("expected Shutdown message, got %T", msg)
|
|
}
|
|
|
|
aliceDeliveryScript := shutdownMsg.Address
|
|
|
|
// Bob will answer the Shutdown message with his own Shutdown.
|
|
chanID := lnwire.NewChanIDFromOutPoint(bobChan.ChannelPoint())
|
|
respShutdown := lnwire.NewShutdown(chanID, dummyDeliveryScript)
|
|
alicePeer.chanCloseMsgs <- &closeMsg{
|
|
cid: chanID,
|
|
msg: respShutdown,
|
|
}
|
|
|
|
// Alice should now respond with a ClosingSigned message with her ideal
|
|
// fee rate.
|
|
select {
|
|
case outMsg := <-alicePeer.outgoingQueue:
|
|
msg = outMsg.msg
|
|
case <-time.After(timeout):
|
|
t.Fatalf("did not receive closing signed")
|
|
}
|
|
closingSignedMsg, ok := msg.(*lnwire.ClosingSigned)
|
|
if !ok {
|
|
t.Fatalf("expected ClosingSigned message, got %T", msg)
|
|
}
|
|
|
|
idealFeeRate := closingSignedMsg.FeeSatoshis
|
|
lastReceivedFee := idealFeeRate
|
|
|
|
increasedFee := btcutil.Amount(float64(idealFeeRate) * 2.1)
|
|
lastSentFee := increasedFee
|
|
|
|
bobSig, _, _, err := bobChan.CreateCloseProposal(
|
|
increasedFee, dummyDeliveryScript, aliceDeliveryScript,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("error creating close proposal: %v", err)
|
|
}
|
|
|
|
parsedSig, err := lnwire.NewSigFromSignature(bobSig)
|
|
if err != nil {
|
|
t.Fatalf("unable to parse signature: %v", err)
|
|
}
|
|
|
|
closingSigned := lnwire.NewClosingSigned(chanID, increasedFee, parsedSig)
|
|
alicePeer.chanCloseMsgs <- &closeMsg{
|
|
cid: chanID,
|
|
msg: closingSigned,
|
|
}
|
|
|
|
// It still won't be accepted, and we should get a new proposal, the
|
|
// average of what we proposed, and what they proposed last time.
|
|
select {
|
|
case outMsg := <-alicePeer.outgoingQueue:
|
|
msg = outMsg.msg
|
|
case <-time.After(timeout):
|
|
t.Fatalf("did not receive closing signed")
|
|
}
|
|
closingSignedMsg, ok = msg.(*lnwire.ClosingSigned)
|
|
if !ok {
|
|
t.Fatalf("expected ClosingSigned message, got %T", msg)
|
|
}
|
|
|
|
aliceFee := closingSignedMsg.FeeSatoshis
|
|
if aliceFee < lastReceivedFee {
|
|
t.Fatalf("new fee should be greater than prior: new=%v, old=%v",
|
|
aliceFee, lastReceivedFee)
|
|
}
|
|
if aliceFee > lastSentFee {
|
|
t.Fatalf("new fee should be less than our fee: new=%v, old=%v",
|
|
aliceFee, lastSentFee)
|
|
}
|
|
|
|
lastReceivedFee = aliceFee
|
|
|
|
// We'll try negotiating a 1.5x fee, which should also be rejected.
|
|
increasedFee = btcutil.Amount(float64(idealFeeRate) * 1.5)
|
|
lastSentFee = increasedFee
|
|
|
|
bobSig, _, _, err = bobChan.CreateCloseProposal(
|
|
increasedFee, dummyDeliveryScript, aliceDeliveryScript,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("error creating close proposal: %v", err)
|
|
}
|
|
|
|
parsedSig, err = lnwire.NewSigFromSignature(bobSig)
|
|
if err != nil {
|
|
t.Fatalf("error parsing signature: %v", err)
|
|
}
|
|
|
|
closingSigned = lnwire.NewClosingSigned(chanID, increasedFee, parsedSig)
|
|
alicePeer.chanCloseMsgs <- &closeMsg{
|
|
cid: chanID,
|
|
msg: closingSigned,
|
|
}
|
|
|
|
// Alice won't accept Bob's new proposal, and Bob should receive a new
|
|
// proposal which is the average of what Bob proposed and Alice proposed
|
|
// last time.
|
|
select {
|
|
case outMsg := <-alicePeer.outgoingQueue:
|
|
msg = outMsg.msg
|
|
case <-time.After(timeout):
|
|
t.Fatalf("did not receive closing signed")
|
|
}
|
|
closingSignedMsg, ok = msg.(*lnwire.ClosingSigned)
|
|
if !ok {
|
|
t.Fatalf("expected ClosingSigned message, got %T", msg)
|
|
}
|
|
|
|
aliceFee = closingSignedMsg.FeeSatoshis
|
|
if aliceFee < lastReceivedFee {
|
|
t.Fatalf("new fee should be greater than prior: new=%v, old=%v",
|
|
aliceFee, lastReceivedFee)
|
|
}
|
|
if aliceFee > lastSentFee {
|
|
t.Fatalf("new fee should be less than Bob's fee: new=%v, old=%v",
|
|
aliceFee, lastSentFee)
|
|
}
|
|
|
|
// Bob will now accept their fee by sending back a ClosingSigned message
|
|
// with an identical fee.
|
|
bobSig, _, _, err = bobChan.CreateCloseProposal(
|
|
aliceFee, dummyDeliveryScript, aliceDeliveryScript,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("error creating close proposal: %v", err)
|
|
}
|
|
|
|
parsedSig, err = lnwire.NewSigFromSignature(bobSig)
|
|
if err != nil {
|
|
t.Fatalf("error parsing signature: %v", err)
|
|
}
|
|
closingSigned = lnwire.NewClosingSigned(chanID, aliceFee, parsedSig)
|
|
alicePeer.chanCloseMsgs <- &closeMsg{
|
|
cid: chanID,
|
|
msg: closingSigned,
|
|
}
|
|
|
|
// Wait for closing tx to be broadcasted.
|
|
select {
|
|
case <-broadcastTxChan:
|
|
case <-time.After(timeout):
|
|
t.Fatalf("closing tx not broadcast")
|
|
}
|
|
|
|
// Alice should respond with the ClosingSigned they both agreed upon.
|
|
select {
|
|
case outMsg := <-alicePeer.outgoingQueue:
|
|
msg = outMsg.msg
|
|
case <-time.After(timeout):
|
|
t.Fatalf("did not receive closing signed message")
|
|
}
|
|
if _, ok := msg.(*lnwire.ClosingSigned); !ok {
|
|
t.Fatalf("expected to receive closing signed message, got %T", msg)
|
|
}
|
|
|
|
// Alice should be waiting on a single confirmation for the coop close tx.
|
|
notifier.ConfChan <- &chainntnfs.TxConfirmation{}
|
|
}
|
|
|
|
// TestChooseDeliveryScript tests that chooseDeliveryScript correctly errors
|
|
// when upfront and user set scripts that do not match are provided, allows
|
|
// matching values and returns appropriate values in the case where one or none
|
|
// are set.
|
|
func TestChooseDeliveryScript(t *testing.T) {
|
|
// generate non-zero scripts for testing.
|
|
script1 := genScript(t, p2SHAddress)
|
|
script2 := genScript(t, p2wshAddress)
|
|
|
|
tests := []struct {
|
|
name string
|
|
userScript lnwire.DeliveryAddress
|
|
shutdownScript lnwire.DeliveryAddress
|
|
expectedScript lnwire.DeliveryAddress
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "Neither set",
|
|
userScript: nil,
|
|
shutdownScript: nil,
|
|
expectedScript: nil,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Both set and equal",
|
|
userScript: script1,
|
|
shutdownScript: script1,
|
|
expectedScript: script1,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Both set and not equal",
|
|
userScript: script1,
|
|
shutdownScript: script2,
|
|
expectedScript: nil,
|
|
expectedError: chancloser.ErrUpfrontShutdownScriptMismatch,
|
|
},
|
|
{
|
|
name: "Only upfront script",
|
|
userScript: nil,
|
|
shutdownScript: script1,
|
|
expectedScript: script1,
|
|
expectedError: nil,
|
|
},
|
|
{
|
|
name: "Only user script",
|
|
userScript: script2,
|
|
shutdownScript: nil,
|
|
expectedScript: script2,
|
|
expectedError: nil,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
test := test
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
script, err := chooseDeliveryScript(
|
|
test.shutdownScript, test.userScript,
|
|
)
|
|
if err != test.expectedError {
|
|
t.Fatalf("Expected: %v, got: %v", test.expectedError, err)
|
|
}
|
|
|
|
if !bytes.Equal(script, test.expectedScript) {
|
|
t.Fatalf("Expected: %x, got: %x", test.expectedScript, script)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestCustomShutdownScript tests that the delivery script of a shutdown
|
|
// message can be set to a specified address. It checks that setting a close
|
|
// script fails for channels which have an upfront shutdown script already set.
|
|
func TestCustomShutdownScript(t *testing.T) {
|
|
script := genScript(t, p2SHAddress)
|
|
|
|
// setShutdown is a function which sets the upfront shutdown address for
|
|
// the local channel.
|
|
setShutdown := func(a, b *channeldb.OpenChannel) {
|
|
a.LocalShutdownScript = script
|
|
b.RemoteShutdownScript = script
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
|
|
// update is a function used to set values on the channel set up for the
|
|
// test. It is used to set values for upfront shutdown addresses.
|
|
update func(a, b *channeldb.OpenChannel)
|
|
|
|
// userCloseScript is the address specified by the user.
|
|
userCloseScript lnwire.DeliveryAddress
|
|
|
|
// expectedScript is the address we expect to be set on the shutdown
|
|
// message.
|
|
expectedScript lnwire.DeliveryAddress
|
|
|
|
// expectedError is the error we expect, if any.
|
|
expectedError error
|
|
}{
|
|
{
|
|
name: "User set script",
|
|
update: noUpdate,
|
|
userCloseScript: script,
|
|
expectedScript: script,
|
|
},
|
|
{
|
|
name: "No user set script",
|
|
update: noUpdate,
|
|
},
|
|
{
|
|
name: "Shutdown set, no user script",
|
|
update: setShutdown,
|
|
expectedScript: script,
|
|
},
|
|
{
|
|
name: "Shutdown set, user script matches",
|
|
update: setShutdown,
|
|
userCloseScript: script,
|
|
expectedScript: script,
|
|
},
|
|
{
|
|
name: "Shutdown set, user script different",
|
|
update: setShutdown,
|
|
userCloseScript: []byte("different addr"),
|
|
expectedError: chancloser.ErrUpfrontShutdownScriptMismatch,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
test := test
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
notifier := &mock.ChainNotifier{
|
|
SpendChan: make(chan *chainntnfs.SpendDetail),
|
|
EpochChan: make(chan *chainntnfs.BlockEpoch),
|
|
ConfChan: make(chan *chainntnfs.TxConfirmation),
|
|
}
|
|
broadcastTxChan := make(chan *wire.MsgTx)
|
|
|
|
// Open a channel.
|
|
alicePeer, bobChan, cleanUp, err := createTestPeer(
|
|
notifier, broadcastTxChan, test.update,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("unable to create test channels: %v", err)
|
|
}
|
|
defer cleanUp()
|
|
|
|
// Request initiator to cooperatively close the channel, with
|
|
// a specified delivery address.
|
|
updateChan := make(chan interface{}, 1)
|
|
errChan := make(chan error, 1)
|
|
chanPoint := bobChan.ChannelPoint()
|
|
closeCommand := htlcswitch.ChanClose{
|
|
CloseType: htlcswitch.CloseRegular,
|
|
ChanPoint: chanPoint,
|
|
Updates: updateChan,
|
|
TargetFeePerKw: 12500,
|
|
DeliveryScript: test.userCloseScript,
|
|
Err: errChan,
|
|
}
|
|
|
|
// Send the close command for the correct channel and check that a
|
|
// shutdown message is sent.
|
|
alicePeer.localCloseChanReqs <- &closeCommand
|
|
|
|
var msg lnwire.Message
|
|
select {
|
|
case outMsg := <-alicePeer.outgoingQueue:
|
|
msg = outMsg.msg
|
|
case <-time.After(timeout):
|
|
t.Fatalf("did not receive shutdown message")
|
|
case err := <-errChan:
|
|
// Fail if we do not expect an error.
|
|
if err != test.expectedError {
|
|
t.Fatalf("error closing channel: %v", err)
|
|
}
|
|
|
|
// Terminate the test early if have received an error, no
|
|
// further action is expected.
|
|
return
|
|
}
|
|
|
|
// Check that we have received a shutdown message.
|
|
shutdownMsg, ok := msg.(*lnwire.Shutdown)
|
|
if !ok {
|
|
t.Fatalf("expected shutdown message, got %T", msg)
|
|
}
|
|
|
|
// If the test has not specified an expected address, do not check
|
|
// whether the shutdown address matches. This covers the case where
|
|
// we epect shutdown to a random address and cannot match it.
|
|
if len(test.expectedScript) == 0 {
|
|
return
|
|
}
|
|
|
|
// Check that the Shutdown message includes the expected delivery
|
|
// script.
|
|
if !bytes.Equal(test.expectedScript, shutdownMsg.Address) {
|
|
t.Fatalf("expected delivery script: %x, got: %x",
|
|
test.expectedScript, shutdownMsg.Address)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestStaticRemoteDowngrade tests that we downgrade our static remote feature
|
|
// bit to optional if we have legacy channels with a peer. This ensures that
|
|
// we can stay connected to peers that don't support the feature bit that we
|
|
// have channels with.
|
|
func TestStaticRemoteDowngrade(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var (
|
|
// We set the same legacy feature bits for all tests, since
|
|
// these are not relevant to our test scenario
|
|
rawLegacy = lnwire.NewRawFeatureVector(
|
|
lnwire.UpfrontShutdownScriptOptional,
|
|
)
|
|
legacy = lnwire.NewFeatureVector(rawLegacy, nil)
|
|
|
|
legacyCombinedOptional = lnwire.NewRawFeatureVector(
|
|
lnwire.UpfrontShutdownScriptOptional,
|
|
lnwire.StaticRemoteKeyOptional,
|
|
)
|
|
|
|
rawFeatureOptional = lnwire.NewRawFeatureVector(
|
|
lnwire.StaticRemoteKeyOptional,
|
|
)
|
|
|
|
featureOptional = lnwire.NewFeatureVector(
|
|
rawFeatureOptional, nil,
|
|
)
|
|
|
|
rawFeatureRequired = lnwire.NewRawFeatureVector(
|
|
lnwire.StaticRemoteKeyRequired,
|
|
)
|
|
|
|
featureRequired = lnwire.NewFeatureVector(
|
|
rawFeatureRequired, nil,
|
|
)
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
legacy bool
|
|
features *lnwire.FeatureVector
|
|
expectedInit *lnwire.Init
|
|
}{
|
|
{
|
|
name: "no legacy channel, static optional",
|
|
legacy: false,
|
|
features: featureOptional,
|
|
expectedInit: &lnwire.Init{
|
|
GlobalFeatures: rawLegacy,
|
|
Features: rawFeatureOptional,
|
|
},
|
|
},
|
|
{
|
|
name: "legacy channel, static optional",
|
|
legacy: true,
|
|
features: featureOptional,
|
|
expectedInit: &lnwire.Init{
|
|
GlobalFeatures: rawLegacy,
|
|
Features: rawFeatureOptional,
|
|
},
|
|
},
|
|
{
|
|
name: "no legacy channel, static required",
|
|
legacy: false,
|
|
features: featureRequired,
|
|
expectedInit: &lnwire.Init{
|
|
GlobalFeatures: rawLegacy,
|
|
Features: rawFeatureRequired,
|
|
},
|
|
},
|
|
|
|
// In this case we need to flip our required bit to optional,
|
|
// this should also propagate to the legacy set of feature bits
|
|
// so we have proper consistency: a bit isn't set to optional
|
|
// in one field and required in the other.
|
|
{
|
|
name: "legacy channel, static required",
|
|
legacy: true,
|
|
features: featureRequired,
|
|
expectedInit: &lnwire.Init{
|
|
GlobalFeatures: legacyCombinedOptional,
|
|
Features: rawFeatureOptional,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
test := test
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
writeBufferPool := pool.NewWriteBuffer(
|
|
pool.DefaultWriteBufferGCInterval,
|
|
pool.DefaultWriteBufferExpiryInterval,
|
|
)
|
|
|
|
writePool := pool.NewWrite(
|
|
writeBufferPool, 1, timeout,
|
|
)
|
|
require.NoError(t, writePool.Start())
|
|
|
|
mockConn := newMockConn(t, 1)
|
|
|
|
p := Brontide{
|
|
cfg: Config{
|
|
LegacyFeatures: legacy,
|
|
Features: test.features,
|
|
Conn: mockConn,
|
|
WritePool: writePool,
|
|
},
|
|
}
|
|
|
|
var b bytes.Buffer
|
|
_, err := lnwire.WriteMessage(&b, test.expectedInit, 0)
|
|
require.NoError(t, err)
|
|
|
|
// Send our init message, assert that we write our expected message
|
|
// and shutdown our write pool.
|
|
require.NoError(t, p.sendInitMsg(test.legacy))
|
|
mockConn.assertWrite(b.Bytes())
|
|
require.NoError(t, writePool.Stop())
|
|
})
|
|
}
|
|
}
|
|
|
|
// genScript creates a script paying out to the address provided, which must
|
|
// be a valid address.
|
|
func genScript(t *testing.T, address string) lnwire.DeliveryAddress {
|
|
// Generate an address which can be used for testing.
|
|
deliveryAddr, err := btcutil.DecodeAddress(
|
|
address,
|
|
&chaincfg.TestNet3Params,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("invalid delivery address: %v", err)
|
|
}
|
|
|
|
script, err := txscript.PayToAddrScript(deliveryAddr)
|
|
if err != nil {
|
|
t.Fatalf("cannot create script: %v", err)
|
|
}
|
|
|
|
return script
|
|
}
|