package lnd
import (
"bytes"
"testing"
"time"
"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/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
)
var (
p2SHAddress = "2NBFNJTktNa7GZusGbDbGKRZTxdK9VVez3n"
p2wshAddress = "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3"
timeout = time.Second * 5
)
func TestPeerChannelClosureAcceptFeeResponder(t *testing.T) {
t.Parallel()
notifier := &mockNotfier{
confChannel: make(chan *chainntnfs.TxConfirmation),
}
broadcastTxChan := make(chan *wire.MsgTx)
responder, responderChan, initiatorChan, cleanUp, err := createTestPeer(
notifier, broadcastTxChan, noUpdate,
)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
chanID := lnwire.NewChanIDFromOutPoint(responderChan.ChannelPoint())
responder.chanCloseMsgs <- &closeMsg{
cid: chanID,
msg: lnwire.NewShutdown(chanID, dummyDeliveryScript),
}
var msg lnwire.Message
select {
case outMsg := <-responder.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
select {
case outMsg := <-responder.outgoingQueue:
msg = outMsg.msg
case <-time.After(timeout):
t.Fatalf("did not receive ClosingSigned message")
}
responderClosingSigned, ok := msg.(*lnwire.ClosingSigned)
if !ok {
t.Fatalf("expected ClosingSigned message, got %T", msg)
}
peerFee := responderClosingSigned.FeeSatoshis
initiatorSig, _, _, err := initiatorChan.CreateCloseProposal(
peerFee, dummyDeliveryScript, respDeliveryScript,
)
if err != nil {
t.Fatalf("error creating close proposal: %v", err)
}
parsedSig, err := lnwire.NewSigFromSignature(initiatorSig)
if err != nil {
t.Fatalf("error parsing signature: %v", err)
}
closingSigned := lnwire.NewClosingSigned(chanID, peerFee, parsedSig)
responder.chanCloseMsgs <- &closeMsg{
cid: chanID,
msg: closingSigned,
}
select {
case <-broadcastTxChan:
case <-time.After(timeout):
t.Fatalf("closing tx not broadcast")
}
notifier.confChannel <- &chainntnfs.TxConfirmation{}
}
func TestPeerChannelClosureAcceptFeeInitiator(t *testing.T) {
t.Parallel()
notifier := &mockNotfier{
confChannel: make(chan *chainntnfs.TxConfirmation),
}
broadcastTxChan := make(chan *wire.MsgTx)
initiator, initiatorChan, responderChan, cleanUp, err := createTestPeer(
notifier, broadcastTxChan, noUpdate,
)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
updateChan := make(chan interface{}, 1)
errChan := make(chan error, 1)
closeCommand := &htlcswitch.ChanClose{
CloseType: htlcswitch.CloseRegular,
ChanPoint: initiatorChan.ChannelPoint(),
Updates: updateChan,
TargetFeePerKw: 12500,
Err: errChan,
}
initiator.localCloseChanReqs <- closeCommand
var msg lnwire.Message
select {
case outMsg := <-initiator.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)
}
initiatorDeliveryScript := shutdownMsg.Address
chanID := shutdownMsg.ChannelID
initiator.chanCloseMsgs <- &closeMsg{
cid: chanID,
msg: lnwire.NewShutdown(chanID,
dummyDeliveryScript),
}
estimator := chainfee.NewStaticEstimator(12500, 0)
feePerKw, err := estimator.EstimateFeePerKW(1)
if err != nil {
t.Fatalf("unable to query fee estimator: %v", err)
}
fee := responderChan.CalcFee(feePerKw)
closeSig, _, _, err := responderChan.CreateCloseProposal(fee,
dummyDeliveryScript, initiatorDeliveryScript)
if err != nil {
t.Fatalf("unable to create close proposal: %v", err)
}
parsedSig, err := lnwire.NewSigFromSignature(closeSig)
if err != nil {
t.Fatalf("unable to parse signature: %v", err)
}
closingSigned := lnwire.NewClosingSigned(shutdownMsg.ChannelID,
fee, parsedSig)
initiator.chanCloseMsgs <- &closeMsg{
cid: chanID,
msg: closingSigned,
}
select {
case outMsg := <-initiator.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 != fee {
t.Fatalf("expected ClosingSigned fee to be %v, instead got %v",
fee, closingSignedMsg.FeeSatoshis)
}
select {
case <-broadcastTxChan:
case <-time.After(timeout):
t.Fatalf("closing tx not broadcast")
}
notifier.confChannel <- &chainntnfs.TxConfirmation{}
}
func TestPeerChannelClosureFeeNegotiationsResponder(t *testing.T) {
t.Parallel()
notifier := &mockNotfier{
confChannel: make(chan *chainntnfs.TxConfirmation),
}
broadcastTxChan := make(chan *wire.MsgTx)
responder, responderChan, initiatorChan, cleanUp, err := createTestPeer(
notifier, broadcastTxChan, noUpdate,
)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
chanID := lnwire.NewChanIDFromOutPoint(responderChan.ChannelPoint())
responder.chanCloseMsgs <- &closeMsg{
cid: chanID,
msg: lnwire.NewShutdown(chanID,
dummyDeliveryScript),
}
var msg lnwire.Message
select {
case outMsg := <-responder.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
select {
case outMsg := <-responder.outgoingQueue:
msg = outMsg.msg
case <-time.After(timeout):
t.Fatalf("did not receive closing signed message")
}
responderClosingSigned, ok := msg.(*lnwire.ClosingSigned)
if !ok {
t.Fatalf("expected ClosingSigned message, got %T", msg)
}
preferredRespFee := responderClosingSigned.FeeSatoshis
increasedFee := btcutil.Amount(float64(preferredRespFee) * 2.5)
initiatorSig, _, _, err := initiatorChan.CreateCloseProposal(
increasedFee, dummyDeliveryScript, respDeliveryScript,
)
if err != nil {
t.Fatalf("error creating close proposal: %v", err)
}
parsedSig, err := lnwire.NewSigFromSignature(initiatorSig)
if err != nil {
t.Fatalf("error parsing signature: %v", err)
}
closingSigned := lnwire.NewClosingSigned(chanID, increasedFee, parsedSig)
responder.chanCloseMsgs <- &closeMsg{
cid: chanID,
msg: closingSigned,
}
select {
case outMsg := <-responder.outgoingQueue:
msg = outMsg.msg
case <-time.After(timeout):
t.Fatalf("did not receive closing signed message")
}
responderClosingSigned, ok = msg.(*lnwire.ClosingSigned)
if !ok {
t.Fatalf("expected ClosingSigned message, got %T", msg)
}
peerFee := responderClosingSigned.FeeSatoshis
if peerFee > increasedFee {
t.Fatalf("new fee should be less than our fee: new=%v, "+
"prior=%v", peerFee, increasedFee)
}
lastFeeResponder := peerFee
increasedFee = btcutil.Amount(float64(preferredRespFee) * 2.1)
initiatorSig, _, _, err = initiatorChan.CreateCloseProposal(
increasedFee, dummyDeliveryScript, respDeliveryScript,
)
if err != nil {
t.Fatalf("error creating close proposal: %v", err)
}
parsedSig, err = lnwire.NewSigFromSignature(initiatorSig)
if err != nil {
t.Fatalf("error parsing signature: %v", err)
}
closingSigned = lnwire.NewClosingSigned(chanID, increasedFee, parsedSig)
responder.chanCloseMsgs <- &closeMsg{
cid: chanID,
msg: closingSigned,
}
select {
case outMsg := <-responder.outgoingQueue:
msg = outMsg.msg
case <-time.After(timeout):
t.Fatalf("did not receive closing signed message")
}
responderClosingSigned, ok = msg.(*lnwire.ClosingSigned)
if !ok {
t.Fatalf("expected ClosingSigned message, got %T", msg)
}
peerFee = responderClosingSigned.FeeSatoshis
if peerFee < lastFeeResponder {
t.Fatalf("new fee should be greater than prior: new=%v, "+
"prior=%v", peerFee, lastFeeResponder)
}
if peerFee > increasedFee {
t.Fatalf("new fee should be less than our fee: new=%v, "+
"prior=%v", peerFee, increasedFee)
}
initiatorSig, _, _, err = initiatorChan.CreateCloseProposal(
peerFee, dummyDeliveryScript, respDeliveryScript,
)
if err != nil {
t.Fatalf("error creating close proposal: %v", err)
}
parsedSig, err = lnwire.NewSigFromSignature(initiatorSig)
if err != nil {
t.Fatalf("error parsing signature: %v", err)
}
closingSigned = lnwire.NewClosingSigned(chanID, peerFee, parsedSig)
responder.chanCloseMsgs <- &closeMsg{
cid: chanID,
msg: closingSigned,
}
select {
case <-broadcastTxChan:
case <-time.After(timeout):
t.Fatalf("closing tx not broadcast")
}
notifier.confChannel <- &chainntnfs.TxConfirmation{}
}
func TestPeerChannelClosureFeeNegotiationsInitiator(t *testing.T) {
t.Parallel()
notifier := &mockNotfier{
confChannel: make(chan *chainntnfs.TxConfirmation),
}
broadcastTxChan := make(chan *wire.MsgTx)
initiator, initiatorChan, responderChan, cleanUp, err := createTestPeer(
notifier, broadcastTxChan, noUpdate,
)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
updateChan := make(chan interface{}, 1)
errChan := make(chan error, 1)
closeCommand := &htlcswitch.ChanClose{
CloseType: htlcswitch.CloseRegular,
ChanPoint: initiatorChan.ChannelPoint(),
Updates: updateChan,
TargetFeePerKw: 12500,
Err: errChan,
}
initiator.localCloseChanReqs <- closeCommand
var msg lnwire.Message
select {
case outMsg := <-initiator.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)
}
initiatorDeliveryScript := shutdownMsg.Address
chanID := lnwire.NewChanIDFromOutPoint(initiatorChan.ChannelPoint())
respShutdown := lnwire.NewShutdown(chanID, dummyDeliveryScript)
initiator.chanCloseMsgs <- &closeMsg{
cid: chanID,
msg: respShutdown,
}
estimator := chainfee.NewStaticEstimator(12500, 0)
initiatorIdealFeeRate, err := estimator.EstimateFeePerKW(1)
if err != nil {
t.Fatalf("unable to query fee estimator: %v", err)
}
initiatorIdealFee := responderChan.CalcFee(initiatorIdealFeeRate)
increasedFee := btcutil.Amount(float64(initiatorIdealFee) * 2.5)
closeSig, _, _, err := responderChan.CreateCloseProposal(
increasedFee, dummyDeliveryScript, initiatorDeliveryScript,
)
if err != nil {
t.Fatalf("unable to create close proposal: %v", err)
}
parsedSig, err := lnwire.NewSigFromSignature(closeSig)
if err != nil {
t.Fatalf("unable to parse signature: %v", err)
}
closingSigned := lnwire.NewClosingSigned(
shutdownMsg.ChannelID, increasedFee, parsedSig,
)
initiator.chanCloseMsgs <- &closeMsg{
cid: chanID,
msg: closingSigned,
}
select {
case outMsg := <-initiator.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)
}
if closingSignedMsg.FeeSatoshis != initiatorIdealFee {
t.Fatalf("expected ClosingSigned fee to be %v, instead got %v",
initiatorIdealFee, closingSignedMsg.FeeSatoshis)
}
lastFeeSent := closingSignedMsg.FeeSatoshis
select {
case outMsg := <-initiator.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)
}
peerFee := closingSignedMsg.FeeSatoshis
if peerFee < lastFeeSent {
t.Fatalf("new fee should be greater than prior: new=%v, "+
"prior=%v", peerFee, lastFeeSent)
}
if peerFee > increasedFee {
t.Fatalf("new fee should be less than our fee: new=%v, "+
"prior=%v", peerFee, increasedFee)
}
lastFeeSent = closingSignedMsg.FeeSatoshis
increasedFee = btcutil.Amount(float64(initiatorIdealFee) * 2.1)
responderSig, _, _, err := responderChan.CreateCloseProposal(
increasedFee, dummyDeliveryScript, initiatorDeliveryScript,
)
if err != nil {
t.Fatalf("error creating close proposal: %v", err)
}
parsedSig, err = lnwire.NewSigFromSignature(responderSig)
if err != nil {
t.Fatalf("error parsing signature: %v", err)
}
closingSigned = lnwire.NewClosingSigned(chanID, increasedFee, parsedSig)
initiator.chanCloseMsgs <- &closeMsg{
cid: chanID,
msg: closingSigned,
}
select {
case outMsg := <-initiator.outgoingQueue:
msg = outMsg.msg
case <-time.After(timeout):
t.Fatalf("did not receive closing signed")
}
initiatorClosingSigned, ok := msg.(*lnwire.ClosingSigned)
if !ok {
t.Fatalf("expected ClosingSigned message, got %T", msg)
}
peerFee = initiatorClosingSigned.FeeSatoshis
if peerFee < lastFeeSent {
t.Fatalf("new fee should be greater than prior: new=%v, "+
"prior=%v", peerFee, lastFeeSent)
}
if peerFee > increasedFee {
t.Fatalf("new fee should be less than our fee: new=%v, "+
"prior=%v", peerFee, increasedFee)
}
responderSig, _, _, err = responderChan.CreateCloseProposal(
peerFee, dummyDeliveryScript, initiatorDeliveryScript,
)
if err != nil {
t.Fatalf("error creating close proposal: %v", err)
}
parsedSig, err = lnwire.NewSigFromSignature(responderSig)
if err != nil {
t.Fatalf("error parsing signature: %v", err)
}
closingSigned = lnwire.NewClosingSigned(chanID, peerFee, parsedSig)
initiator.chanCloseMsgs <- &closeMsg{
cid: chanID,
msg: closingSigned,
}
select {
case <-broadcastTxChan:
case <-time.After(timeout):
t.Fatalf("closing tx not broadcast")
}
}
func TestChooseDeliveryScript(t *testing.T) {
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: 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)
}
})
}
}
func TestCustomShutdownScript(t *testing.T) {
script := genScript(t, p2SHAddress)
setShutdown := func(a, b *channeldb.OpenChannel) {
a.LocalShutdownScript = script
b.RemoteShutdownScript = script
}
tests := []struct {
name string
update func(a, b *channeldb.OpenChannel)
userCloseScript lnwire.DeliveryAddress
expectedScript lnwire.DeliveryAddress
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: errUpfrontShutdownScriptMismatch,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
notifier := &mockNotfier{
confChannel: make(chan *chainntnfs.TxConfirmation),
}
broadcastTxChan := make(chan *wire.MsgTx)
initiator, initiatorChan, _, cleanUp, err := createTestPeer(
notifier, broadcastTxChan, test.update,
)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
updateChan := make(chan interface{}, 1)
errChan := make(chan error, 1)
chanPoint := initiatorChan.ChannelPoint()
closeCommand := htlcswitch.ChanClose{
CloseType: htlcswitch.CloseRegular,
ChanPoint: chanPoint,
Updates: updateChan,
TargetFeePerKw: 12500,
DeliveryScript: test.userCloseScript,
Err: errChan,
}
initiator.localCloseChanReqs <- &closeCommand
var msg lnwire.Message
select {
case outMsg := <-initiator.outgoingQueue:
msg = outMsg.msg
case <-time.After(timeout):
t.Fatalf("did not receive shutdown message")
case err := <-errChan:
if err != test.expectedError {
t.Fatalf("error closing channel: %v", err)
}
return
}
shutdownMsg, ok := msg.(*lnwire.Shutdown)
if !ok {
t.Fatalf("expected shutdown message, got %T", msg)
}
if len(test.expectedScript) == 0 {
return
}
if !bytes.Equal(test.expectedScript, shutdownMsg.Address) {
t.Fatalf("expected delivery script: %x, got: %x",
test.expectedScript, shutdownMsg.Address)
}
})
}
}
func genScript(t *testing.T, address string) lnwire.DeliveryAddress {
deliveryAddr, err := btcutil.DecodeAddress(
address,
activeNetParams.Params,
)
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
}