lnd/peer_test.go

522 lines
16 KiB
Go

package main
import (
"testing"
"time"
"github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/txscript"
"github.com/roasbeef/btcd/wire"
)
func disablePeerLogger(t *testing.T) {
peerLog = btclog.Disabled
srvrLog = btclog.Disabled
lnwallet.UseLogger(btclog.Disabled)
htlcswitch.UseLogger(btclog.Disabled)
channeldb.UseLogger(btclog.Disabled)
}
// TestPeerChannelClosureAcceptFeeResponder tests the shutdown responder's
// behavior if we can agree on the fee immediately.
func TestPeerChannelClosureAcceptFeeResponder(t *testing.T) {
disablePeerLogger(t)
t.Parallel()
notifier := &mockNotfier{
confChannel: make(chan *chainntnfs.TxConfirmation),
}
broadcastTxChan := make(chan *wire.MsgTx)
responder, responderChan, initiatorChan, cleanUp, err := createTestPeer(
notifier, broadcastTxChan)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
chanID := lnwire.NewChanIDFromOutPoint(responderChan.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.
responder.shutdownChanReqs <- lnwire.NewShutdown(chanID, dummyDeliveryScript)
var msg lnwire.Message
select {
case outMsg := <-responder.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
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 thereafter send a ClosingSigned message, indicating her
// proposed closing transaction fee.
select {
case outMsg := <-responder.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
t.Fatalf("did not receive ClosingSigned message")
}
responderClosingSigned, 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.
peerFee := responderClosingSigned.FeeSatoshis
initiatorSig, proposedFee, err := initiatorChan.CreateCloseProposal(
peerFee, dummyDeliveryScript, respDeliveryScript)
if err != nil {
t.Fatalf("error creating close proposal: %v", err)
}
initSig := append(initiatorSig, byte(txscript.SigHashAll))
parsedSig, err := btcec.ParseSignature(initSig, btcec.S256())
if err != nil {
t.Fatalf("error parsing signature: %v", err)
}
closingSigned := lnwire.NewClosingSigned(chanID, proposedFee, parsedSig)
responder.closingSignedChanReqs <- closingSigned
// The responder will now see that we agreed on the fee, and broadcast
// the closing transaction.
select {
case <-broadcastTxChan:
case <-time.After(time.Second * 5):
t.Fatalf("closing tx not broadcast")
}
// And the initiator should be waiting for a confirmation notification.
notifier.confChannel <- &chainntnfs.TxConfirmation{}
}
// TestPeerChannelClosureAcceptFeeInitiator tests the shutdown initiator's
// behavior if we can agree on the fee immediately.
func TestPeerChannelClosureAcceptFeeInitiator(t *testing.T) {
disablePeerLogger(t)
t.Parallel()
notifier := &mockNotfier{
confChannel: make(chan *chainntnfs.TxConfirmation),
}
broadcastTxChan := make(chan *wire.MsgTx)
initiator, initiatorChan, responderChan, cleanUp, err := createTestPeer(
notifier, broadcastTxChan)
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 *lnrpc.CloseStatusUpdate, 1)
errChan := make(chan error, 1)
closeCommand := &htlcswitch.ChanClose{
CloseType: htlcswitch.CloseRegular,
ChanPoint: initiatorChan.ChannelPoint(),
Updates: updateChan,
Err: errChan,
}
initiator.localCloseChanReqs <- closeCommand
// We should now be getting the shutdown request.
var msg lnwire.Message
select {
case outMsg := <-initiator.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
t.Fatalf("did not receive shutdown request")
}
shutdownMsg, ok := msg.(*lnwire.Shutdown)
if !ok {
t.Fatalf("expected Shutdown message, got %T", msg)
}
initiatorDeliveryScript := shutdownMsg.Address
// We'll answer the shutdown message with our own Shutdown, and then a
// ClosingSigned message.
chanID := shutdownMsg.ChannelID
initiator.shutdownChanReqs <- lnwire.NewShutdown(chanID,
dummyDeliveryScript)
estimator := lnwallet.StaticFeeEstimator{FeeRate: 50}
feeRate := estimator.EstimateFeePerWeight(1) * 1000
fee := responderChan.CalcFee(feeRate)
closeSig, proposedFee, err := responderChan.CreateCloseProposal(fee,
dummyDeliveryScript, initiatorDeliveryScript)
if err != nil {
t.Fatalf("unable to create close proposal: %v", err)
}
parsedSig, err := btcec.ParseSignature(closeSig, btcec.S256())
if err != nil {
t.Fatalf("unable to parse signature: %v", err)
}
closingSigned := lnwire.NewClosingSigned(shutdownMsg.ChannelID,
proposedFee, parsedSig)
initiator.closingSignedChanReqs <- closingSigned
// And we expect the initiator to accept the fee, and broadcast the
// closing transaction.
select {
case outMsg := <-initiator.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
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 != proposedFee {
t.Fatalf("expected ClosingSigned fee to be %v, instead got %v",
proposedFee, closingSignedMsg.FeeSatoshis)
}
// The initiator will now see that we agreed on the fee, and broadcast
// the closing transaction.
select {
case <-broadcastTxChan:
case <-time.After(time.Second * 5):
t.Fatalf("closing tx not broadcast")
}
// And the initiator should be waiting for a confirmation notification.
notifier.confChannel <- &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) {
disablePeerLogger(t)
t.Parallel()
notifier := &mockNotfier{
confChannel: make(chan *chainntnfs.TxConfirmation),
}
broadcastTxChan := make(chan *wire.MsgTx)
responder, responderChan, initiatorChan, cleanUp, err := createTestPeer(
notifier, broadcastTxChan)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
chanID := lnwire.NewChanIDFromOutPoint(responderChan.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.
responder.shutdownChanReqs <- lnwire.NewShutdown(chanID,
dummyDeliveryScript)
var msg lnwire.Message
select {
case outMsg := <-responder.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
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 thereafter send a ClosingSigned message, indicating her
// proposed closing transaction fee.
select {
case outMsg := <-responder.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
t.Fatalf("did not receive closing signed message")
}
responderClosingSigned, ok := msg.(*lnwire.ClosingSigned)
if !ok {
t.Fatalf("expected ClosingSigned message, got %T", msg)
}
// We don't agree with the fee, and will send back one that's 2.5x.
preferredRespFee := responderClosingSigned.FeeSatoshis
increasedFee := uint64(float64(preferredRespFee) * 2.5)
initiatorSig, proposedFee, err := initiatorChan.CreateCloseProposal(
increasedFee, dummyDeliveryScript, respDeliveryScript,
)
if err != nil {
t.Fatalf("error creating close proposal: %v", err)
}
parsedSig, err := btcec.ParseSignature(initiatorSig, btcec.S256())
if err != nil {
t.Fatalf("error parsing signature: %v", err)
}
closingSigned := lnwire.NewClosingSigned(chanID, proposedFee, parsedSig)
responder.closingSignedChanReqs <- closingSigned
// The responder will see the new fee we propose, but with current
// settings wont't accept anything over 2*FeeRate. We should get a new
// proposal back, which should have the average fee rate proposed.
select {
case outMsg := <-responder.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
t.Fatalf("did not receive closing signed message")
}
responderClosingSigned, ok = msg.(*lnwire.ClosingSigned)
if !ok {
t.Fatalf("expected ClosingSigned message, got %T", msg)
}
avgFee := (preferredRespFee + increasedFee) / 2
peerFee := responderClosingSigned.FeeSatoshis
if peerFee != avgFee {
t.Fatalf("expected ClosingSigned with fee %v, got %v",
proposedFee, responderClosingSigned.FeeSatoshis)
}
// We try negotiating a 2.1x fee, which should also be rejected.
increasedFee = uint64(float64(preferredRespFee) * 2.1)
initiatorSig, proposedFee, err = initiatorChan.CreateCloseProposal(
increasedFee, dummyDeliveryScript, respDeliveryScript,
)
if err != nil {
t.Fatalf("error creating close proposal: %v", err)
}
parsedSig, err = btcec.ParseSignature(initiatorSig, btcec.S256())
if err != nil {
t.Fatalf("error parsing signature: %v", err)
}
closingSigned = lnwire.NewClosingSigned(chanID, proposedFee, parsedSig)
responder.closingSignedChanReqs <- 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 := <-responder.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
t.Fatalf("did not receive closing signed message")
}
responderClosingSigned, ok = msg.(*lnwire.ClosingSigned)
if !ok {
t.Fatalf("expected ClosingSigned message, got %T", msg)
}
avgFee = (peerFee + increasedFee) / 2
peerFee = responderClosingSigned.FeeSatoshis
if peerFee != avgFee {
t.Fatalf("expected ClosingSigned with fee %v, got %v",
proposedFee, responderClosingSigned.FeeSatoshis)
}
// Accept fee.
initiatorSig, proposedFee, err = initiatorChan.CreateCloseProposal(
peerFee, dummyDeliveryScript, respDeliveryScript,
)
if err != nil {
t.Fatalf("error creating close proposal: %v", err)
}
initSig := append(initiatorSig, byte(txscript.SigHashAll))
parsedSig, err = btcec.ParseSignature(initSig, btcec.S256())
if err != nil {
t.Fatalf("error parsing signature: %v", err)
}
closingSigned = lnwire.NewClosingSigned(chanID, proposedFee, parsedSig)
responder.closingSignedChanReqs <- closingSigned
// The responder will now see that we agreed on the fee, and broadcast
// the closing transaction.
select {
case <-broadcastTxChan:
case <-time.After(time.Second * 5):
t.Fatalf("closing tx not broadcast")
}
// And the responder should be waiting for a confirmation notification.
notifier.confChannel <- &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) {
disablePeerLogger(t)
t.Parallel()
notifier := &mockNotfier{
confChannel: make(chan *chainntnfs.TxConfirmation),
}
broadcastTxChan := make(chan *wire.MsgTx)
initiator, initiatorChan, responderChan, cleanUp, err := createTestPeer(
notifier, broadcastTxChan)
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 *lnrpc.CloseStatusUpdate, 1)
errChan := make(chan error, 1)
closeCommand := &htlcswitch.ChanClose{
CloseType: htlcswitch.CloseRegular,
ChanPoint: initiatorChan.ChannelPoint(),
Updates: updateChan,
Err: errChan,
}
initiator.localCloseChanReqs <- closeCommand
// We should now be getting the shutdown request.
var msg lnwire.Message
select {
case outMsg := <-initiator.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
t.Fatalf("did not receive shutdown request")
}
shutdownMsg, ok := msg.(*lnwire.Shutdown)
if !ok {
t.Fatalf("expected Shutdown message, got %T", msg)
}
initiatorDeliveryScript := shutdownMsg.Address
// We'll answer the shutdown message with our own Shutdown, and then a
// ClosingSigned message.
chanID := lnwire.NewChanIDFromOutPoint(initiatorChan.ChannelPoint())
respShutdown := lnwire.NewShutdown(chanID, dummyDeliveryScript)
initiator.shutdownChanReqs <- respShutdown
estimator := lnwallet.StaticFeeEstimator{FeeRate: 50}
initiatorIdealFeeRate := estimator.EstimateFeePerWeight(1) * 1000
initiatorIdealFee := responderChan.CalcFee(initiatorIdealFeeRate)
increasedFee := uint64(float64(initiatorIdealFee) * 2.5)
closeSig, proposedFee, err := responderChan.CreateCloseProposal(
increasedFee, dummyDeliveryScript, initiatorDeliveryScript,
)
if err != nil {
t.Fatalf("unable to create close proposal: %v", err)
}
parsedSig, err := btcec.ParseSignature(closeSig, btcec.S256())
if err != nil {
t.Fatalf("unable to parse signature: %v", err)
}
closingSigned := lnwire.NewClosingSigned(shutdownMsg.ChannelID,
proposedFee, parsedSig)
initiator.closingSignedChanReqs <- closingSigned
// And we expect the initiator to reject the fee, and suggest a lower
// one.
select {
case outMsg := <-initiator.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
t.Fatalf("did not receive closing signed")
}
closingSignedMsg, ok := msg.(*lnwire.ClosingSigned)
if !ok {
t.Fatalf("expected ClosingSigned message, got %T", msg)
}
avgFee := (initiatorIdealFee + increasedFee) / 2
peerFee := closingSignedMsg.FeeSatoshis
if peerFee != avgFee {
t.Fatalf("expected ClosingSigned fee to be %v, instead got %v",
avgFee, peerFee)
}
// We try negotiating a 2.1x fee, which should also be rejected.
increasedFee = uint64(float64(initiatorIdealFee) * 2.1)
responderSig, proposedFee, err := responderChan.CreateCloseProposal(
increasedFee, dummyDeliveryScript, initiatorDeliveryScript,
)
if err != nil {
t.Fatalf("error creating close proposal: %v", err)
}
parsedSig, err = btcec.ParseSignature(responderSig, btcec.S256())
if err != nil {
t.Fatalf("error parsing signature: %v", err)
}
closingSigned = lnwire.NewClosingSigned(chanID, proposedFee, parsedSig)
initiator.closingSignedChanReqs <- 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 := <-initiator.outgoingQueue:
msg = outMsg.msg
case <-time.After(time.Second * 5):
t.Fatalf("did not receive closing signed")
}
initiatorClosingSigned, ok := msg.(*lnwire.ClosingSigned)
if !ok {
t.Fatalf("expected ClosingSigned message, got %T", msg)
}
avgFee = (peerFee + increasedFee) / 2
peerFee = initiatorClosingSigned.FeeSatoshis
if peerFee != avgFee {
t.Fatalf("expected ClosingSigned with fee %v, got %v",
proposedFee, initiatorClosingSigned.FeeSatoshis)
}
// Accept fee.
responderSig, proposedFee, err = responderChan.CreateCloseProposal(
peerFee, dummyDeliveryScript, initiatorDeliveryScript,
)
if err != nil {
t.Fatalf("error creating close proposal: %v", err)
}
respSig := append(responderSig, byte(txscript.SigHashAll))
parsedSig, err = btcec.ParseSignature(respSig, btcec.S256())
if err != nil {
t.Fatalf("error parsing signature: %v", err)
}
closingSigned = lnwire.NewClosingSigned(chanID, proposedFee, parsedSig)
initiator.closingSignedChanReqs <- closingSigned
// Wait for closing tx to be broadcasted.
select {
case <-broadcastTxChan:
case <-time.After(time.Second * 5):
t.Fatalf("closing tx not broadcast")
}
}