You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1005 lines
28 KiB
1005 lines
28 KiB
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 |
|
}
|
|
|