lnd/funding/manager_test.go
Olaoluwa Osuntokun 15978a8691
funding+peer: add support for new musig2 channel funding flow
In this commit, we add support for the new musig2 channel funding flow.
This flow is identical to the existing flow, but not both sides need to
exchange local nonces up front, and then signatures sent are now partial
signatures instead of regular signatures.

The funding manager also gains some new state of the local nonces it
needs to generate in order to send the funding locked message, and also
process the funding locked message from the remote party.

In order to allow the funding manger to generate the nonces that need to
be applied to each channel, then AddNewChannel method has been modified
to accept a set of options that the peer will then use to bind the
nonces to a new channel.
2023-08-22 16:32:07 -07:00

4555 lines
135 KiB
Go

package funding
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"net"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
"time"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/chainreg"
acpt "github.com/lightningnetwork/lnd/chanacceptor"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/channelnotifier"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnpeer"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntest/mock"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
const (
// testPollNumTries is the number of times we attempt to query
// for a certain expected database state before we give up and
// consider the test failed. Since it sometimes can take a
// while to update the database, we poll a certain amount of
// times, until it gets into the state we expect, or we are out
// of tries.
testPollNumTries = 10
// testPollSleepMs is the number of milliseconds to sleep between
// each attempt to access the database to check its state.
testPollSleepMs = 500
// maxPending is the maximum number of channels we allow opening to the
// same peer in the max pending channels test.
maxPending = 4
// A dummy value to use for the funding broadcast height.
fundingBroadcastHeight = 123
// defaultMaxLocalCSVDelay is the maximum delay we accept on our
// commitment output.
defaultMaxLocalCSVDelay = 2016
)
var (
// Use hard-coded keys for Alice and Bob, the two FundingManagers that
// we will test the interaction between.
alicePrivKeyBytes = [32]byte{
0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,
0x4d, 0x92, 0x73, 0xd1, 0x90, 0x63, 0x81, 0xb4,
0x4f, 0x2f, 0x6f, 0x25, 0x88, 0xa3, 0xef, 0xb9,
0x6a, 0x49, 0x18, 0x83, 0x31, 0x98, 0x47, 0x53,
}
alicePrivKey, alicePubKey = btcec.PrivKeyFromBytes(
alicePrivKeyBytes[:],
)
aliceTCPAddr, _ = net.ResolveTCPAddr("tcp", "10.0.0.2:9001")
aliceAddr = &lnwire.NetAddress{
IdentityKey: alicePubKey,
Address: aliceTCPAddr,
}
bobPrivKeyBytes = [32]byte{
0x81, 0xb6, 0x37, 0xd8, 0xfc, 0xd2, 0xc6, 0xda,
0x63, 0x59, 0xe6, 0x96, 0x31, 0x13, 0xa1, 0x17,
0xd, 0xe7, 0x95, 0xe4, 0xb7, 0x25, 0xb8, 0x4d,
0x1e, 0xb, 0x4c, 0xfd, 0x9e, 0xc5, 0x8c, 0xe9,
}
bobPrivKey, bobPubKey = btcec.PrivKeyFromBytes(bobPrivKeyBytes[:])
bobTCPAddr, _ = net.ResolveTCPAddr("tcp", "10.0.0.2:9000")
bobAddr = &lnwire.NetAddress{
IdentityKey: bobPubKey,
Address: bobTCPAddr,
}
testRBytes, _ = hex.DecodeString("8ce2bc69281ce27da07e6683571319d18e949ddfa2965fb6caa1bf0314f882d7")
testSBytes, _ = hex.DecodeString("299105481d63e0f4bc2a88121167221b6700d72a0ead154c03be696a292d24ae")
testRScalar = new(btcec.ModNScalar)
testSScalar = new(btcec.ModNScalar)
_ = testRScalar.SetByteSlice(testRBytes)
_ = testSScalar.SetByteSlice(testSBytes)
testSig = ecdsa.NewSignature(testRScalar, testSScalar)
testKeyLoc = keychain.KeyLocator{Family: keychain.KeyFamilyNodeKey}
fundingNetParams = chainreg.BitcoinTestNetParams
alias = lnwire.ShortChannelID{
BlockHeight: 16_000_000,
TxIndex: 0,
TxPosition: 0,
}
)
type mockAliasMgr struct{}
func (m *mockAliasMgr) RequestAlias() (lnwire.ShortChannelID, error) {
return alias, nil
}
func (m *mockAliasMgr) PutPeerAlias(lnwire.ChannelID,
lnwire.ShortChannelID) error {
return nil
}
func (m *mockAliasMgr) GetPeerAlias(lnwire.ChannelID) (lnwire.ShortChannelID,
error) {
return alias, nil
}
func (m *mockAliasMgr) AddLocalAlias(lnwire.ShortChannelID,
lnwire.ShortChannelID, bool) error {
return nil
}
func (m *mockAliasMgr) GetAliases(
lnwire.ShortChannelID) []lnwire.ShortChannelID {
return []lnwire.ShortChannelID{alias}
}
func (m *mockAliasMgr) DeleteSixConfs(lnwire.ShortChannelID) error {
return nil
}
type mockNotifier struct {
oneConfChannel chan *chainntnfs.TxConfirmation
sixConfChannel chan *chainntnfs.TxConfirmation
epochChan chan *chainntnfs.BlockEpoch
}
func (m *mockNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
_ []byte, numConfs, heightHint uint32,
opts ...chainntnfs.NotifierOption) (*chainntnfs.ConfirmationEvent,
error) {
if numConfs == 6 {
return &chainntnfs.ConfirmationEvent{
Confirmed: m.sixConfChannel,
}, nil
}
return &chainntnfs.ConfirmationEvent{
Confirmed: m.oneConfChannel,
}, nil
}
func (m *mockNotifier) RegisterBlockEpochNtfn(
bestBlock *chainntnfs.BlockEpoch) (*chainntnfs.BlockEpochEvent, error) {
return &chainntnfs.BlockEpochEvent{
Epochs: m.epochChan,
Cancel: func() {},
}, nil
}
func (m *mockNotifier) Start() error {
return nil
}
func (m *mockNotifier) Started() bool {
return true
}
func (m *mockNotifier) Stop() error {
return nil
}
func (m *mockNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint, _ []byte,
heightHint uint32) (*chainntnfs.SpendEvent, error) {
return &chainntnfs.SpendEvent{
Spend: make(chan *chainntnfs.SpendDetail),
Cancel: func() {},
}, nil
}
type mockChanEvent struct {
openEvent chan wire.OutPoint
pendingOpenEvent chan channelnotifier.PendingOpenChannelEvent
}
func (m *mockChanEvent) NotifyOpenChannelEvent(outpoint wire.OutPoint) {
m.openEvent <- outpoint
}
func (m *mockChanEvent) NotifyPendingOpenChannelEvent(outpoint wire.OutPoint,
pendingChannel *channeldb.OpenChannel) {
m.pendingOpenEvent <- channelnotifier.PendingOpenChannelEvent{
ChannelPoint: &outpoint,
PendingChannel: pendingChannel,
}
}
// mockZeroConfAcceptor always accepts the channel open request for zero-conf
// channels. It will set the ZeroConf bool in the ChannelAcceptResponse. This
// is needed to properly unit test the zero-conf logic in the funding manager.
type mockZeroConfAcceptor struct{}
func (m *mockZeroConfAcceptor) Accept(
req *acpt.ChannelAcceptRequest) *acpt.ChannelAcceptResponse {
return &acpt.ChannelAcceptResponse{
ZeroConf: true,
}
}
type newChannelMsg struct {
channel *lnpeer.NewChannel
err chan error
}
type testNode struct {
privKey *btcec.PrivateKey
addr *lnwire.NetAddress
msgChan chan lnwire.Message
announceChan chan lnwire.Message
publTxChan chan *wire.MsgTx
fundingMgr *Manager
newChannels chan *newChannelMsg
mockNotifier *mockNotifier
mockChanEvent *mockChanEvent
testDir string
shutdownChannel chan struct{}
reportScidChan chan struct{}
localFeatures []lnwire.FeatureBit
remoteFeatures []lnwire.FeatureBit
remotePeer *testNode
sendMessage func(lnwire.Message) error
}
var _ lnpeer.Peer = (*testNode)(nil)
func (n *testNode) IdentityKey() *btcec.PublicKey {
return n.addr.IdentityKey
}
func (n *testNode) Address() net.Addr {
return n.addr.Address
}
func (n *testNode) PubKey() [33]byte {
return newSerializedKey(n.addr.IdentityKey)
}
func (n *testNode) SendMessage(_ bool, msg ...lnwire.Message) error {
return n.sendMessage(msg[0])
}
func (n *testNode) SendMessageLazy(sync bool, msgs ...lnwire.Message) error {
return n.SendMessage(sync, msgs...)
}
func (n *testNode) WipeChannel(_ *wire.OutPoint) {}
func (n *testNode) QuitSignal() <-chan struct{} {
return n.shutdownChannel
}
func (n *testNode) LocalFeatures() *lnwire.FeatureVector {
return lnwire.NewFeatureVector(
lnwire.NewRawFeatureVector(n.localFeatures...), nil,
)
}
func (n *testNode) RemoteFeatures() *lnwire.FeatureVector {
return lnwire.NewFeatureVector(
lnwire.NewRawFeatureVector(n.remoteFeatures...), nil,
)
}
func (n *testNode) AddNewChannel(channel *lnpeer.NewChannel,
quit <-chan struct{}) error {
errChan := make(chan error)
msg := &newChannelMsg{
channel: channel,
err: errChan,
}
select {
case n.newChannels <- msg:
case <-quit:
return ErrFundingManagerShuttingDown
}
select {
case err := <-errChan:
return err
case <-quit:
return ErrFundingManagerShuttingDown
}
}
func (n *testNode) AddPendingChannel(_ lnwire.ChannelID,
quit <-chan struct{}) error {
return nil
}
func (n *testNode) RemovePendingChannel(_ lnwire.ChannelID) error {
return nil
}
func createTestWallet(cdb *channeldb.ChannelStateDB, netParams *chaincfg.Params,
notifier chainntnfs.ChainNotifier, wc lnwallet.WalletController,
signer input.Signer, keyRing keychain.SecretKeyRing,
bio lnwallet.BlockChainIO,
estimator chainfee.Estimator) (*lnwallet.LightningWallet, error) {
wallet, err := lnwallet.NewLightningWallet(lnwallet.Config{
Database: cdb,
Notifier: notifier,
SecretKeyRing: keyRing,
WalletController: wc,
Signer: signer,
ChainIO: bio,
FeeEstimator: estimator,
NetParams: *netParams,
DefaultConstraints: chainreg.GenDefaultBtcConstraints(),
})
if err != nil {
return nil, err
}
if err := wallet.Startup(); err != nil {
return nil, err
}
return wallet, nil
}
func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
addr *lnwire.NetAddress, tempTestDir string,
options ...cfgOption) (*testNode, error) {
netParams := fundingNetParams.Params
estimator := chainfee.NewStaticEstimator(62500, 0)
chainNotifier := &mockNotifier{
oneConfChannel: make(chan *chainntnfs.TxConfirmation, 1),
sixConfChannel: make(chan *chainntnfs.TxConfirmation, 1),
epochChan: make(chan *chainntnfs.BlockEpoch, 2),
}
aliasMgr := &mockAliasMgr{}
sentMessages := make(chan lnwire.Message)
sentAnnouncements := make(chan lnwire.Message)
publTxChan := make(chan *wire.MsgTx, 1)
shutdownChan := make(chan struct{})
reportScidChan := make(chan struct{})
wc := &mock.WalletController{
RootKey: alicePrivKey,
}
signer := &mock.SingleSigner{
Privkey: alicePrivKey,
}
bio := &mock.ChainIO{
BestHeight: fundingBroadcastHeight,
}
// The mock channel event notifier will receive events for each pending
// open and open channel. Because some tests will create multiple
// channels in a row before advancing to the next step, these channels
// need to be buffered.
evt := &mockChanEvent{
openEvent: make(chan wire.OutPoint, maxPending),
pendingOpenEvent: make(
chan channelnotifier.PendingOpenChannelEvent,
maxPending,
),
}
dbDir := filepath.Join(tempTestDir, "cdb")
fullDB, err := channeldb.Open(dbDir)
if err != nil {
return nil, err
}
cdb := fullDB.ChannelStateDB()
keyRing := &mock.SecretKeyRing{
RootKey: alicePrivKey,
}
lnw, err := createTestWallet(
cdb, netParams, chainNotifier, wc, signer, keyRing, bio,
estimator,
)
require.NoError(t, err, "unable to create test ln wallet")
var chanIDSeed [32]byte
chainedAcceptor := acpt.NewChainedAcceptor()
fundingCfg := Config{
IDKey: privKey.PubKey(),
IDKeyLoc: testKeyLoc,
Wallet: lnw,
Notifier: chainNotifier,
ChannelDB: cdb,
FeeEstimator: estimator,
SignMessage: func(_ keychain.KeyLocator,
_ []byte, _ bool) (*ecdsa.Signature, error) {
return testSig, nil
},
SendAnnouncement: func(msg lnwire.Message,
_ ...discovery.OptionalMsgField) chan error {
errChan := make(chan error, 1)
select {
case sentAnnouncements <- msg:
errChan <- nil
case <-shutdownChan:
errChan <- fmt.Errorf("shutting down")
}
return errChan
},
CurrentNodeAnnouncement: func() (lnwire.NodeAnnouncement,
error) {
return lnwire.NodeAnnouncement{}, nil
},
TempChanIDSeed: chanIDSeed,
FindChannel: func(node *btcec.PublicKey,
chanID lnwire.ChannelID) (*channeldb.OpenChannel,
error) {
nodeChans, err := cdb.FetchOpenChannels(node)
if err != nil {
return nil, err
}
for _, channel := range nodeChans {
if chanID.IsChanPoint(
&channel.FundingOutpoint,
) {
return channel, nil
}
}
return nil, fmt.Errorf("unable to find channel")
},
DefaultRoutingPolicy: models.ForwardingPolicy{
MinHTLCOut: 5,
BaseFee: 100,
FeeRate: 1000,
TimeLockDelta: 10,
},
DefaultMinHtlcIn: 5,
NumRequiredConfs: func(chanAmt btcutil.Amount,
pushAmt lnwire.MilliSatoshi) uint16 {
return 3
},
RequiredRemoteDelay: func(amt btcutil.Amount) uint16 {
return 4
},
RequiredRemoteChanReserve: func(chanAmt,
dustLimit btcutil.Amount) btcutil.Amount {
reserve := chanAmt / 100
if reserve < dustLimit {
reserve = dustLimit
}
return reserve
},
RequiredRemoteMaxValue: func(chanAmt btcutil.Amount) lnwire.MilliSatoshi {
reserve := lnwire.NewMSatFromSatoshis(chanAmt / 100)
return lnwire.NewMSatFromSatoshis(chanAmt) - reserve
},
RequiredRemoteMaxHTLCs: func(chanAmt btcutil.Amount) uint16 {
return uint16(input.MaxHTLCNumber / 2)
},
WatchNewChannel: func(*channeldb.OpenChannel,
*btcec.PublicKey) error {
return nil
},
ReportShortChanID: func(wire.OutPoint) error {
reportScidChan <- struct{}{}
return nil
},
PublishTransaction: func(txn *wire.MsgTx, _ string) error {
publTxChan <- txn
return nil
},
UpdateLabel: func(chainhash.Hash, string) error {
return nil
},
ZombieSweeperInterval: 1 * time.Hour,
ReservationTimeout: 1 * time.Nanosecond,
MaxChanSize: MaxBtcFundingAmount,
MaxLocalCSVDelay: defaultMaxLocalCSVDelay,
MaxPendingChannels: lncfg.DefaultMaxPendingChannels,
NotifyOpenChannelEvent: evt.NotifyOpenChannelEvent,
OpenChannelPredicate: chainedAcceptor,
NotifyPendingOpenChannelEvent: evt.NotifyPendingOpenChannelEvent,
RegisteredChains: chainreg.NewChainRegistry(),
DeleteAliasEdge: func(scid lnwire.ShortChannelID) (
*channeldb.ChannelEdgePolicy, error) {
return nil, nil
},
AliasManager: aliasMgr,
}
for _, op := range options {
op(&fundingCfg)
}
f, err := NewFundingManager(fundingCfg)
require.NoError(t, err, "failed creating fundingManager")
if err = f.Start(); err != nil {
t.Fatalf("failed starting fundingManager: %v", err)
}
testNode := &testNode{
privKey: privKey,
msgChan: sentMessages,
newChannels: make(chan *newChannelMsg),
announceChan: sentAnnouncements,
publTxChan: publTxChan,
fundingMgr: f,
mockNotifier: chainNotifier,
mockChanEvent: evt,
testDir: tempTestDir,
shutdownChannel: shutdownChan,
reportScidChan: reportScidChan,
addr: addr,
}
f.cfg.NotifyWhenOnline = func(peer [33]byte,
connectedChan chan<- lnpeer.Peer) {
connectedChan <- testNode.remotePeer
}
return testNode, nil
}
func recreateAliceFundingManager(t *testing.T, alice *testNode) {
// Stop the old fundingManager before creating a new one.
close(alice.shutdownChannel)
if err := alice.fundingMgr.Stop(); err != nil {
t.Fatalf("failed stop funding manager: %v", err)
}
aliceMsgChan := make(chan lnwire.Message)
aliceAnnounceChan := make(chan lnwire.Message)
shutdownChan := make(chan struct{})
publishChan := make(chan *wire.MsgTx, 10)
oldCfg := alice.fundingMgr.cfg
chainedAcceptor := acpt.NewChainedAcceptor()
f, err := NewFundingManager(Config{
IDKey: oldCfg.IDKey,
IDKeyLoc: oldCfg.IDKeyLoc,
Wallet: oldCfg.Wallet,
Notifier: oldCfg.Notifier,
ChannelDB: oldCfg.ChannelDB,
FeeEstimator: oldCfg.FeeEstimator,
SignMessage: func(_ keychain.KeyLocator,
_ []byte, _ bool) (*ecdsa.Signature, error) {
return testSig, nil
},
SendAnnouncement: func(msg lnwire.Message,
_ ...discovery.OptionalMsgField) chan error {
errChan := make(chan error, 1)
select {
case aliceAnnounceChan <- msg:
errChan <- nil
case <-shutdownChan:
errChan <- fmt.Errorf("shutting down")
}
return errChan
},
CurrentNodeAnnouncement: func() (lnwire.NodeAnnouncement,
error) {
return lnwire.NodeAnnouncement{}, nil
},
NotifyWhenOnline: func(peer [33]byte,
connectedChan chan<- lnpeer.Peer) {
connectedChan <- alice.remotePeer
},
TempChanIDSeed: oldCfg.TempChanIDSeed,
FindChannel: oldCfg.FindChannel,
DefaultRoutingPolicy: models.ForwardingPolicy{
MinHTLCOut: 5,
BaseFee: 100,
FeeRate: 1000,
TimeLockDelta: 10,
},
DefaultMinHtlcIn: 5,
RequiredRemoteMaxValue: oldCfg.RequiredRemoteMaxValue,
ReportShortChanID: oldCfg.ReportShortChanID,
PublishTransaction: func(txn *wire.MsgTx, _ string) error {
publishChan <- txn
return nil
},
UpdateLabel: func(chainhash.Hash, string) error {
return nil
},
ZombieSweeperInterval: oldCfg.ZombieSweeperInterval,
ReservationTimeout: oldCfg.ReservationTimeout,
OpenChannelPredicate: chainedAcceptor,
DeleteAliasEdge: oldCfg.DeleteAliasEdge,
AliasManager: oldCfg.AliasManager,
})
require.NoError(t, err, "failed recreating aliceFundingManager")
alice.fundingMgr = f
alice.msgChan = aliceMsgChan
alice.announceChan = aliceAnnounceChan
alice.publTxChan = publishChan
alice.shutdownChannel = shutdownChan
if err = f.Start(); err != nil {
t.Fatalf("failed starting fundingManager: %v", err)
}
}
type cfgOption func(*Config)
func setupFundingManagers(t *testing.T,
options ...cfgOption) (*testNode, *testNode) {
alice, err := createTestFundingManager(
t, alicePrivKey, aliceAddr, t.TempDir(), options...,
)
require.NoError(t, err, "failed creating fundingManager")
bob, err := createTestFundingManager(
t, bobPrivKey, bobAddr, t.TempDir(), options...,
)
require.NoError(t, err, "failed creating fundingManager")
// With the funding manager's created, we'll now attempt to mimic a
// connection pipe between them. In order to intercept the messages
// within it, we'll redirect all messages back to the msgChan of the
// sender. Since the fundingManager now has a reference to peers itself,
// alice.sendMessage will be triggered when Bob's funding manager
// attempts to send a message to Alice and vice versa.
alice.remotePeer = bob
alice.sendMessage = func(msg lnwire.Message) error {
select {
case alice.remotePeer.msgChan <- msg:
case <-alice.shutdownChannel:
return errors.New("shutting down")
}
return nil
}
bob.remotePeer = alice
bob.sendMessage = func(msg lnwire.Message) error {
select {
case bob.remotePeer.msgChan <- msg:
case <-bob.shutdownChannel:
return errors.New("shutting down")
}
return nil
}
return alice, bob
}
func tearDownFundingManagers(t *testing.T, a, b *testNode) {
close(a.shutdownChannel)
close(b.shutdownChannel)
if err := a.fundingMgr.Stop(); err != nil {
t.Fatalf("failed stop funding manager: %v", err)
}
if err := b.fundingMgr.Stop(); err != nil {
t.Fatalf("failed stop funding manager: %v", err)
}
}
// openChannel takes the funding process to the point where the funding
// transaction is confirmed on-chain. Returns the funding out point.
func openChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
pushAmt btcutil.Amount, numConfs uint32,
updateChan chan *lnrpc.OpenStatusUpdate, announceChan bool) (
*wire.OutPoint, *wire.MsgTx) {
publ := fundChannel(
t, alice, bob, localFundingAmt, pushAmt, false, 0, 0, numConfs,
updateChan, announceChan, nil,
)
fundingOutPoint := &wire.OutPoint{
Hash: publ.TxHash(),
Index: 0,
}
return fundingOutPoint, publ
}
// fundChannel takes the funding process to the point where the funding
// transaction is confirmed on-chain. Returns the funding tx.
func fundChannel(t *testing.T, alice, bob *testNode, localFundingAmt,
pushAmt btcutil.Amount, subtractFees bool, fundUpToMaxAmt,
minFundAmt btcutil.Amount, numConfs uint32,
updateChan chan *lnrpc.OpenStatusUpdate, announceChan bool,
chanType *lnwire.ChannelType) *wire.MsgTx {
// Create a funding request and start the workflow.
errChan := make(chan error, 1)
initReq := &InitFundingMsg{
Peer: bob,
TargetPubkey: bob.privKey.PubKey(),
ChainHash: *fundingNetParams.GenesisHash,
SubtractFees: subtractFees,
FundUpToMaxAmt: fundUpToMaxAmt,
MinConfs: int32(numConfs),
MinFundAmt: minFundAmt,
LocalFundingAmt: localFundingAmt,
PushAmt: lnwire.NewMSatFromSatoshis(pushAmt),
FundingFeePerKw: 1000,
Private: !announceChan,
ChannelType: chanType,
Updates: updateChan,
Err: errChan,
}
alice.fundingMgr.InitFundingWorkflow(initReq)
// Alice should have sent the OpenChannel message to Bob.
var aliceMsg lnwire.Message
select {
case aliceMsg = <-alice.msgChan:
case err := <-initReq.Err:
t.Fatalf("error init funding workflow: %v", err)
case <-time.After(time.Second * 5):
t.Fatalf("alice did not send OpenChannel message")
}
openChannelReq, ok := aliceMsg.(*lnwire.OpenChannel)
if !ok {
errorMsg, gotError := aliceMsg.(*lnwire.Error)
if gotError {
t.Fatalf("expected OpenChannel to be sent "+
"from bob, instead got error: %v",
errorMsg.Error())
}
t.Fatalf("expected OpenChannel to be sent from "+
"alice, instead got %T", aliceMsg)
}
// Let Bob handle the init message.
bob.fundingMgr.ProcessFundingMsg(openChannelReq, alice)
// Bob should answer with an AcceptChannel message.
acceptChannelResponse := assertFundingMsgSent(
t, bob.msgChan, "AcceptChannel",
).(*lnwire.AcceptChannel)
// They now should both have pending reservations for this channel
// active.
assertNumPendingReservations(t, alice, bobPubKey, 1)
assertNumPendingReservations(t, bob, alicePubKey, 1)
// Forward the response to Alice.
alice.fundingMgr.ProcessFundingMsg(acceptChannelResponse, bob)
// Check that sending warning messages does not abort the funding
// process.
warningMsg := &lnwire.Warning{
Data: []byte("random warning"),
}
alice.fundingMgr.ProcessFundingMsg(warningMsg, bob)
bob.fundingMgr.ProcessFundingMsg(warningMsg, alice)
// Alice responds with a FundingCreated message.
fundingCreated := assertFundingMsgSent(
t, alice.msgChan, "FundingCreated",
).(*lnwire.FundingCreated)
// Give the message to Bob.
bob.fundingMgr.ProcessFundingMsg(fundingCreated, alice)
// Finally, Bob should send the FundingSigned message.
fundingSigned := assertFundingMsgSent(
t, bob.msgChan, "FundingSigned",
).(*lnwire.FundingSigned)
// Forward the signature to Alice.
alice.fundingMgr.ProcessFundingMsg(fundingSigned, bob)
// After Alice processes the singleFundingSignComplete message, she will
// broadcast the funding transaction to the network. We expect to get a
// channel update saying the channel is pending.
var pendingUpdate *lnrpc.OpenStatusUpdate
select {
case pendingUpdate = <-updateChan:
case <-time.After(time.Second * 5):
t.Fatalf("alice did not send OpenStatusUpdate_ChanPending")
}
_, ok = pendingUpdate.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
if !ok {
t.Fatal("OpenStatusUpdate was not OpenStatusUpdate_ChanPending")
}
// Get and return the transaction Alice published to the network.
var publ *wire.MsgTx
select {
case publ = <-alice.publTxChan:
case <-time.After(time.Second * 5):
t.Fatalf("alice did not publish funding tx")
}
// Make sure the notification about the pending channel was sent out.
select {
case <-alice.mockChanEvent.pendingOpenEvent:
case <-time.After(time.Second * 5):
t.Fatalf("alice did not send pending channel event")
}
select {
case <-bob.mockChanEvent.pendingOpenEvent:
case <-time.After(time.Second * 5):
t.Fatalf("bob did not send pending channel event")
}
// Finally, make sure neither have active reservation for the channel
// now pending open in the database.
assertNumPendingReservations(t, alice, bobPubKey, 0)
assertNumPendingReservations(t, bob, alicePubKey, 0)
return publ
}
func assertErrorNotSent(t *testing.T, msgChan chan lnwire.Message) {
t.Helper()
select {
case <-msgChan:
t.Fatalf("error sent unexpectedly")
case <-time.After(100 * time.Millisecond):
// Expected, return.
}
}
func assertErrorSent(t *testing.T, msgChan chan lnwire.Message) {
t.Helper()
var msg lnwire.Message
select {
case msg = <-msgChan:
case <-time.After(time.Second * 5):
t.Fatalf("node did not send Error message")
}
_, ok := msg.(*lnwire.Error)
if !ok {
t.Fatalf("expected Error to be sent from "+
"node, instead got %T", msg)
}
}
func assertFundingMsgSent(t *testing.T, msgChan chan lnwire.Message,
msgType string) lnwire.Message {
t.Helper()
var msg lnwire.Message
select {
case msg = <-msgChan:
case <-time.After(time.Second * 5):
t.Fatalf("peer did not send %s message", msgType)
}
var (
sentMsg lnwire.Message
ok bool
)
switch msgType {
case "AcceptChannel":
sentMsg, ok = msg.(*lnwire.AcceptChannel)
case "FundingCreated":
sentMsg, ok = msg.(*lnwire.FundingCreated)
case "FundingSigned":
sentMsg, ok = msg.(*lnwire.FundingSigned)
case "ChannelReady":
sentMsg, ok = msg.(*lnwire.ChannelReady)
case "Error":
sentMsg, ok = msg.(*lnwire.Error)
default:
t.Fatalf("unknown message type: %s", msgType)
}
if !ok {
errorMsg, gotError := msg.(*lnwire.Error)
if gotError {
t.Fatalf("expected %s to be sent, instead got "+
"error: %v", msgType, errorMsg.Error())
}
_, _, line, _ := runtime.Caller(1)
t.Fatalf("expected %s to be sent, instead got %T at %v",
msgType, msg, line)
}
return sentMsg
}
func assertNumPendingReservations(t *testing.T, node *testNode,
peerPubKey *btcec.PublicKey, expectedNum int) {
t.Helper()
serializedPubKey := newSerializedKey(peerPubKey)
actualNum := len(node.fundingMgr.activeReservations[serializedPubKey])
if actualNum == expectedNum {
// Success, return.
return
}
t.Fatalf("Expected node to have %d pending reservations, had %v",
expectedNum, actualNum)
}
func assertNumPendingChannelsBecomes(t *testing.T, node *testNode,
expectedNum int) {
t.Helper()
var numPendingChans int
for i := 0; i < testPollNumTries; i++ {
// If this is not the first try, sleep before retrying.
if i > 0 {
time.Sleep(testPollSleepMs * time.Millisecond)
}
pendingChannels, err := node.fundingMgr.
cfg.Wallet.Cfg.Database.FetchPendingChannels()
if err != nil {
t.Fatalf("unable to fetch pending channels: %v", err)
}
numPendingChans = len(pendingChannels)
if numPendingChans == expectedNum {
// Success, return.
return
}
}
t.Fatalf("Expected node to have %d pending channels, had %v",
expectedNum, numPendingChans)
}
func assertNumPendingChannelsRemains(t *testing.T, node *testNode,
expectedNum int) {
t.Helper()
var numPendingChans int
for i := 0; i < 5; i++ {
// If this is not the first try, sleep before retrying.
if i > 0 {
time.Sleep(200 * time.Millisecond)
}
pendingChannels, err := node.fundingMgr.
cfg.Wallet.Cfg.Database.FetchPendingChannels()
if err != nil {
t.Fatalf("unable to fetch pending channels: %v", err)
}
numPendingChans = len(pendingChannels)
if numPendingChans != expectedNum {
t.Fatalf("Expected node to have %d pending channels, had %v",
expectedNum, numPendingChans)
}
}
}
func assertDatabaseState(t *testing.T, node *testNode,
fundingOutPoint *wire.OutPoint, expectedState channelOpeningState) {
t.Helper()
var state channelOpeningState
var err error
for i := 0; i < testPollNumTries; i++ {
// If this is not the first try, sleep before retrying.
if i > 0 {
time.Sleep(testPollSleepMs * time.Millisecond)
}
state, _, err = node.fundingMgr.getChannelOpeningState(
fundingOutPoint)
if err != nil && err != channeldb.ErrChannelNotFound {
t.Fatalf("unable to get channel state: %v", err)
}
// If we found the channel, check if it had the expected state.
if !errors.Is(err, channeldb.ErrChannelNotFound) &&
state == expectedState {
// Got expected state, return with success.
return
}
}
// 10 tries without success.
if err != nil {
t.Fatalf("error getting channelOpeningState: %v", err)
} else {
t.Fatalf("expected state to be %v, was %v", expectedState,
state)
}
}
func assertMarkedOpen(t *testing.T, alice, bob *testNode,
fundingOutPoint *wire.OutPoint) {
t.Helper()
// Make sure the notification about the pending channel was sent out.
select {
case <-alice.mockChanEvent.openEvent:
case <-time.After(time.Second * 5):
t.Fatalf("alice did not send open channel event")
}
select {
case <-bob.mockChanEvent.openEvent:
case <-time.After(time.Second * 5):
t.Fatalf("bob did not send open channel event")
}
assertDatabaseState(t, alice, fundingOutPoint, markedOpen)
assertDatabaseState(t, bob, fundingOutPoint, markedOpen)
}
func assertChannelReadySent(t *testing.T, alice, bob *testNode,
fundingOutPoint *wire.OutPoint) {
t.Helper()
assertDatabaseState(t, alice, fundingOutPoint, channelReadySent)
assertDatabaseState(t, bob, fundingOutPoint, channelReadySent)
}
func assertAddedToRouterGraph(t *testing.T, alice, bob *testNode,
fundingOutPoint *wire.OutPoint) {
t.Helper()
assertDatabaseState(t, alice, fundingOutPoint, addedToRouterGraph)
assertDatabaseState(t, bob, fundingOutPoint, addedToRouterGraph)
}
// assertChannelAnnouncements checks that alice and bob both sends the expected
// announcements (ChannelAnnouncement, ChannelUpdate) after the funding tx has
// confirmed. The last arguments can be set if we expect the nodes to advertise
// custom min_htlc values as part of their ChannelUpdate. We expect Alice to
// advertise the value required by Bob and vice versa. If they are not set the
// advertised value will be checked against the other node's default min_htlc,
// base fee and fee rate values.
func assertChannelAnnouncements(t *testing.T, alice, bob *testNode,
capacity btcutil.Amount, customMinHtlc []lnwire.MilliSatoshi,
customMaxHtlc []lnwire.MilliSatoshi, baseFees []lnwire.MilliSatoshi,
feeRates []lnwire.MilliSatoshi) {
t.Helper()
// The following checks are to make sure the parameters are used
// correctly, as we currently only support 2 values, one for each node.
aliceCfg := alice.fundingMgr.cfg
if len(customMinHtlc) > 0 {
require.Len(t, customMinHtlc, 2, "incorrect usage")
}
if len(customMaxHtlc) > 0 {
require.Len(t, customMaxHtlc, 2, "incorrect usage")
}
if len(baseFees) > 0 {
require.Len(t, baseFees, 2, "incorrect usage")
}
if len(feeRates) > 0 {
require.Len(t, feeRates, 2, "incorrect usage")
}
// After the ChannelReady message is sent, Alice and Bob will each send
// the following messages to their gossiper:
// 1) ChannelAnnouncement
// 2) ChannelUpdate
// The ChannelAnnouncement is kept locally, while the ChannelUpdate is
// sent directly to the other peer, so the edge policies are known to
// both peers.
nodes := []*testNode{alice, bob}
for j, node := range nodes {
announcements := make([]lnwire.Message, 2)
for i := 0; i < len(announcements); i++ {
select {
case announcements[i] = <-node.announceChan:
case <-time.After(time.Second * 5):
t.Fatalf("node didn't send announcement: %v", i)
}
}
gotChannelAnnouncement := false
gotChannelUpdate := false
for _, msg := range announcements {
switch m := msg.(type) {
case *lnwire.ChannelAnnouncement:
gotChannelAnnouncement = true
case *lnwire.ChannelUpdate:
// The channel update sent by the node should
// advertise the MinHTLC value required by the
// _other_ node.
other := (j + 1) % 2
otherCfg := nodes[other].fundingMgr.cfg
minHtlc := otherCfg.DefaultMinHtlcIn
maxHtlc := aliceCfg.RequiredRemoteMaxValue(
capacity,
)
baseFee := aliceCfg.DefaultRoutingPolicy.BaseFee
feeRate := aliceCfg.DefaultRoutingPolicy.FeeRate
require.EqualValues(t, 1, m.MessageFlags)
// We might expect a custom MinHTLC value.
if len(customMinHtlc) > 0 {
minHtlc = customMinHtlc[j]
}
require.Equal(t, minHtlc, m.HtlcMinimumMsat)
// We might expect a custom MaxHltc value.
if len(customMaxHtlc) > 0 {
maxHtlc = customMaxHtlc[j]
}
require.Equal(t, maxHtlc, m.HtlcMaximumMsat)
// We might expect a custom baseFee value.
if len(baseFees) > 0 {
baseFee = baseFees[j]
}
require.EqualValues(t, baseFee, m.BaseFee)
// We might expect a custom feeRate value.
if len(feeRates) > 0 {
feeRate = feeRates[j]
}
require.EqualValues(t, feeRate, m.FeeRate)
gotChannelUpdate = true
}
}
require.Truef(
t, gotChannelAnnouncement,
"ChannelAnnouncement from %d", j,
)
require.Truef(t, gotChannelUpdate, "ChannelUpdate from %d", j)
// Make sure no other message is sent.
select {
case <-node.announceChan:
t.Fatalf("received unexpected announcement")
case <-time.After(300 * time.Millisecond):
// Expected
}
}
}
func assertAnnouncementSignatures(t *testing.T, alice, bob *testNode) {
t.Helper()
// After the ChannelReady message is sent and six confirmations have
// been reached, the channel will be announced to the greater network
// by having the nodes exchange announcement signatures.
// Two distinct messages will be sent:
// 1) AnnouncementSignatures
// 2) NodeAnnouncement
// These may arrive in no particular order.
// Note that sending the NodeAnnouncement at this point is an
// implementation detail, and not something required by the LN spec.
for j, node := range []*testNode{alice, bob} {
announcements := make([]lnwire.Message, 2)
for i := 0; i < len(announcements); i++ {
select {
case announcements[i] = <-node.announceChan:
case <-time.After(time.Second * 5):
t.Fatalf("node did not send announcement %v", i)
}
}
gotAnnounceSignatures := false
gotNodeAnnouncement := false
for _, msg := range announcements {
switch msg.(type) {
case *lnwire.AnnounceSignatures:
gotAnnounceSignatures = true
case *lnwire.NodeAnnouncement:
gotNodeAnnouncement = true
}
}
if !gotAnnounceSignatures {
t.Fatalf("did not get AnnounceSignatures from node %d",
j)
}
if !gotNodeAnnouncement {
t.Fatalf("did not get NodeAnnouncement from node %d", j)
}
}
}
func waitForOpenUpdate(t *testing.T, updateChan chan *lnrpc.OpenStatusUpdate) {
var openUpdate *lnrpc.OpenStatusUpdate
select {
case openUpdate = <-updateChan:
case <-time.After(time.Second * 5):
t.Fatalf("alice did not send OpenStatusUpdate")
}
_, ok := openUpdate.Update.(*lnrpc.OpenStatusUpdate_ChanOpen)
if !ok {
t.Fatal("OpenStatusUpdate was not OpenStatusUpdate_ChanOpen")
}
}
func assertNoChannelState(t *testing.T, alice, bob *testNode,
fundingOutPoint *wire.OutPoint) {
t.Helper()
assertErrChannelNotFound(t, alice, fundingOutPoint)
assertErrChannelNotFound(t, bob, fundingOutPoint)
}
func assertNoFwdingPolicy(t *testing.T, alice, bob *testNode,
chanID lnwire.ChannelID) {
t.Helper()
assertInitialFwdingPolicyNotFound(t, alice, &chanID)
assertInitialFwdingPolicyNotFound(t, bob, &chanID)
}
func assertErrChannelNotFound(t *testing.T, node *testNode,
fundingOutPoint *wire.OutPoint) {
t.Helper()
var state channelOpeningState
var err error
for i := 0; i < testPollNumTries; i++ {
// If this is not the first try, sleep before retrying.
if i > 0 {
time.Sleep(testPollSleepMs * time.Millisecond)
}
state, _, err = node.fundingMgr.getChannelOpeningState(
fundingOutPoint)
if err == channeldb.ErrChannelNotFound {
// Got expected state, return with success.
return
} else if err != nil {
t.Fatalf("unable to get channel state: %v", err)
}
}
// 10 tries without success.
t.Fatalf("expected to not find state, found state %v", state)
}
func assertInitialFwdingPolicyNotFound(t *testing.T, node *testNode,
chanID *lnwire.ChannelID) {
t.Helper()
err := wait.NoError(func() error {
_, err := node.fundingMgr.getInitialForwardingPolicy(*chanID)
if errors.Is(err, channeldb.ErrChannelNotFound) {
return nil
}
return fmt.Errorf("expected ErrChannelNotFound, got %w", err)
}, wait.DefaultTimeout)
require.NoError(t, err)
}
func assertHandleChannelReady(t *testing.T, alice, bob *testNode,
checkChannel ...func(node *testNode, msg *newChannelMsg) bool) {
t.Helper()
const timeout = time.Second * 15
// They should both send the new channel state to their peer.
select {
case c := <-alice.newChannels:
for _, check := range checkChannel {
require.NoError(t, wait.Predicate(func() bool {
return check(alice, c)
}, timeout))
}
close(c.err)
case <-time.After(timeout):
t.Fatalf("alice did not send new channel to peer")
}
select {
case c := <-bob.newChannels:
for _, check := range checkChannel {
require.NoError(t, wait.Predicate(func() bool {
return check(bob, c)
}, timeout))
}
close(c.err)
case <-time.After(timeout):
t.Fatalf("bob did not send new channel to peer")
}
}
func TestFundingManagerNormalWorkflow(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// We will consume the channel updates as we go, so no buffering is
// needed.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
// Run through the process of opening the channel, up until the funding
// transaction is broadcasted.
localAmt := btcutil.Amount(500000)
pushAmt := btcutil.Amount(0)
capacity := localAmt + pushAmt
fundingOutPoint, fundingTx := openChannel(
t, alice, bob, localAmt, pushAmt, 1, updateChan, true,
)
// Check that neither Alice nor Bob sent an error message.
assertErrorNotSent(t, alice.msgChan)
assertErrorNotSent(t, bob.msgChan)
// Notify that transaction was mined.
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// The funding transaction was mined, so assert that both funding
// managers now have the state of this channel 'markedOpen' in their
// internal state machine.
assertMarkedOpen(t, alice, bob, fundingOutPoint)
// After the funding transaction is mined, Alice will send
// channelReady to Bob.
channelReadyAlice, ok := assertFundingMsgSent(
t, alice.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
// And similarly Bob will send channel_ready to Alice.
channelReadyBob, ok := assertFundingMsgSent(
t, bob.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
// Check that the state machine is updated accordingly
assertChannelReadySent(t, alice, bob, fundingOutPoint)
// Exchange the channelReady messages.
alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
bob.fundingMgr.ProcessFundingMsg(channelReadyAlice, alice)
// Check that they notify the breach arbiter and peer about the new
// channel.
assertHandleChannelReady(t, alice, bob)
// Make sure both fundingManagers send the expected channel
// announcements.
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
// Check that the state machine is updated accordingly
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
// The funding transaction is now confirmed, wait for the
// OpenStatusUpdate_ChanOpen update
waitForOpenUpdate(t, updateChan)
// Notify that six confirmations has been reached on funding
// transaction.
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// Make sure the fundingManagers exchange announcement signatures.
assertAnnouncementSignatures(t, alice, bob)
// The internal state-machine should now have deleted the channelStates
// from the database, as the channel is announced.
assertNoChannelState(t, alice, bob, fundingOutPoint)
// The forwarding policy for the channel announcement should
// have been deleted from the database, as the channel is announced.
assertNoFwdingPolicy(t, alice, bob, channelReadyAlice.ChanID)
}
// TestFundingManagerRejectCSV tests checking of local CSV values against our
// local CSV limit for incoming and outgoing channels.
func TestFundingManagerRejectCSV(t *testing.T) {
t.Run("csv too high", func(t *testing.T) {
testLocalCSVLimit(t, 400, 500)
})
t.Run("csv within limit", func(t *testing.T) {
testLocalCSVLimit(t, 600, 500)
})
}
// testLocalCSVLimit creates two funding managers, alice and bob, where alice
// has a limit on her maximum local CSV and bob sets his required CSV for alice.
// We test an incoming and outgoing channel, ensuring that alice accepts csvs
// below her maximum, and rejects those above it.
func testLocalCSVLimit(t *testing.T, aliceMaxCSV, bobRequiredCSV uint16) {
t.Parallel()
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// Set a maximum local delay in alice's config to aliceMaxCSV and
// overwrite bob's required remote delay function to return
// bobRequiredCSV.
alice.fundingMgr.cfg.MaxLocalCSVDelay = aliceMaxCSV
bob.fundingMgr.cfg.RequiredRemoteDelay = func(_ btcutil.Amount) uint16 {
return bobRequiredCSV
}
// For convenience, we bump our max pending channels to 2 so that we
// can test incoming and outgoing channels without needing to step
// through the full funding process.
alice.fundingMgr.cfg.MaxPendingChannels = 2
bob.fundingMgr.cfg.MaxPendingChannels = 2
// If our maximum is less than the value bob sets, we expect this test
// to fail.
expectFail := aliceMaxCSV < bobRequiredCSV
// First, we will initiate an outgoing channel from Alice -> Bob.
errChan := make(chan error, 1)
updateChan := make(chan *lnrpc.OpenStatusUpdate)
initReq := &InitFundingMsg{
Peer: bob,
TargetPubkey: bob.privKey.PubKey(),
ChainHash: *fundingNetParams.GenesisHash,
LocalFundingAmt: 200000,
FundingFeePerKw: 1000,
Updates: updateChan,
Err: errChan,
}
// Alice should have sent the OpenChannel message to Bob.
alice.fundingMgr.InitFundingWorkflow(initReq)
var aliceMsg lnwire.Message
select {
case aliceMsg = <-alice.msgChan:
case err := <-initReq.Err:
t.Fatalf("error init funding workflow: %v", err)
case <-time.After(time.Second * 5):
t.Fatalf("alice did not send OpenChannel message")
}
openChannelReq, ok := aliceMsg.(*lnwire.OpenChannel)
require.True(t, ok)
// Let Bob handle the init message.
bob.fundingMgr.ProcessFundingMsg(openChannelReq, alice)
// Bob should answer with an AcceptChannel message.
acceptChannelResponse := assertFundingMsgSent(
t, bob.msgChan, "AcceptChannel",
).(*lnwire.AcceptChannel)
// They now should both have pending reservations for this channel
// active.
assertNumPendingReservations(t, alice, bobPubKey, 1)
assertNumPendingReservations(t, bob, alicePubKey, 1)
// Forward the response to Alice.
alice.fundingMgr.ProcessFundingMsg(acceptChannelResponse, bob)
// At this point, Alice has received an AcceptChannel message from
// bob with the CSV value that he has set for her, and has to evaluate
// whether she wants to accept this channel. If we get an error, we
// assert that we expected the channel to fail, otherwise we assert that
// she proceeded with the channel open as usual.
select {
case err := <-errChan:
require.Error(t, err)
require.True(t, expectFail)
case msg := <-alice.msgChan:
_, ok := msg.(*lnwire.FundingCreated)
require.True(t, ok)
require.False(t, expectFail)
case <-time.After(time.Second):
t.Fatal("funding flow was not failed")
}
// We do not need to complete the rest of the funding flow (it is
// covered in other tests). So now we test that Alice will appropriately
// handle incoming channels, opening a channel from Bob->Alice.
errChan = make(chan error, 1)
updateChan = make(chan *lnrpc.OpenStatusUpdate)
initReq = &InitFundingMsg{
Peer: alice,
TargetPubkey: alice.privKey.PubKey(),
ChainHash: *fundingNetParams.GenesisHash,
LocalFundingAmt: 200000,
FundingFeePerKw: 1000,
Updates: updateChan,
Err: errChan,
}
bob.fundingMgr.InitFundingWorkflow(initReq)
// Bob should have sent the OpenChannel message to Alice.
var bobMsg lnwire.Message
select {
case bobMsg = <-bob.msgChan:
case err := <-initReq.Err:
t.Fatalf("bob OpenChannel message failed: %v", err)
case <-time.After(time.Second * 5):
t.Fatalf("bob did not send OpenChannel message")
}
openChannelReq, ok = bobMsg.(*lnwire.OpenChannel)
require.True(t, ok)
// Let Alice handle the init message.
alice.fundingMgr.ProcessFundingMsg(openChannelReq, bob)
// We expect a error message from Alice if we're expecting the channel
// to fail, otherwise we expect her to proceed with the channel as
// usual.
select {
case msg := <-alice.msgChan:
var ok bool
if expectFail {
_, ok = msg.(*lnwire.Error)
} else {
_, ok = msg.(*lnwire.AcceptChannel)
}
require.True(t, ok)
case <-time.After(time.Second * 5):
t.Fatal("funding flow was not failed")
}
}
func TestFundingManagerRestartBehavior(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// Run through the process of opening the channel, up until the funding
// transaction is broadcasted.
localAmt := btcutil.Amount(500000)
pushAmt := btcutil.Amount(0)
capacity := localAmt + pushAmt
updateChan := make(chan *lnrpc.OpenStatusUpdate)
fundingOutPoint, fundingTx := openChannel(
t, alice, bob, localAmt, pushAmt, 1, updateChan, true,
)
// After the funding transaction gets mined, both nodes will send the
// channelReady message to the other peer. If the funding node fails
// before this message has been successfully sent, it should retry
// sending it on restart. We mimic this behavior by letting the
// SendToPeer method return an error, as if the message was not
// successfully sent. We then recreate the fundingManager and make sure
// it continues the process as expected. We'll save the current
// implementation of sendMessage to restore the original behavior later
// on.
workingSendMessage := bob.sendMessage
bob.sendMessage = func(msg lnwire.Message) error {
return fmt.Errorf("intentional error in SendToPeer")
}
notifyWhenOnline := func(peer [33]byte, con chan<- lnpeer.Peer) {
// Intentionally empty
}
alice.fundingMgr.cfg.NotifyWhenOnline = notifyWhenOnline
// Notify that transaction was mined
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// The funding transaction was mined, so assert that both funding
// managers now have the state of this channel 'markedOpen' in their
// internal state machine.
assertMarkedOpen(t, alice, bob, fundingOutPoint)
// After the funding transaction was mined, Bob should have successfully
// sent the channelReady message, while Alice failed sending it. In
// Alice's case this means that there should be no messages for Bob, and
// the channel should still be in state 'markedOpen'
select {
case msg := <-alice.msgChan:
t.Fatalf("did not expect any message from Alice: %v", msg)
default:
// Expected.
}
// Bob will send channel_ready to Alice.
channelReadyBob, ok := assertFundingMsgSent(
t, bob.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
// Alice should still be markedOpen
assertDatabaseState(t, alice, fundingOutPoint, markedOpen)
// While Bob successfully sent channelReady.
assertDatabaseState(t, bob, fundingOutPoint, channelReadySent)
// We now recreate Alice's fundingManager with the correct sendMessage
// implementation, and expect it to retry sending the channelReady
// message. We'll explicitly shut down Alice's funding manager to
// prevent a race when overriding the sendMessage implementation.
if err := alice.fundingMgr.Stop(); err != nil {
t.Fatalf("failed stop funding manager: %v", err)
}
bob.sendMessage = workingSendMessage
recreateAliceFundingManager(t, alice)
// Intentionally make the channel announcements fail
alice.fundingMgr.cfg.SendAnnouncement = func(msg lnwire.Message,
_ ...discovery.OptionalMsgField) chan error {
errChan := make(chan error, 1)
errChan <- fmt.Errorf("intentional error in SendAnnouncement")
return errChan
}
channelReadyAlice, ok := assertFundingMsgSent(
t, alice.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
// The state should now be channelReadySent
assertDatabaseState(t, alice, fundingOutPoint, channelReadySent)
// Check that the channel announcements were never sent
select {
case ann := <-alice.announceChan:
t.Fatalf("unexpectedly got channel announcement message: %v",
ann)
default:
// Expected
}
// Exchange the channelReady messages.
alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
bob.fundingMgr.ProcessFundingMsg(channelReadyAlice, alice)
// Check that they notify the breach arbiter and peer about the new
// channel.
assertHandleChannelReady(t, alice, bob)
// Next up, we check that Alice rebroadcasts the announcement
// messages on restart. Bob should as expected send announcements.
recreateAliceFundingManager(t, alice)
time.Sleep(300 * time.Millisecond)
// Make sure both fundingManagers send the expected channel
// announcements.
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
// Check that the state machine is updated accordingly
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
// Next, we check that Alice sends the announcement signatures
// on restart after six confirmations. Bob should as expected send
// them as well.
recreateAliceFundingManager(t, alice)
time.Sleep(300 * time.Millisecond)
// Notify that six confirmations has been reached on funding
// transaction.
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// Make sure the fundingManagers exchange announcement signatures.
assertAnnouncementSignatures(t, alice, bob)
// The internal state-machine should now have deleted the channelStates
// from the database, as the channel is announced.
assertNoChannelState(t, alice, bob, fundingOutPoint)
// The forwarding policy for the channel announcement should
// have been deleted from the database, as the channel is announced.
assertNoFwdingPolicy(t, alice, bob, channelReadyAlice.ChanID)
}
// TestFundingManagerOfflinePeer checks that the fundingManager waits for the
// server to notify when the peer comes online, in case sending the
// channelReady message fails the first time.
func TestFundingManagerOfflinePeer(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// Run through the process of opening the channel, up until the funding
// transaction is broadcasted.
localAmt := btcutil.Amount(500000)
pushAmt := btcutil.Amount(0)
capacity := localAmt + pushAmt
updateChan := make(chan *lnrpc.OpenStatusUpdate)
fundingOutPoint, fundingTx := openChannel(
t, alice, bob, localAmt, pushAmt, 1, updateChan, true,
)
// After the funding transaction gets mined, both nodes will send the
// channelReady message to the other peer. If the funding node fails
// to send the channelReady message to the peer, it should wait for
// the server to notify it that the peer is back online, and try again.
// We'll save the current implementation of sendMessage to restore the
// original behavior later on.
workingSendMessage := bob.sendMessage
bob.sendMessage = func(msg lnwire.Message) error {
return fmt.Errorf("intentional error in SendToPeer")
}
peerChan := make(chan [33]byte, 1)
conChan := make(chan chan<- lnpeer.Peer, 1)
alice.fundingMgr.cfg.NotifyWhenOnline = func(peer [33]byte,
connected chan<- lnpeer.Peer) {
peerChan <- peer
conChan <- connected
}
// Notify that transaction was mined
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// The funding transaction was mined, so assert that both funding
// managers now have the state of this channel 'markedOpen' in their
// internal state machine.
assertMarkedOpen(t, alice, bob, fundingOutPoint)
// After the funding transaction was mined, Bob should have successfully
// sent the channelReady message, while Alice failed sending it. In
// Alice's case this means that there should be no messages for Bob, and
// the channel should still be in state 'markedOpen'
select {
case msg := <-alice.msgChan:
t.Fatalf("did not expect any message from Alice: %v", msg)
default:
// Expected.
}
// Bob will send channel_ready to Alice
channelReadyBob, ok := assertFundingMsgSent(
t, bob.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
// Alice should still be markedOpen
assertDatabaseState(t, alice, fundingOutPoint, markedOpen)
// While Bob successfully sent channelReady.
assertDatabaseState(t, bob, fundingOutPoint, channelReadySent)
// Alice should be waiting for the server to notify when Bob comes back
// online.
var peer [33]byte
var con chan<- lnpeer.Peer
select {
case peer = <-peerChan:
// Expected
case <-time.After(time.Second * 3):
t.Fatalf("alice did not register peer with server")
}
select {
case con = <-conChan:
// Expected
case <-time.After(time.Second * 3):
t.Fatalf("alice did not register connectedChan with server")
}
if !bytes.Equal(peer[:], bobPubKey.SerializeCompressed()) {
t.Fatalf("expected to receive Bob's pubkey (%v), instead "+
"got %v", bobPubKey, peer)
}
// Before sending on the con chan, update Alice's NotifyWhenOnline
// function so that the next invocation in receivedChannelReady will
// use this new function.
alice.fundingMgr.cfg.NotifyWhenOnline = func(peer [33]byte,
connected chan<- lnpeer.Peer) {
connected <- bob
}
// Restore the correct sendMessage implementation, and notify that Bob
// is back online.
bob.sendMessage = workingSendMessage
con <- bob
// This should make Alice send the channelReady.
channelReadyAlice, ok := assertFundingMsgSent(
t, alice.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
// The state should now be channelReadySent
assertDatabaseState(t, alice, fundingOutPoint, channelReadySent)
// Exchange the channelReady messages.
alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
bob.fundingMgr.ProcessFundingMsg(channelReadyAlice, alice)
// Check that they notify the breach arbiter and peer about the new
// channel.
assertHandleChannelReady(t, alice, bob)
// Make sure both fundingManagers send the expected channel
// announcements.
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
// Check that the state machine is updated accordingly
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
// The funding transaction is now confirmed, wait for the
// OpenStatusUpdate_ChanOpen update
waitForOpenUpdate(t, updateChan)
// Notify that six confirmations has been reached on funding
// transaction.
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// Make sure both fundingManagers send the expected announcement
// signatures.
assertAnnouncementSignatures(t, alice, bob)
// The internal state-machine should now have deleted the channelStates
// from the database, as the channel is announced.
assertNoChannelState(t, alice, bob, fundingOutPoint)
// The forwarding policy for the channel announcement should
// have been deleted from the database, as the channel is announced.
assertNoFwdingPolicy(t, alice, bob, channelReadyAlice.ChanID)
}
// TestFundingManagerPeerTimeoutAfterInitFunding checks that the zombie sweeper
// will properly clean up a zombie reservation that times out after the
// InitFundingMsg has been handled.
func TestFundingManagerPeerTimeoutAfterInitFunding(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// We will consume the channel updates as we go, so no buffering is
// needed.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
// Create a funding request and start the workflow.
errChan := make(chan error, 1)
initReq := &InitFundingMsg{
Peer: bob,
TargetPubkey: bob.privKey.PubKey(),
ChainHash: *fundingNetParams.GenesisHash,
LocalFundingAmt: 500000,
PushAmt: lnwire.NewMSatFromSatoshis(0),
Private: false,
Updates: updateChan,
Err: errChan,
}
alice.fundingMgr.InitFundingWorkflow(initReq)
// Alice should have sent the OpenChannel message to Bob.
var aliceMsg lnwire.Message
select {
case aliceMsg = <-alice.msgChan:
case err := <-initReq.Err:
t.Fatalf("error init funding workflow: %v", err)
case <-time.After(time.Second * 5):
t.Fatalf("alice did not send OpenChannel message")
}
_, ok := aliceMsg.(*lnwire.OpenChannel)
if !ok {
errorMsg, gotError := aliceMsg.(*lnwire.Error)
if gotError {
t.Fatalf("expected OpenChannel to be sent "+
"from bob, instead got error: %v",
errorMsg.Error())
}
t.Fatalf("expected OpenChannel to be sent from "+
"alice, instead got %T", aliceMsg)
}
// Alice should have a new pending reservation.
assertNumPendingReservations(t, alice, bobPubKey, 1)
// Make sure Alice's reservation times out and then run her zombie
// sweeper.
time.Sleep(1 * time.Millisecond)
go alice.fundingMgr.pruneZombieReservations()
// Alice should have sent an Error message to Bob.
assertErrorSent(t, alice.msgChan)
// Alice's zombie reservation should have been pruned.
assertNumPendingReservations(t, alice, bobPubKey, 0)
}
// TestFundingManagerPeerTimeoutAfterFundingOpen checks that the zombie sweeper
// will properly clean up a zombie reservation that times out after the
// fundingOpenMsg has been handled.
func TestFundingManagerPeerTimeoutAfterFundingOpen(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// We will consume the channel updates as we go, so no buffering is
// needed.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
// Create a funding request and start the workflow.
errChan := make(chan error, 1)
initReq := &InitFundingMsg{
Peer: bob,
TargetPubkey: bob.privKey.PubKey(),
ChainHash: *fundingNetParams.GenesisHash,
LocalFundingAmt: 500000,
PushAmt: lnwire.NewMSatFromSatoshis(0),
Private: false,
Updates: updateChan,
Err: errChan,
}
alice.fundingMgr.InitFundingWorkflow(initReq)
// Alice should have sent the OpenChannel message to Bob.
var aliceMsg lnwire.Message
select {
case aliceMsg = <-alice.msgChan:
case err := <-initReq.Err:
t.Fatalf("error init funding workflow: %v", err)
case <-time.After(time.Second * 5):
t.Fatalf("alice did not send OpenChannel message")
}
openChannelReq, ok := aliceMsg.(*lnwire.OpenChannel)
if !ok {
errorMsg, gotError := aliceMsg.(*lnwire.Error)
if gotError {
t.Fatalf("expected OpenChannel to be sent "+
"from bob, instead got error: %v",
errorMsg.Error())
}
t.Fatalf("expected OpenChannel to be sent from "+
"alice, instead got %T", aliceMsg)
}
// Alice should have a new pending reservation.
assertNumPendingReservations(t, alice, bobPubKey, 1)
// Let Bob handle the init message.
bob.fundingMgr.ProcessFundingMsg(openChannelReq, alice)
// Bob should answer with an AcceptChannel.
assertFundingMsgSent(t, bob.msgChan, "AcceptChannel")
// Bob should have a new pending reservation.
assertNumPendingReservations(t, bob, alicePubKey, 1)
// Make sure Bob's reservation times out and then run his zombie
// sweeper.
time.Sleep(1 * time.Millisecond)
go bob.fundingMgr.pruneZombieReservations()
// Bob should have sent an Error message to Alice.
assertErrorSent(t, bob.msgChan)
// Bob's zombie reservation should have been pruned.
assertNumPendingReservations(t, bob, alicePubKey, 0)
}
// TestFundingManagerPeerTimeoutAfterFundingAccept checks that the zombie
// sweeper will properly clean up a zombie reservation that times out after the
// fundingAcceptMsg has been handled.
func TestFundingManagerPeerTimeoutAfterFundingAccept(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// We will consume the channel updates as we go, so no buffering is
// needed.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
// Create a funding request and start the workflow.
errChan := make(chan error, 1)
initReq := &InitFundingMsg{
Peer: bob,
TargetPubkey: bob.privKey.PubKey(),
ChainHash: *fundingNetParams.GenesisHash,
LocalFundingAmt: 500000,
PushAmt: lnwire.NewMSatFromSatoshis(0),
Private: false,
Updates: updateChan,
Err: errChan,
}
alice.fundingMgr.InitFundingWorkflow(initReq)
// Alice should have sent the OpenChannel message to Bob.
var aliceMsg lnwire.Message
select {
case aliceMsg = <-alice.msgChan:
case err := <-initReq.Err:
t.Fatalf("error init funding workflow: %v", err)
case <-time.After(time.Second * 5):
t.Fatalf("alice did not send OpenChannel message")
}
openChannelReq, ok := aliceMsg.(*lnwire.OpenChannel)
if !ok {
errorMsg, gotError := aliceMsg.(*lnwire.Error)
if gotError {
t.Fatalf("expected OpenChannel to be sent "+
"from bob, instead got error: %v",
errorMsg.Error())
}
t.Fatalf("expected OpenChannel to be sent from "+
"alice, instead got %T", aliceMsg)
}
// Alice should have a new pending reservation.
assertNumPendingReservations(t, alice, bobPubKey, 1)
// Let Bob handle the init message.
bob.fundingMgr.ProcessFundingMsg(openChannelReq, alice)
// Bob should answer with an AcceptChannel.
acceptChannelResponse := assertFundingMsgSent(
t, bob.msgChan, "AcceptChannel",
).(*lnwire.AcceptChannel)
// Bob should have a new pending reservation.
assertNumPendingReservations(t, bob, alicePubKey, 1)
// Forward the response to Alice.
alice.fundingMgr.ProcessFundingMsg(acceptChannelResponse, bob)
// Alice responds with a FundingCreated messages.
assertFundingMsgSent(t, alice.msgChan, "FundingCreated")
// Make sure Alice's reservation times out and then run her zombie
// sweeper.
time.Sleep(1 * time.Millisecond)
go alice.fundingMgr.pruneZombieReservations()
// Alice should have sent an Error message to Bob.
assertErrorSent(t, alice.msgChan)
// Alice's zombie reservation should have been pruned.
assertNumPendingReservations(t, alice, bobPubKey, 0)
}
func TestFundingManagerFundingTimeout(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// We will consume the channel updates as we go, so no buffering is
// needed.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
// Run through the process of opening the channel, up until the funding
// transaction is broadcasted.
_, _ = openChannel(t, alice, bob, 500000, 0, 1, updateChan, true)
// Bob will at this point be waiting for the funding transaction to be
// confirmed, so the channel should be considered pending.
pendingChannels, err := bob.fundingMgr.cfg.Wallet.Cfg.Database.FetchPendingChannels()
require.NoError(t, err, "unable to fetch pending channels")
if len(pendingChannels) != 1 {
t.Fatalf("Expected Bob to have 1 pending channel, had %v",
len(pendingChannels))
}
// We expect Bob to forget the channel after 2016 blocks (2 weeks), so
// mine 2016-1, and check that it is still pending.
bob.mockNotifier.epochChan <- &chainntnfs.BlockEpoch{
Height: fundingBroadcastHeight +
MaxWaitNumBlocksFundingConf - 1,
}
// Bob should still be waiting for the channel to open.
assertNumPendingChannelsRemains(t, bob, 1)
bob.mockNotifier.epochChan <- &chainntnfs.BlockEpoch{
Height: fundingBroadcastHeight + MaxWaitNumBlocksFundingConf,
}
// Bob should have sent an Error message to Alice.
assertErrorSent(t, bob.msgChan)
// Should not be pending anymore.
assertNumPendingChannelsBecomes(t, bob, 0)
}
// TestFundingManagerFundingNotTimeoutInitiator checks that if the user was
// the channel initiator, that it does not timeout when the lnd restarts.
func TestFundingManagerFundingNotTimeoutInitiator(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// We will consume the channel updates as we go, so no buffering is
// needed.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
// Run through the process of opening the channel, up until the funding
// transaction is broadcasted.
_, _ = openChannel(t, alice, bob, 500000, 0, 1, updateChan, true)
// Alice will at this point be waiting for the funding transaction to be
// confirmed, so the channel should be considered pending.
pendingChannels, err := alice.fundingMgr.cfg.Wallet.Cfg.Database.FetchPendingChannels()
require.NoError(t, err, "unable to fetch pending channels")
if len(pendingChannels) != 1 {
t.Fatalf("Expected Alice to have 1 pending channel, had %v",
len(pendingChannels))
}
recreateAliceFundingManager(t, alice)
// We should receive the rebroadcasted funding txn.
select {
case <-alice.publTxChan:
case <-time.After(time.Second * 5):
t.Fatalf("alice did not publish funding tx")
}
// Increase the height to 1 minus the MaxWaitNumBlocksFundingConf
// height.
alice.mockNotifier.epochChan <- &chainntnfs.BlockEpoch{
Height: fundingBroadcastHeight +
MaxWaitNumBlocksFundingConf - 1,
}
bob.mockNotifier.epochChan <- &chainntnfs.BlockEpoch{
Height: fundingBroadcastHeight +
MaxWaitNumBlocksFundingConf - 1,
}
// Assert both and Alice and Bob still have 1 pending channels.
assertNumPendingChannelsRemains(t, alice, 1)
assertNumPendingChannelsRemains(t, bob, 1)
// Increase both Alice and Bob to MaxWaitNumBlocksFundingConf height.
alice.mockNotifier.epochChan <- &chainntnfs.BlockEpoch{
Height: fundingBroadcastHeight + MaxWaitNumBlocksFundingConf,
}
bob.mockNotifier.epochChan <- &chainntnfs.BlockEpoch{
Height: fundingBroadcastHeight + MaxWaitNumBlocksFundingConf,
}
// Since Alice was the initiator, the channel should not have timed out.
assertNumPendingChannelsRemains(t, alice, 1)
// Bob should have sent an Error message to Alice.
assertErrorSent(t, bob.msgChan)
// Since Bob was not the initiator, the channel should timeout.
assertNumPendingChannelsBecomes(t, bob, 0)
}
// TestFundingManagerReceiveChannelReadyTwice checks that the fundingManager
// continues to operate as expected in case we receive a duplicate channelReady
// message.
func TestFundingManagerReceiveChannelReadyTwice(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// We will consume the channel updates as we go, so no buffering is
// needed.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
// Run through the process of opening the channel, up until the funding
// transaction is broadcasted.
localAmt := btcutil.Amount(500000)
pushAmt := btcutil.Amount(0)
capacity := localAmt + pushAmt
fundingOutPoint, fundingTx := openChannel(
t, alice, bob, localAmt, pushAmt, 1, updateChan, true,
)
// Notify that transaction was mined
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// The funding transaction was mined, so assert that both funding
// managers now have the state of this channel 'markedOpen' in their
// internal state machine.
assertMarkedOpen(t, alice, bob, fundingOutPoint)
// After the funding transaction is mined, Alice will send
// channelReady to Bob.
channelReadyAlice, ok := assertFundingMsgSent(
t, alice.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
// And similarly Bob will send channel_ready to Alice.
channelReadyBob, ok := assertFundingMsgSent(
t, bob.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
// Check that the state machine is updated accordingly
assertChannelReadySent(t, alice, bob, fundingOutPoint)
// Send the channelReady message twice to Alice, and once to Bob.
alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
bob.fundingMgr.ProcessFundingMsg(channelReadyAlice, alice)
// Check that they notify the breach arbiter and peer about the new
// channel.
assertHandleChannelReady(t, alice, bob)
// Alice should not send the channel state the second time, as the
// second channel_ready should just be ignored.
select {
case <-alice.newChannels:
t.Fatalf("alice sent new channel to peer a second time")
case <-time.After(time.Millisecond * 300):
// Expected
}
// Another channelReady should also be ignored, since Alice should
// have updated her database at this point.
alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
select {
case <-alice.newChannels:
t.Fatalf("alice sent new channel to peer a second time")
case <-time.After(time.Millisecond * 300):
// Expected
}
// Make sure both fundingManagers send the expected channel
// announcements.
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
// Check that the state machine is updated accordingly
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
// The funding transaction is now confirmed, wait for the
// OpenStatusUpdate_ChanOpen update
waitForOpenUpdate(t, updateChan)
// Notify that six confirmations has been reached on funding
// transaction.
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// Make sure the fundingManagers exchange announcement signatures.
assertAnnouncementSignatures(t, alice, bob)
// The internal state-machine should now have deleted the channelStates
// from the database, as the channel is announced.
assertNoChannelState(t, alice, bob, fundingOutPoint)
// The forwarding policy for the channel announcement should
// have been deleted from the database, as the channel is announced.
assertNoFwdingPolicy(t, alice, bob, channelReadyAlice.ChanID)
}
// TestFundingManagerRestartAfterChanAnn checks that the fundingManager properly
// handles receiving a channelReady after the its own channelReady and channel
// announcement is sent and gets restarted.
func TestFundingManagerRestartAfterChanAnn(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// We will consume the channel updates as we go, so no buffering is
// needed.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
// Run through the process of opening the channel, up until the funding
// transaction is broadcasted.
localAmt := btcutil.Amount(500000)
pushAmt := btcutil.Amount(0)
capacity := localAmt + pushAmt
fundingOutPoint, fundingTx := openChannel(
t, alice, bob, localAmt, pushAmt, 1, updateChan, true,
)
// Notify that transaction was mined
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// The funding transaction was mined, so assert that both funding
// managers now have the state of this channel 'markedOpen' in their
// internal state machine.
assertMarkedOpen(t, alice, bob, fundingOutPoint)
// After the funding transaction is mined, Alice will send
// channelReady to Bob.
channelReadyAlice, ok := assertFundingMsgSent(
t, alice.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
// And similarly Bob will send channel_ready to Alice.
channelReadyBob, ok := assertFundingMsgSent(
t, bob.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
// Check that the state machine is updated accordingly
assertChannelReadySent(t, alice, bob, fundingOutPoint)
// Exchange the channelReady messages.
alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
bob.fundingMgr.ProcessFundingMsg(channelReadyAlice, alice)
// Check that they notify the breach arbiter and peer about the new
// channel.
assertHandleChannelReady(t, alice, bob)
// Make sure both fundingManagers send the expected channel
// announcements.
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
// Check that the state machine is updated accordingly
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
// The funding transaction is now confirmed, wait for the
// OpenStatusUpdate_ChanOpen update
waitForOpenUpdate(t, updateChan)
// At this point we restart Alice's fundingManager, before she receives
// the channelReady message. After restart, she will receive it, and
// we expect her to be able to handle it correctly.
recreateAliceFundingManager(t, alice)
// Notify that six confirmations has been reached on funding
// transaction.
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// Make sure both fundingManagers send the expected channel
// announcements.
assertAnnouncementSignatures(t, alice, bob)
// The internal state-machine should now have deleted the channelStates
// from the database, as the channel is announced.
assertNoChannelState(t, alice, bob, fundingOutPoint)
// The forwarding policy for the channel announcement should
// have been deleted from the database, as the channel is announced.
assertNoFwdingPolicy(t, alice, bob, channelReadyAlice.ChanID)
}
// TestFundingManagerRestartAfterReceivingChannelReady checks that the
// fundingManager continues to operate as expected after it has received
// channelReady and then gets restarted.
func TestFundingManagerRestartAfterReceivingChannelReady(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// We will consume the channel updates as we go, so no buffering is
// needed.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
// Run through the process of opening the channel, up until the funding
// transaction is broadcasted.
localAmt := btcutil.Amount(500000)
pushAmt := btcutil.Amount(0)
capacity := localAmt + pushAmt
fundingOutPoint, fundingTx := openChannel(
t, alice, bob, localAmt, pushAmt, 1, updateChan, true,
)
// Notify that transaction was mined
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// The funding transaction was mined, so assert that both funding
// managers now have the state of this channel 'markedOpen' in their
// internal state machine.
assertMarkedOpen(t, alice, bob, fundingOutPoint)
// After the funding transaction is mined, Alice will send
// channelReady to Bob.
channelReadyAlice, ok := assertFundingMsgSent(
t, alice.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
// And similarly Bob will send channel_ready to Alice.
channelReadyBob, ok := assertFundingMsgSent(
t, bob.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
// Check that the state machine is updated accordingly
assertChannelReadySent(t, alice, bob, fundingOutPoint)
// Let Alice immediately get the channelReady message.
alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
// Also let Bob get the channelReady message.
bob.fundingMgr.ProcessFundingMsg(channelReadyAlice, alice)
// Check that they notify the breach arbiter and peer about the new
// channel.
assertHandleChannelReady(t, alice, bob)
// At this point we restart Alice's fundingManager.
recreateAliceFundingManager(t, alice)
// Make sure both fundingManagers send the expected channel
// announcements.
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
// Check that the state machine is updated accordingly
assertAddedToRouterGraph(t, alice, bob, fundingOutPoint)
// Notify that six confirmations has been reached on funding
// transaction.
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// Make sure both fundingManagers send the expected channel
// announcements.
assertAnnouncementSignatures(t, alice, bob)
// The internal state-machine should now have deleted the channelStates
// from the database, as the channel is announced.
assertNoChannelState(t, alice, bob, fundingOutPoint)
// The forwarding policy for the channel announcement should
// have been deleted from the database, as the channel is announced.
assertNoFwdingPolicy(t, alice, bob, channelReadyAlice.ChanID)
}
// TestFundingManagerPrivateChannel tests that if we open a private channel
// (a channel not supposed to be announced to the rest of the network),
// the announcementSignatures nor the nodeAnnouncement messages are sent.
func TestFundingManagerPrivateChannel(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// We will consume the channel updates as we go, so no buffering is
// needed.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
// Run through the process of opening the channel, up until the funding
// transaction is broadcasted.
localAmt := btcutil.Amount(500000)
pushAmt := btcutil.Amount(0)
capacity := localAmt + pushAmt
fundingOutPoint, fundingTx := openChannel(
t, alice, bob, localAmt, pushAmt, 1, updateChan, false,
)
// Notify that transaction was mined
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// The funding transaction was mined, so assert that both funding
// managers now have the state of this channel 'markedOpen' in their
// internal state machine.
assertMarkedOpen(t, alice, bob, fundingOutPoint)
// After the funding transaction is mined, Alice will send
// channelReady to Bob.
channelReadyAlice, ok := assertFundingMsgSent(
t, alice.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
// And similarly Bob will send channel_ready to Alice.
channelReadyBob, ok := assertFundingMsgSent(
t, bob.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
// Check that the state machine is updated accordingly
assertChannelReadySent(t, alice, bob, fundingOutPoint)
// Exchange the channelReady messages.
alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
bob.fundingMgr.ProcessFundingMsg(channelReadyAlice, alice)
// Check that they notify the breach arbiter and peer about the new
// channel.
assertHandleChannelReady(t, alice, bob)
// Make sure both fundingManagers send the expected channel
// announcements.
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
// The funding transaction is now confirmed, wait for the
// OpenStatusUpdate_ChanOpen update
waitForOpenUpdate(t, updateChan)
// Notify that six confirmations has been reached on funding
// transaction.
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// Since this is a private channel, we shouldn't receive the
// announcement signatures.
select {
case ann := <-alice.announceChan:
t.Fatalf("unexpectedly got channel announcement message: %v",
ann)
case <-time.After(300 * time.Millisecond):
// Expected
}
select {
case ann := <-bob.announceChan:
t.Fatalf("unexpectedly got channel announcement message: %v",
ann)
case <-time.After(300 * time.Millisecond):
// Expected
}
// We should however receive each side's node announcement.
select {
case msg := <-alice.msgChan:
if _, ok := msg.(*lnwire.NodeAnnouncement); !ok {
t.Fatalf("expected to receive node announcement")
}
case <-time.After(time.Second):
t.Fatalf("expected to receive node announcement")
}
select {
case msg := <-bob.msgChan:
if _, ok := msg.(*lnwire.NodeAnnouncement); !ok {
t.Fatalf("expected to receive node announcement")
}
case <-time.After(time.Second):
t.Fatalf("expected to receive node announcement")
}
// The internal state-machine should now have deleted the channelStates
// from the database, as the channel is announced.
assertNoChannelState(t, alice, bob, fundingOutPoint)
// The forwarding policy for the channel announcement should
// have been deleted from the database.
assertNoFwdingPolicy(t, alice, bob, channelReadyAlice.ChanID)
}
// TestFundingManagerPrivateRestart tests that the privacy guarantees granted
// by the private channel persist even on restart. This means that the
// announcement signatures nor the node announcement messages are sent upon
// restart.
func TestFundingManagerPrivateRestart(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// We will consume the channel updates as we go, so no buffering is
// needed.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
// Run through the process of opening the channel, up until the funding
// transaction is broadcasted.
localAmt := btcutil.Amount(500000)
pushAmt := btcutil.Amount(0)
capacity := localAmt + pushAmt
fundingOutPoint, fundingTx := openChannel(
t, alice, bob, localAmt, pushAmt, 1, updateChan, false,
)
// Notify that transaction was mined
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// The funding transaction was mined, so assert that both funding
// managers now have the state of this channel 'markedOpen' in their
// internal state machine.
assertMarkedOpen(t, alice, bob, fundingOutPoint)
// After the funding transaction is mined, Alice will send
// channelReady to Bob.
channelReadyAlice, ok := assertFundingMsgSent(
t, alice.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
// And similarly Bob will send channel_ready to Alice.
channelReadyBob, ok := assertFundingMsgSent(
t, bob.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
// Check that the state machine is updated accordingly
assertChannelReadySent(t, alice, bob, fundingOutPoint)
// Exchange the channelReady messages.
alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
bob.fundingMgr.ProcessFundingMsg(channelReadyAlice, alice)
// Check that they notify the breach arbiter and peer about the new
// channel.
assertHandleChannelReady(t, alice, bob)
// Make sure both fundingManagers send the expected channel
// announcements.
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil, nil, nil)
// Note: We don't check for the addedToRouterGraph state because in
// the private channel mode, the state is quickly changed from
// addedToRouterGraph to deleted from the database since the public
// announcement phase is skipped.
// The funding transaction is now confirmed, wait for the
// OpenStatusUpdate_ChanOpen update
waitForOpenUpdate(t, updateChan)
// Notify that six confirmations has been reached on funding
// transaction.
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// Since this is a private channel, we shouldn't receive the public
// channel announcement messages.
select {
case ann := <-alice.announceChan:
t.Fatalf("unexpectedly got channel announcement message: %v",
ann)
case <-time.After(300 * time.Millisecond):
}
select {
case ann := <-bob.announceChan:
t.Fatalf("unexpectedly got channel announcement message: %v",
ann)
case <-time.After(300 * time.Millisecond):
}
// We should however receive each side's node announcement.
select {
case msg := <-alice.msgChan:
if _, ok := msg.(*lnwire.NodeAnnouncement); !ok {
t.Fatalf("expected to receive node announcement")
}
case <-time.After(time.Second):
t.Fatalf("expected to receive node announcement")
}
select {
case msg := <-bob.msgChan:
if _, ok := msg.(*lnwire.NodeAnnouncement); !ok {
t.Fatalf("expected to receive node announcement")
}
case <-time.After(time.Second):
t.Fatalf("expected to receive node announcement")
}
// Restart Alice's fundingManager so we can prove that the public
// channel announcements are not sent upon restart and that the private
// setting persists upon restart.
recreateAliceFundingManager(t, alice)
select {
case ann := <-alice.announceChan:
t.Fatalf("unexpectedly got channel announcement message: %v",
ann)
case <-time.After(300 * time.Millisecond):
// Expected
}
select {
case ann := <-bob.announceChan:
t.Fatalf("unexpectedly got channel announcement message: %v",
ann)
case <-time.After(300 * time.Millisecond):
// Expected
}
// The internal state-machine should now have deleted the channelStates
// from the database, as the channel is announced.
assertNoChannelState(t, alice, bob, fundingOutPoint)
// The forwarding policy for the channel announcement should
// have been deleted from the database.
assertNoFwdingPolicy(t, alice, bob, channelReadyAlice.ChanID)
}
// TestFundingManagerCustomChannelParameters checks that custom requirements we
// specify during the channel funding flow is preserved correctly on both sides.
func TestFundingManagerCustomChannelParameters(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// This is the custom parameters we'll use.
const csvDelay = 67
const minHtlcIn = 1234
const maxValueInFlight = 50000
const fundingAmt = 5000000
const chanReserve = 100000
// Use custom channel fees. These will show up in the channel
// reservation context.
var (
baseFee uint64 = 42
feeRate uint64 = 1337
)
// We will consume the channel updates as we go, so no buffering is
// needed.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
localAmt := btcutil.Amount(5000000)
pushAmt := btcutil.Amount(0)
capacity := localAmt + pushAmt
// Create a funding request with the custom parameters and start the
// workflow.
errChan := make(chan error, 1)
initReq := &InitFundingMsg{
Peer: bob,
TargetPubkey: bob.privKey.PubKey(),
ChainHash: *fundingNetParams.GenesisHash,
LocalFundingAmt: localAmt,
PushAmt: lnwire.NewMSatFromSatoshis(pushAmt),
Private: false,
MaxValueInFlight: maxValueInFlight,
MinHtlcIn: minHtlcIn,
RemoteCsvDelay: csvDelay,
RemoteChanReserve: chanReserve,
Updates: updateChan,
Err: errChan,
BaseFee: &baseFee,
FeeRate: &feeRate,
}
alice.fundingMgr.InitFundingWorkflow(initReq)
// Alice should have sent the OpenChannel message to Bob.
var aliceMsg lnwire.Message
select {
case aliceMsg = <-alice.msgChan:
case err := <-initReq.Err:
t.Fatalf("error init funding workflow: %v", err)
case <-time.After(time.Second * 5):
t.Fatalf("alice did not send OpenChannel message")
}
openChannelReq, ok := aliceMsg.(*lnwire.OpenChannel)
if !ok {
errorMsg, gotError := aliceMsg.(*lnwire.Error)
if gotError {
t.Fatalf("expected OpenChannel to be sent "+
"from bob, instead got error: %v",
errorMsg.Error())
}
t.Fatalf("expected OpenChannel to be sent from "+
"alice, instead got %T", aliceMsg)
}
// Check that the custom CSV delay is sent as part of OpenChannel.
if openChannelReq.CsvDelay != csvDelay {
t.Fatalf("expected OpenChannel to have CSV delay %v, got %v",
csvDelay, openChannelReq.CsvDelay)
}
// Check that the custom minHTLC value is sent.
if openChannelReq.HtlcMinimum != minHtlcIn {
t.Fatalf("expected OpenChannel to have minHtlc %v, got %v",
minHtlcIn, openChannelReq.HtlcMinimum)
}
// Check that the max value in flight is sent as part of OpenChannel.
if openChannelReq.MaxValueInFlight != maxValueInFlight {
t.Fatalf("expected OpenChannel to have MaxValueInFlight %v, "+
"got %v", maxValueInFlight,
openChannelReq.MaxValueInFlight)
}
// Check that the custom remoteChanReserve value is sent.
if openChannelReq.ChannelReserve != chanReserve {
t.Fatalf("expected OpenChannel to have chanReserve %v, got %v",
chanReserve, openChannelReq.ChannelReserve)
}
chanID := openChannelReq.PendingChannelID
// Let Bob handle the init message.
bob.fundingMgr.ProcessFundingMsg(openChannelReq, alice)
// Bob should answer with an AcceptChannel message.
acceptChannelResponse := assertFundingMsgSent(
t, bob.msgChan, "AcceptChannel",
).(*lnwire.AcceptChannel)
// Bob should require the default delay of 4.
if acceptChannelResponse.CsvDelay != 4 {
t.Fatalf("expected AcceptChannel to have CSV delay %v, got %v",
4, acceptChannelResponse.CsvDelay)
}
// And the default MinHTLC value of 5.
if acceptChannelResponse.HtlcMinimum != 5 {
t.Fatalf("expected AcceptChannel to have minHtlc %v, got %v",
5, acceptChannelResponse.HtlcMinimum)
}
reserve := lnwire.NewMSatFromSatoshis(fundingAmt / 100)
maxValueAcceptChannel := lnwire.NewMSatFromSatoshis(fundingAmt) - reserve
if acceptChannelResponse.MaxValueInFlight != maxValueAcceptChannel {
t.Fatalf("expected AcceptChannel to have MaxValueInFlight %v, "+
"got %v", maxValueAcceptChannel,
acceptChannelResponse.MaxValueInFlight)
}
// Forward the response to Alice.
alice.fundingMgr.ProcessFundingMsg(acceptChannelResponse, bob)
// Alice responds with a FundingCreated message.
fundingCreated := assertFundingMsgSent(
t, alice.msgChan, "FundingCreated",
).(*lnwire.FundingCreated)
// Helper method for checking the CSV delay stored for a reservation.
assertDelay := func(resCtx *reservationWithCtx,
ourDelay, theirDelay uint16) error {
ourCsvDelay := resCtx.reservation.OurContribution().CsvDelay
if ourCsvDelay != ourDelay {
return fmt.Errorf("expected our CSV delay to be %v, "+
"was %v", ourDelay, ourCsvDelay)
}
theirCsvDelay := resCtx.reservation.TheirContribution().CsvDelay
if theirCsvDelay != theirDelay {
return fmt.Errorf("expected their CSV delay to be %v, "+
"was %v", theirDelay, theirCsvDelay)
}
return nil
}
// Helper method for checking the MinHtlc value stored for a
// reservation.
assertMinHtlc := func(resCtx *reservationWithCtx,
expOurMinHtlc, expTheirMinHtlc lnwire.MilliSatoshi) error {
ourMinHtlc := resCtx.reservation.OurContribution().MinHTLC
if ourMinHtlc != expOurMinHtlc {
return fmt.Errorf("expected our minHtlc to be %v, "+
"was %v", expOurMinHtlc, ourMinHtlc)
}
theirMinHtlc := resCtx.reservation.TheirContribution().MinHTLC
if theirMinHtlc != expTheirMinHtlc {
return fmt.Errorf("expected their minHtlc to be %v, "+
"was %v", expTheirMinHtlc, theirMinHtlc)
}
return nil
}
// Helper method for checking the MaxValueInFlight stored for a
// reservation.
assertMaxHtlc := func(resCtx *reservationWithCtx,
expOurMaxValue, expTheirMaxValue lnwire.MilliSatoshi) error {
ourMaxValue :=
resCtx.reservation.OurContribution().MaxPendingAmount
if ourMaxValue != expOurMaxValue {
return fmt.Errorf("expected our maxValue to be %v, "+
"was %v", expOurMaxValue, ourMaxValue)
}
theirMaxValue :=
resCtx.reservation.TheirContribution().MaxPendingAmount
if theirMaxValue != expTheirMaxValue {
return fmt.Errorf("expected their MaxPendingAmount to "+
"be %v, was %v", expTheirMaxValue,
theirMaxValue)
}
return nil
}
// Helper method for checking baseFee and feeRate stored for a
// reservation.
assertFees := func(forwardingPolicy *models.ForwardingPolicy,
baseFee, feeRate lnwire.MilliSatoshi) error {
if forwardingPolicy.BaseFee != baseFee {
return fmt.Errorf("expected baseFee to be %v, "+
"was %v", baseFee, forwardingPolicy.BaseFee)
}
if forwardingPolicy.FeeRate != feeRate {
return fmt.Errorf("expected feeRate to be %v, "+
"was %v", feeRate, forwardingPolicy.FeeRate)
}
return nil
}
// Check that the custom channel parameters were properly set in the
// channel reservation.
resCtx, err := alice.fundingMgr.getReservationCtx(bobPubKey, chanID)
require.NoError(t, err, "unable to find ctx")
// Alice's CSV delay should be 4 since Bob sent the default value, and
// Bob's should be 67 since Alice sent the custom value.
if err := assertDelay(resCtx, 4, csvDelay); err != nil {
t.Fatal(err)
}
// The minimum HTLC value Alice can offer should be 5, and the minimum
// Bob can offer should be 1234.
if err := assertMinHtlc(resCtx, 5, minHtlcIn); err != nil {
t.Fatal(err)
}
// The max value in flight Alice can have should be
// maxValueAcceptChannel, which is the default value and the maximum Bob
// can offer should be maxValueInFlight.
if err := assertMaxHtlc(resCtx,
maxValueAcceptChannel, maxValueInFlight); err != nil {
t.Fatal(err)
}
// The optional channel fees that will be applied in the channel
// announcement phase. Both base fee and fee rate were provided
// in the channel open request.
if err := assertFees(&resCtx.forwardingPolicy, 42, 1337); err != nil {
t.Fatal(err)
}
// Also make sure the parameters are properly set on Bob's end.
resCtx, err = bob.fundingMgr.getReservationCtx(alicePubKey, chanID)
require.NoError(t, err, "unable to find ctx")
if err := assertDelay(resCtx, csvDelay, 4); err != nil {
t.Fatal(err)
}
if err := assertMinHtlc(resCtx, minHtlcIn, 5); err != nil {
t.Fatal(err)
}
if err := assertMaxHtlc(resCtx,
maxValueInFlight, maxValueAcceptChannel); err != nil {
t.Fatal(err)
}
if err := assertFees(&resCtx.forwardingPolicy, 100, 1000); err != nil {
t.Fatal(err)
}
// Give the message to Bob.
bob.fundingMgr.ProcessFundingMsg(fundingCreated, alice)
// Finally, Bob should send the FundingSigned message.
fundingSigned := assertFundingMsgSent(
t, bob.msgChan, "FundingSigned",
).(*lnwire.FundingSigned)
// Forward the signature to Alice.
alice.fundingMgr.ProcessFundingMsg(fundingSigned, bob)
// After Alice processes the singleFundingSignComplete message, she will
// broadcast the funding transaction to the network. We expect to get a
// channel update saying the channel is pending.
var pendingUpdate *lnrpc.OpenStatusUpdate
select {
case pendingUpdate = <-updateChan:
case <-time.After(time.Second * 5):
t.Fatalf("alice did not send OpenStatusUpdate_ChanPending")
}
_, ok = pendingUpdate.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
if !ok {
t.Fatal("OpenStatusUpdate was not OpenStatusUpdate_ChanPending")
}
// After the funding is signed and before the channel announcement
// we expect Alice and Bob to store their respective fees in the
// database.
forwardingPolicy, err := alice.fundingMgr.getInitialForwardingPolicy(
fundingSigned.ChanID,
)
require.NoError(t, err)
require.NoError(t, assertFees(forwardingPolicy, 42, 1337))
forwardingPolicy, err = bob.fundingMgr.getInitialForwardingPolicy(
fundingSigned.ChanID,
)
require.NoError(t, err)
require.NoError(t, assertFees(forwardingPolicy, 100, 1000))
// Wait for Alice to publish the funding tx to the network.
var fundingTx *wire.MsgTx
select {
case fundingTx = <-alice.publTxChan:
case <-time.After(time.Second * 5):
t.Fatalf("alice did not publish funding tx")
}
// Notify that transaction was mined.
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
// After the funding transaction is mined, Alice will send
// channelReady to Bob.
channelReadyAlice, ok := assertFundingMsgSent(
t, alice.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
// And similarly Bob will send channel_ready to Alice.
channelReadyBob, ok := assertFundingMsgSent(
t, bob.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
// Exchange the channelReady messages.
alice.fundingMgr.ProcessFundingMsg(channelReadyBob, bob)
bob.fundingMgr.ProcessFundingMsg(channelReadyAlice, alice)
// Make sure both fundingManagers send the expected channel
// announcements.
// Alice should advertise the default MinHTLC value of
// 5, while bob should advertise the value minHtlc, since Alice
// required him to use it.
minHtlcArr := []lnwire.MilliSatoshi{5, minHtlcIn}
// For maxHltc Alice should advertise the default MaxHtlc value of
// maxValueAcceptChannel, while bob should advertise the value
// maxValueInFlight since Alice required him to use it.
maxHtlcArr := []lnwire.MilliSatoshi{
maxValueAcceptChannel, maxValueInFlight,
}
// Alice should have custom fees set whereas Bob should see his
// configured default fees announced.
defaultDelta := bob.fundingMgr.cfg.DefaultRoutingPolicy.TimeLockDelta
defaultBaseFee := bob.fundingMgr.cfg.DefaultRoutingPolicy.BaseFee
defaultFeeRate := bob.fundingMgr.cfg.DefaultRoutingPolicy.FeeRate
baseFees := []lnwire.MilliSatoshi{
lnwire.MilliSatoshi(baseFee), defaultBaseFee,
}
feeRates := []lnwire.MilliSatoshi{
lnwire.MilliSatoshi(feeRate), defaultFeeRate,
}
// Check that they notify the breach arbiter and peer about the new
// channel.
assertHandleChannelReady(
t, alice, bob, func(node *testNode, msg *newChannelMsg) bool {
aliceDB := alice.fundingMgr.cfg.ChannelDB
chanID := lnwire.NewChanIDFromOutPoint(
&msg.channel.FundingOutpoint,
)
if node == alice {
p, err := aliceDB.GetInitialForwardingPolicy(
chanID,
)
require.NoError(t, err)
return reflect.DeepEqual(
p, &models.ForwardingPolicy{
MaxHTLC: maxHtlcArr[0],
MinHTLCOut: minHtlcArr[0],
BaseFee: baseFees[0],
FeeRate: feeRates[0],
TimeLockDelta: defaultDelta,
},
)
}
return true
},
)
assertChannelAnnouncements(
t, alice, bob, capacity, minHtlcArr, maxHtlcArr, baseFees,
feeRates,
)
// The funding transaction is now confirmed, wait for the
// OpenStatusUpdate_ChanOpen update
waitForOpenUpdate(t, updateChan)
// Send along the 6-confirmation channel so that announcement sigs can
// be exchanged.
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
assertAnnouncementSignatures(t, alice, bob)
// After the announcement we expect Alice and Bob to have cleared
// the fees for the channel from the database.
assertNoFwdingPolicy(t, alice, bob, channelReadyAlice.ChanID)
}
// TestFundingManagerInvalidChanReserve ensures proper validation is done on
// remoteChanReserve parameter sent to open channel.
func TestFundingManagerInvalidChanReserve(t *testing.T) {
t.Parallel()
var (
fundingAmt = btcutil.Amount(500000)
pushAmt = lnwire.NewMSatFromSatoshis(10)
genesisHash = *fundingNetParams.GenesisHash
)
tests := []struct {
name string
chanReserve btcutil.Amount
expectErr bool
errorContains string
}{
{
name: "Use default chan reserve",
chanReserve: 0,
},
{
name: "Above dust but below 1% of the capacity",
chanReserve: 400,
},
{
name: "Channel reserve below dust",
chanReserve: 300,
expectErr: true,
errorContains: "channel reserve of 300 sat is too " +
"small",
},
{
name: "Channel reserve more than 20% of the " +
"channel capacity",
chanReserve: fundingAmt,
expectErr: true,
errorContains: "channel reserve is too large",
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
defer tearDownFundingManagers(t, alice, bob)
// Create a funding request and start the workflow.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
errChan := make(chan error, 1)
initReq := &InitFundingMsg{
Peer: bob,
TargetPubkey: bob.privKey.PubKey(),
ChainHash: genesisHash,
LocalFundingAmt: fundingAmt,
PushAmt: pushAmt,
Updates: updateChan,
RemoteChanReserve: test.chanReserve,
Err: errChan,
}
alice.fundingMgr.InitFundingWorkflow(initReq)
var err error
select {
case <-alice.msgChan:
case err = <-initReq.Err:
case <-time.After(time.Second * 5):
t.Fatalf("no message or error received")
}
if !test.expectErr {
require.NoError(t, err)
return
}
require.ErrorContains(t, err, test.errorContains)
})
}
}
// TestFundingManagerMaxPendingChannels checks that trying to open another
// channel with the same peer when MaxPending channels are pending fails.
func TestFundingManagerMaxPendingChannels(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(
t, func(cfg *Config) {
cfg.MaxPendingChannels = maxPending
},
)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// Create InitFundingMsg structs for maxPending+1 channels.
var initReqs []*InitFundingMsg
for i := 0; i < maxPending+1; i++ {
updateChan := make(chan *lnrpc.OpenStatusUpdate)
errChan := make(chan error, 1)
initReq := &InitFundingMsg{
Peer: bob,
TargetPubkey: bob.privKey.PubKey(),
ChainHash: *fundingNetParams.GenesisHash,
LocalFundingAmt: 5000000,
PushAmt: lnwire.NewMSatFromSatoshis(0),
Private: false,
Updates: updateChan,
Err: errChan,
}
initReqs = append(initReqs, initReq)
}
// Kick of maxPending+1 funding workflows.
var accepts []*lnwire.AcceptChannel
var lastOpen *lnwire.OpenChannel
for i, initReq := range initReqs {
alice.fundingMgr.InitFundingWorkflow(initReq)
// Alice should have sent the OpenChannel message to Bob.
var aliceMsg lnwire.Message
select {
case aliceMsg = <-alice.msgChan:
case err := <-initReq.Err:
t.Fatalf("error init funding workflow: %v", err)
case <-time.After(time.Second * 5):
t.Fatalf("alice did not send OpenChannel message")
}
openChannelReq, ok := aliceMsg.(*lnwire.OpenChannel)
if !ok {
errorMsg, gotError := aliceMsg.(*lnwire.Error)
if gotError {
t.Fatalf("expected OpenChannel to be sent "+
"from bob, instead got error: %v",
errorMsg.Error())
}
t.Fatalf("expected OpenChannel to be sent from "+
"alice, instead got %T", aliceMsg)
}
// Let Bob handle the init message.
bob.fundingMgr.ProcessFundingMsg(openChannelReq, alice)
// Bob should answer with an AcceptChannel message for the
// first maxPending channels.
if i < maxPending {
acceptChannelResponse := assertFundingMsgSent(
t, bob.msgChan, "AcceptChannel",
).(*lnwire.AcceptChannel)
accepts = append(accepts, acceptChannelResponse)
continue
}
// For the last channel, Bob should answer with an error.
lastOpen = openChannelReq
_ = assertFundingMsgSent(
t, bob.msgChan, "Error",
).(*lnwire.Error)
}
// Forward the responses to Alice.
var signs []*lnwire.FundingSigned
for _, accept := range accepts {
alice.fundingMgr.ProcessFundingMsg(accept, bob)
// Alice responds with a FundingCreated message.
fundingCreated := assertFundingMsgSent(
t, alice.msgChan, "FundingCreated",
).(*lnwire.FundingCreated)
// Give the message to Bob.
bob.fundingMgr.ProcessFundingMsg(fundingCreated, alice)
// Finally, Bob should send the FundingSigned message.
fundingSigned := assertFundingMsgSent(
t, bob.msgChan, "FundingSigned",
).(*lnwire.FundingSigned)
signs = append(signs, fundingSigned)
}
// Sending another init request from Alice should still make Bob
// respond with an error.
bob.fundingMgr.ProcessFundingMsg(lastOpen, alice)
_ = assertFundingMsgSent(
t, bob.msgChan, "Error",
).(*lnwire.Error)
// Give the FundingSigned messages to Alice.
var txs []*wire.MsgTx
for i, sign := range signs {
alice.fundingMgr.ProcessFundingMsg(sign, bob)
// Alice should send a status update for each channel, and
// publish a funding tx to the network.
var pendingUpdate *lnrpc.OpenStatusUpdate
select {
case pendingUpdate = <-initReqs[i].Updates:
case <-time.After(time.Second * 5):
t.Fatalf("alice did not send " +
"OpenStatusUpdate_ChanPending")
}
_, ok := pendingUpdate.Update.(*lnrpc.OpenStatusUpdate_ChanPending)
if !ok {
t.Fatal("OpenStatusUpdate was not " +
"OpenStatusUpdate_ChanPending")
}
select {
case tx := <-alice.publTxChan:
txs = append(txs, tx)
case <-time.After(time.Second * 5):
t.Fatalf("alice did not publish funding tx")
}
}
// Sending another init request from Alice should still make Bob
// respond with an error, since the funding transactions are not
// confirmed yet,
bob.fundingMgr.ProcessFundingMsg(lastOpen, alice)
_ = assertFundingMsgSent(
t, bob.msgChan, "Error",
).(*lnwire.Error)
// Notify that the transactions were mined.
for i := 0; i < maxPending; i++ {
alice.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: txs[i],
}
bob.mockNotifier.oneConfChannel <- &chainntnfs.TxConfirmation{
Tx: txs[i],
}
// Expect both to be sending ChannelReady.
_ = assertFundingMsgSent(
t, alice.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
_ = assertFundingMsgSent(
t, bob.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
}
// Now opening another channel should work.
bob.fundingMgr.ProcessFundingMsg(lastOpen, alice)
// Bob should answer with an AcceptChannel message.
_ = assertFundingMsgSent(
t, bob.msgChan, "AcceptChannel",
).(*lnwire.AcceptChannel)
}
// TestFundingManagerRejectPush checks behaviour of 'rejectpush'
// option, namely that non-zero incoming push amounts are disabled.
func TestFundingManagerRejectPush(t *testing.T) {
t.Parallel()
// Enable 'rejectpush' option and initialize funding managers.
alice, bob := setupFundingManagers(
t, func(cfg *Config) {
cfg.RejectPush = true
},
)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// Create a funding request and start the workflow.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
errChan := make(chan error, 1)
initReq := &InitFundingMsg{
Peer: bob,
TargetPubkey: bob.privKey.PubKey(),
ChainHash: *fundingNetParams.GenesisHash,
LocalFundingAmt: 500000,
PushAmt: lnwire.NewMSatFromSatoshis(10),
Private: true,
Updates: updateChan,
Err: errChan,
}
alice.fundingMgr.InitFundingWorkflow(initReq)
// Alice should have sent the OpenChannel message to Bob.
var aliceMsg lnwire.Message
select {
case aliceMsg = <-alice.msgChan:
case err := <-initReq.Err:
t.Fatalf("error init funding workflow: %v", err)
case <-time.After(time.Second * 5):
t.Fatalf("alice did not send OpenChannel message")
}
openChannelReq, ok := aliceMsg.(*lnwire.OpenChannel)
if !ok {
errorMsg, gotError := aliceMsg.(*lnwire.Error)
if gotError {
t.Fatalf("expected OpenChannel to be sent "+
"from bob, instead got error: %v",
errorMsg.Error())
}
t.Fatalf("expected OpenChannel to be sent from "+
"alice, instead got %T", aliceMsg)
}
// Let Bob handle the init message.
bob.fundingMgr.ProcessFundingMsg(openChannelReq, alice)
// Assert Bob responded with an ErrNonZeroPushAmount error.
err := assertFundingMsgSent(t, bob.msgChan, "Error").(*lnwire.Error)
require.ErrorContains(
t, err, "non-zero push amounts are disabled",
"expected ErrNonZeroPushAmount error, got \"%v\"", err.Error(),
)
}
// TestFundingManagerMaxConfs ensures that we don't accept a funding proposal
// that proposes a MinAcceptDepth greater than the maximum number of
// confirmations we're willing to accept.
func TestFundingManagerMaxConfs(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// Create a funding request and start the workflow.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
errChan := make(chan error, 1)
initReq := &InitFundingMsg{
Peer: bob,
TargetPubkey: bob.privKey.PubKey(),
ChainHash: *fundingNetParams.GenesisHash,
LocalFundingAmt: 500000,
PushAmt: lnwire.NewMSatFromSatoshis(10),
Private: false,
Updates: updateChan,
Err: errChan,
}
alice.fundingMgr.InitFundingWorkflow(initReq)
// Alice should have sent the OpenChannel message to Bob.
var aliceMsg lnwire.Message
select {
case aliceMsg = <-alice.msgChan:
case err := <-initReq.Err:
t.Fatalf("error init funding workflow: %v", err)
case <-time.After(time.Second * 5):
t.Fatalf("alice did not send OpenChannel message")
}
openChannelReq, ok := aliceMsg.(*lnwire.OpenChannel)
if !ok {
errorMsg, gotError := aliceMsg.(*lnwire.Error)
if gotError {
t.Fatalf("expected OpenChannel to be sent "+
"from bob, instead got error: %v",
errorMsg.Error())
}
t.Fatalf("expected OpenChannel to be sent from "+
"alice, instead got %T", aliceMsg)
}
// Let Bob handle the init message.
bob.fundingMgr.ProcessFundingMsg(openChannelReq, alice)
// Bob should answer with an AcceptChannel message.
acceptChannelResponse := assertFundingMsgSent(
t, bob.msgChan, "AcceptChannel",
).(*lnwire.AcceptChannel)
// Modify the AcceptChannel message Bob is proposing to including a
// MinAcceptDepth Alice won't be willing to accept.
acceptChannelResponse.MinAcceptDepth = chainntnfs.MaxNumConfs + 1
alice.fundingMgr.ProcessFundingMsg(acceptChannelResponse, bob)
// Alice should respond back with an error indicating MinAcceptDepth is
// too large.
err := assertFundingMsgSent(t, alice.msgChan, "Error").(*lnwire.Error)
if !strings.Contains(err.Error(), "minimum depth") {
t.Fatalf("expected ErrNumConfsTooLarge, got \"%v\"",
err.Error())
}
}
// TestFundingManagerFundAll tests that we can initiate a funding request to
// use the funds remaining in the wallet. This should produce a funding tx with
// no change output.
func TestFundingManagerFundAll(t *testing.T) {
t.Parallel()
// We set up our mock wallet to control a list of UTXOs that sum to
// less than the max channel size.
allCoins := []*lnwallet.Utxo{
{
AddressType: lnwallet.WitnessPubKey,
Value: btcutil.Amount(
0.05 * btcutil.SatoshiPerBitcoin,
),
PkScript: mock.CoinPkScript,
OutPoint: wire.OutPoint{
Hash: chainhash.Hash{},
Index: 0,
},
},
{
AddressType: lnwallet.WitnessPubKey,
Value: btcutil.Amount(
0.06 * btcutil.SatoshiPerBitcoin,
),
PkScript: mock.CoinPkScript,
OutPoint: wire.OutPoint{
Hash: chainhash.Hash{},
Index: 1,
},
},
}
tests := []struct {
spendAmt btcutil.Amount
change bool
}{
{
// We will spend all the funds in the wallet, and
// expects no change output.
spendAmt: btcutil.Amount(
0.11 * btcutil.SatoshiPerBitcoin,
),
change: false,
},
{
// We spend a little less than the funds in the wallet,
// so a change output should be created.
spendAmt: btcutil.Amount(
0.10 * btcutil.SatoshiPerBitcoin,
),
change: true,
},
}
for _, test := range tests {
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
alice.fundingMgr.cfg.Wallet.WalletController.(*mock.WalletController).Utxos = allCoins
// We will consume the channel updates as we go, so no
// buffering is needed.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
// Initiate a fund channel, and inspect the funding tx.
pushAmt := btcutil.Amount(0)
fundingTx := fundChannel(
t, alice, bob, test.spendAmt, pushAmt, true, 0, 0, 1,
updateChan, true, nil,
)
// Check whether the expected change output is present.
if test.change && len(fundingTx.TxOut) != 2 {
t.Fatalf("expected 2 outputs, had %v",
len(fundingTx.TxOut))
}
if !test.change && len(fundingTx.TxOut) != 1 {
t.Fatalf("expected 1 output, had %v",
len(fundingTx.TxOut))
}
// Inputs should be all funds in the wallet.
if len(fundingTx.TxIn) != len(allCoins) {
t.Fatalf("Had %d inputs, expected %d",
len(fundingTx.TxIn), len(allCoins))
}
for i, txIn := range fundingTx.TxIn {
if txIn.PreviousOutPoint != allCoins[i].OutPoint {
t.Fatalf("expected outpoint to be %v, was %v",
allCoins[i].OutPoint,
txIn.PreviousOutPoint)
}
}
}
}
// TestFundingManagerFundMax tests that we can initiate a funding request to use
// the maximum allowed funds remaining in the wallet.
func TestFundingManagerFundMax(t *testing.T) {
t.Parallel()
// Helper function to create a test utxos
constructTestUtxos := func(values ...btcutil.Amount) []*lnwallet.Utxo {
var utxos []*lnwallet.Utxo
for _, value := range values {
utxos = append(utxos, &lnwallet.Utxo{
AddressType: lnwallet.WitnessPubKey,
Value: value,
PkScript: mock.CoinPkScript,
OutPoint: wire.OutPoint{
Hash: chainhash.Hash{},
Index: 0,
},
})
}
return utxos
}
tests := []struct {
name string
coins []*lnwallet.Utxo
fundUpToMaxAmt btcutil.Amount
minFundAmt btcutil.Amount
pushAmt btcutil.Amount
change bool
}{
{
// We will spend all the funds in the wallet, and expect
// no change output due to the dust limit.
coins: constructTestUtxos(
MaxBtcFundingAmount + 1,
),
fundUpToMaxAmt: MaxBtcFundingAmount,
minFundAmt: MinChanFundingSize,
pushAmt: 0,
change: false,
},
{
// We spend less than the funds in the wallet, so a
// change output should be created.
coins: constructTestUtxos(
2 * MaxBtcFundingAmount,
),
fundUpToMaxAmt: MaxBtcFundingAmount,
minFundAmt: MinChanFundingSize,
pushAmt: 0,
change: true,
},
{
// We spend less than the funds in the wallet when
// setting a smaller channel size, so a change output
// should be created.
coins: constructTestUtxos(
MaxBtcFundingAmount,
),
fundUpToMaxAmt: MaxBtcFundingAmount / 2,
minFundAmt: MinChanFundingSize,
pushAmt: 0,
change: true,
},
{
// We are using the entirety of two inputs for the
// funding of a channel, hence expect no change output.
coins: constructTestUtxos(
MaxBtcFundingAmount/2, MaxBtcFundingAmount/2,
),
fundUpToMaxAmt: MaxBtcFundingAmount,
minFundAmt: MinChanFundingSize,
pushAmt: 0,
change: false,
},
{
// We are using a fraction of two inputs for the funding
// of our channel, hence expect a change output.
coins: constructTestUtxos(
MaxBtcFundingAmount/2, MaxBtcFundingAmount/2,
),
fundUpToMaxAmt: MaxBtcFundingAmount / 2,
minFundAmt: MinChanFundingSize,
pushAmt: 0,
change: true,
},
{
// We are funding a channel with half of the balance in
// our wallet hence expect a change output. Furthermore
// we push half of the funding amount to the remote end
// which we expect to succeed.
coins: constructTestUtxos(MaxBtcFundingAmount),
fundUpToMaxAmt: MaxBtcFundingAmount / 2,
minFundAmt: MinChanFundingSize / 4,
pushAmt: MaxBtcFundingAmount / 4,
change: true,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
// We set up our mock wallet to control a list of UTXOs
// that sum to more than the max channel size.
addFunds := func(fundingCfg *Config) {
wc := fundingCfg.Wallet.WalletController
mockWc, ok := wc.(*mock.WalletController)
if ok {
mockWc.Utxos = test.coins
}
}
alice, bob := setupFundingManagers(t, addFunds)
defer tearDownFundingManagers(t, alice, bob)
// We will consume the channel updates as we go, so no
// buffering is needed.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
// Initiate a fund channel, and inspect the funding tx.
pushAmt := test.pushAmt
fundingTx := fundChannel(
t, alice, bob, 0, pushAmt, false,
test.fundUpToMaxAmt, test.minFundAmt, 1,
updateChan, true, nil,
)
// Check whether the expected change output is present.
if test.change {
require.EqualValues(t, 2, len(fundingTx.TxOut))
}
if !test.change {
require.EqualValues(t, 1, len(fundingTx.TxOut))
}
// Inputs should be all funds in the wallet.
require.Equal(t, len(test.coins), len(fundingTx.TxIn))
for i, txIn := range fundingTx.TxIn {
require.Equal(
t, test.coins[i].OutPoint,
txIn.PreviousOutPoint,
)
}
})
}
}
// TestGetUpfrontShutdown tests different combinations of inputs for getting a
// shutdown script. It varies whether the peer has the feature set, whether
// the user has provided a script and our local configuration to test that
// GetUpfrontShutdownScript returns the expected outcome.
func TestGetUpfrontShutdownScript(t *testing.T) {
upfrontScript := []byte("upfront script")
generatedScript := []byte("generated script")
getScript := func(_ bool) (lnwire.DeliveryAddress, error) {
return generatedScript, nil
}
tests := []struct {
name string
getScript func(bool) (lnwire.DeliveryAddress, error)
upfrontScript lnwire.DeliveryAddress
peerEnabled bool
localEnabled bool
expectedScript lnwire.DeliveryAddress
expectedErr error
}{
{
name: "peer disabled, no shutdown",
getScript: getScript,
},
{
name: "peer disabled, upfront provided",
upfrontScript: upfrontScript,
expectedErr: errUpfrontShutdownScriptNotSupported,
},
{
name: "peer enabled, upfront provided",
upfrontScript: upfrontScript,
peerEnabled: true,
expectedScript: upfrontScript,
},
{
name: "peer enabled, local disabled",
peerEnabled: true,
},
{
name: "local enabled, no upfront script",
getScript: getScript,
peerEnabled: true,
localEnabled: true,
expectedScript: generatedScript,
},
{
name: "local enabled, upfront script",
peerEnabled: true,
upfrontScript: upfrontScript,
localEnabled: true,
expectedScript: upfrontScript,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
var mockPeer testNode
// If the remote peer in the test should support
// upfront shutdown, add the feature bit.
if test.peerEnabled {
mockPeer.remoteFeatures = []lnwire.FeatureBit{
lnwire.UpfrontShutdownScriptOptional,
}
}
addr, err := getUpfrontShutdownScript(
test.localEnabled, &mockPeer, test.upfrontScript,
test.getScript,
)
if err != test.expectedErr {
t.Fatalf("got: %v, expected error: %v", err,
test.expectedErr)
}
if !bytes.Equal(addr, test.expectedScript) {
t.Fatalf("expected address: %x, got: %x",
test.expectedScript, addr)
}
})
}
}
func expectOpenChannelMsg(t *testing.T,
msgChan chan lnwire.Message) *lnwire.OpenChannel {
var msg lnwire.Message
select {
case msg = <-msgChan:
case <-time.After(time.Second * 5):
t.Fatalf("node did not send OpenChannel message")
}
openChannelReq, ok := msg.(*lnwire.OpenChannel)
if !ok {
errorMsg, gotError := msg.(*lnwire.Error)
if gotError {
t.Fatalf("expected OpenChannel to be sent "+
"from bob, instead got error: %v",
errorMsg.Error())
}
t.Fatalf("expected OpenChannel to be sent, instead got %T",
msg)
}
return openChannelReq
}
func TestMaxChannelSizeConfig(t *testing.T) {
t.Parallel()
// Create a set of funding managers that will reject wumbo
// channels but set --maxchansize explicitly lower than soft-limit.
// Verify that wumbo rejecting funding managers will respect
// --maxchansize below 16777215 satoshi (MaxBtcFundingAmount) limit.
alice, bob := setupFundingManagers(t, func(cfg *Config) {
cfg.NoWumboChans = true
cfg.MaxChanSize = MaxBtcFundingAmount - 1
})
// Attempt to create a channel above the limit
// imposed by --maxchansize, which should be rejected.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
errChan := make(chan error, 1)
initReq := &InitFundingMsg{
Peer: bob,
TargetPubkey: bob.privKey.PubKey(),
ChainHash: *fundingNetParams.GenesisHash,
LocalFundingAmt: MaxBtcFundingAmount,
PushAmt: lnwire.NewMSatFromSatoshis(0),
Private: false,
Updates: updateChan,
Err: errChan,
}
// After processing the funding open message, bob should respond with
// an error rejecting the channel that exceeds size limit.
alice.fundingMgr.InitFundingWorkflow(initReq)
openChanMsg := expectOpenChannelMsg(t, alice.msgChan)
bob.fundingMgr.ProcessFundingMsg(openChanMsg, alice)
assertErrorSent(t, bob.msgChan)
// Create a set of funding managers that will reject wumbo
// channels but set --maxchansize explicitly higher than soft-limit
// A --maxchansize greater than this limit should have no effect.
tearDownFundingManagers(t, alice, bob)
alice, bob = setupFundingManagers(t, func(cfg *Config) {
cfg.NoWumboChans = true
cfg.MaxChanSize = MaxBtcFundingAmount + 1
})
// Reset the Peer to the newly created one.
initReq.Peer = bob
// We expect Bob to respond with an Accept channel message.
alice.fundingMgr.InitFundingWorkflow(initReq)
openChanMsg = expectOpenChannelMsg(t, alice.msgChan)
bob.fundingMgr.ProcessFundingMsg(openChanMsg, alice)
assertFundingMsgSent(t, bob.msgChan, "AcceptChannel")
// Verify that wumbo accepting funding managers will respect
// --maxchansize. Create the funding managers, this time allowing wumbo
// channels but setting --maxchansize explicitly.
tearDownFundingManagers(t, alice, bob)
alice, bob = setupFundingManagers(t, func(cfg *Config) {
cfg.NoWumboChans = false
cfg.MaxChanSize = btcutil.Amount(100000000)
})
// Reset the Peer to the newly created one.
initReq.Peer = bob
// Attempt to create a channel above the limit
// imposed by --maxchansize, which should be rejected.
initReq.LocalFundingAmt = btcutil.SatoshiPerBitcoin + 1
// After processing the funding open message, bob should respond with
// an error rejecting the channel that exceeds size limit.
alice.fundingMgr.InitFundingWorkflow(initReq)
openChanMsg = expectOpenChannelMsg(t, alice.msgChan)
bob.fundingMgr.ProcessFundingMsg(openChanMsg, alice)
assertErrorSent(t, bob.msgChan)
}
// TestWumboChannelConfig tests that the funding manager will respect the wumbo
// channel config param when creating or accepting new channels.
func TestWumboChannelConfig(t *testing.T) {
t.Parallel()
// First we'll create a set of funding managers that will reject wumbo
// channels.
alice, bob := setupFundingManagers(t, func(cfg *Config) {
cfg.NoWumboChans = true
})
// If we attempt to initiate a new funding open request to Alice,
// that's below the wumbo channel mark, we should be able to start the
// funding process w/o issue.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
errChan := make(chan error, 1)
initReq := &InitFundingMsg{
Peer: bob,
TargetPubkey: bob.privKey.PubKey(),
ChainHash: *fundingNetParams.GenesisHash,
LocalFundingAmt: MaxBtcFundingAmount,
PushAmt: lnwire.NewMSatFromSatoshis(0),
Private: false,
Updates: updateChan,
Err: errChan,
}
// We expect Bob to respond with an Accept channel message.
alice.fundingMgr.InitFundingWorkflow(initReq)
openChanMsg := expectOpenChannelMsg(t, alice.msgChan)
bob.fundingMgr.ProcessFundingMsg(openChanMsg, alice)
assertFundingMsgSent(t, bob.msgChan, "AcceptChannel")
// We'll now attempt to create a channel above the wumbo mark, which
// should be rejected.
initReq.LocalFundingAmt = btcutil.SatoshiPerBitcoin
// After processing the funding open message, bob should respond with an
// error rejecting the channel.
alice.fundingMgr.InitFundingWorkflow(initReq)
openChanMsg = expectOpenChannelMsg(t, alice.msgChan)
bob.fundingMgr.ProcessFundingMsg(openChanMsg, alice)
assertErrorSent(t, bob.msgChan)
// Next, we'll re-create the funding managers, but this time allowing
// wumbo channels explicitly.
tearDownFundingManagers(t, alice, bob)
alice, bob = setupFundingManagers(t, func(cfg *Config) {
cfg.NoWumboChans = false
cfg.MaxChanSize = MaxBtcFundingAmountWumbo
})
// Reset the Peer to the newly created one.
initReq.Peer = bob
// We should now be able to initiate a wumbo channel funding w/o any
// issues.
alice.fundingMgr.InitFundingWorkflow(initReq)
openChanMsg = expectOpenChannelMsg(t, alice.msgChan)
bob.fundingMgr.ProcessFundingMsg(openChanMsg, alice)
assertFundingMsgSent(t, bob.msgChan, "AcceptChannel")
}
// TestFundingManagerUpfrontShutdown asserts that we'll properly fail out if
// an invalid upfront shutdown script is sent in the open_channel message.
// Since both the open_channel and accept_message logic validate the script
// using the same validation function, it suffices to just check the
// open_channel case.
func TestFundingManagerUpfrontShutdown(t *testing.T) {
t.Parallel()
tests := []struct {
name string
pkscript []byte
expectErr bool
}{
{
name: "p2pk script",
pkscript: []byte("\x21\x02\xd3\x00\x50\x2f\x61\x15" +
"\x0d\x58\x0a\x42\xa0\x99\x63\xe3\x47\xa2" +
"\xad\x3c\xe5\x1f\x11\x96\x0d\x35\x8d\xf8" +
"\xf3\x94\xf9\x67\x2a\x67\xac"),
expectErr: true,
},
{
name: "op return script",
pkscript: []byte("\x6a\x24\xaa\x21\xa9\xed\x18\xa9" +
"\x93\x58\x94\xbd\x48\x9b\xeb\x87\x66\x13" +
"\x60\xbc\x80\x92\xab\xf6\xdd\xe9\x1e\x82" +
"\x0c\x7d\x91\x89\x9d\x0a\x02\x34\x14\x3f"),
expectErr: true,
},
{
name: "standard (non-p2sh) 2-of-3 multisig",
pkscript: []byte("\x51\x41\x04\xcc\x71\xeb\x30\xd6" +
"\x53\xc0\xc3\x16\x39\x90\xc4\x7b\x97\x6f" +
"\x3f\xb3\xf3\x7c\xcc\xdc\xbe\xdb\x16\x9a" +
"\x1d\xfe\xf5\x8b\xbf\xbf\xaf\xf7\xd8\xa4" +
"\x73\xe7\xe2\xe6\xd3\x17\xb8\x7b\xaf\xe8" +
"\xbd\xe9\x7e\x3c\xf8\xf0\x65\xde\xc0\x22" +
"\xb5\x1d\x11\xfc\xdd\x0d\x34\x8a\xc4\x41" +
"\x04\x61\xcb\xdc\xc5\x40\x9f\xb4\xb4\xd4" +
"\x2b\x51\xd3\x33\x81\x35\x4d\x80\xe5\x50" +
"\x07\x8c\xb5\x32\xa3\x4b\xfa\x2f\xcf\xde" +
"\xb7\xd7\x65\x19\xae\xcc\x62\x77\x0f\x5b" +
"\x0e\x4e\xf8\x55\x19\x46\xd8\xa5\x40\x91" +
"\x1a\xbe\x3e\x78\x54\xa2\x6f\x39\xf5\x8b" +
"\x25\xc1\x53\x42\xaf\x52\xae"),
expectErr: true,
},
{
name: "nonstandard script",
pkscript: []byte("\x99"),
expectErr: true,
},
{
name: "p2sh script",
pkscript: []byte("\xa9\x14\xfe\x44\x10\x65\xb6\x53" +
"\x22\x31\xde\x2f\xac\x56\x31\x52\x20\x5e" +
"\xc4\xf5\x9c\x74\x87"),
expectErr: true,
},
{
name: "p2pkh script",
pkscript: []byte("\x76\xa9\x14\x64\x1a\xd5\x05\x1e" +
"\xdd\x97\x02\x9a\x00\x3f\xe9\xef\xb2\x93" +
"\x59\xfc\xee\x40\x9d\x88\xac"),
expectErr: true,
},
{
name: "p2wpkh script",
pkscript: []byte("\x00\x14\x4b\xfe\x98\x3f\x16\xaa" +
"\xde\x1b\x1c\xb1\x54\x5a\xa5\xa4\x88\xd5" +
"\xe3\x68\xb5\xdc"),
expectErr: false,
},
{
name: "p2wsh script",
pkscript: []byte("\x00\x20\x1d\xd6\x3c\x20\x13\x89" +
"\x3a\x8b\x41\x5e\xb2\xe7\x41\x8f\x07\x5d" +
"\x4f\x3b\xf1\x81\x34\x99\xef\x31\xfb\xd7" +
"\x8c\xa8\xc4\x5d\x8f\xf0"),
expectErr: false,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
testUpfrontFailure(t, test.pkscript, test.expectErr)
})
}
}
func testUpfrontFailure(t *testing.T, pkscript []byte, expectErr bool) {
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
errChan := make(chan error, 1)
updateChan := make(chan *lnrpc.OpenStatusUpdate)
fundingAmt := btcutil.Amount(500000)
pushAmt := lnwire.NewMSatFromSatoshis(btcutil.Amount(0))
initReq := &InitFundingMsg{
Peer: alice,
TargetPubkey: alice.privKey.PubKey(),
ChainHash: *fundingNetParams.GenesisHash,
SubtractFees: false,
LocalFundingAmt: fundingAmt,
PushAmt: pushAmt,
FundingFeePerKw: 1000,
Private: false,
Updates: updateChan,
Err: errChan,
}
bob.fundingMgr.InitFundingWorkflow(initReq)
// Bob should send an open_channel message to Alice.
var bobMsg lnwire.Message
select {
case bobMsg = <-bob.msgChan:
case err := <-initReq.Err:
t.Fatalf("received unexpected error: %v", err)
case <-time.After(time.Second * 5):
t.Fatalf("timed out waiting for bob's message")
}
bobOpenChan, ok := bobMsg.(*lnwire.OpenChannel)
require.True(t, ok, "did not receive OpenChannel")
// Set the UpfrontShutdownScript in OpenChannel.
bobOpenChan.UpfrontShutdownScript = lnwire.DeliveryAddress(pkscript)
// Send the OpenChannel message to Alice now. If we expected an error,
// check that we received it.
alice.fundingMgr.ProcessFundingMsg(bobOpenChan, bob)
var aliceMsg lnwire.Message
select {
case aliceMsg = <-alice.msgChan:
case <-time.After(time.Second * 5):
t.Fatalf("timed out waiting for alice's message")
}
if expectErr {
// Assert that Error was received.
_, ok = aliceMsg.(*lnwire.Error)
require.True(t, ok, "did not receive Error")
} else {
// Assert that AcceptChannel was received.
_, ok = aliceMsg.(*lnwire.AcceptChannel)
require.True(t, ok, "did not receive AcceptChannel")
}
}
// TestFundingManagerZeroConf tests that the fundingmanager properly handles
// the whole flow for zero-conf channels.
func TestFundingManagerZeroConf(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// Alice and Bob will have the same set of feature bits in our test.
featureBits := []lnwire.FeatureBit{
lnwire.ZeroConfOptional,
lnwire.ScidAliasOptional,
lnwire.ExplicitChannelTypeOptional,
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
}
alice.localFeatures = featureBits
alice.remoteFeatures = featureBits
bob.localFeatures = featureBits
bob.remoteFeatures = featureBits
fundingAmt := btcutil.Amount(500000)
pushAmt := btcutil.Amount(0)
updateChan := make(chan *lnrpc.OpenStatusUpdate)
// Construct the zero-conf ChannelType for use in open_channel.
channelTypeBits := []lnwire.FeatureBit{
lnwire.ZeroConfRequired,
lnwire.StaticRemoteKeyRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
}
channelType := lnwire.ChannelType(
*lnwire.NewRawFeatureVector(channelTypeBits...),
)
// Create a default-accept channelacceptor so that the test passes and
// we don't have to use any goroutines.
mockAcceptor := &mockZeroConfAcceptor{}
bob.fundingMgr.cfg.OpenChannelPredicate = mockAcceptor
// Call fundChannel with the zero-conf ChannelType.
fundingTx := fundChannel(
t, alice, bob, fundingAmt, pushAmt, false, 0, 0, 1, updateChan,
true, &channelType,
)
fundingOp := &wire.OutPoint{
Hash: fundingTx.TxHash(),
Index: 0,
}
// Assert that Bob's channel_ready message has an AliasScid.
bobChannelReady, ok := assertFundingMsgSent(
t, bob.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
require.NotNil(t, bobChannelReady.AliasScid)
require.Equal(t, *bobChannelReady.AliasScid, alias)
// Do the same for Alice as well.
aliceChannelReady, ok := assertFundingMsgSent(
t, alice.msgChan, "ChannelReady",
).(*lnwire.ChannelReady)
require.True(t, ok)
require.NotNil(t, aliceChannelReady.AliasScid)
require.Equal(t, *aliceChannelReady.AliasScid, alias)
// Exchange the channel_ready messages.
alice.fundingMgr.ProcessFundingMsg(bobChannelReady, bob)
bob.fundingMgr.ProcessFundingMsg(aliceChannelReady, alice)
// We'll assert that they both create new links.
assertHandleChannelReady(t, alice, bob)
// We'll now assert that both sides send ChannelAnnouncement and
// ChannelUpdate messages.
assertChannelAnnouncements(
t, alice, bob, fundingAmt, nil, nil, nil, nil,
)
// We'll now wait for the OpenStatusUpdate_ChanOpen update.
waitForOpenUpdate(t, updateChan)
// Assert that both Alice & Bob are in the addedToRouterGraph state.
assertAddedToRouterGraph(t, alice, bob, fundingOp)
// We'll now restart Alice's funding manager and assert that the tx
// is rebroadcast.
recreateAliceFundingManager(t, alice)
select {
case <-alice.publTxChan:
case <-time.After(time.Second * 5):
t.Fatalf("timed out waiting for alice to rebroadcast tx")
}
// We'll now confirm the funding transaction.
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
assertChannelAnnouncements(
t, alice, bob, fundingAmt, nil, nil, nil, nil,
)
// Both Alice and Bob should send on reportScidChan.
select {
case <-alice.reportScidChan:
case <-time.After(time.Second * 5):
t.Fatalf("did not call ReportShortChanID in time")
}
select {
case <-bob.reportScidChan:
case <-time.After(time.Second * 5):
t.Fatalf("did not call ReportShortChanID in time")
}
// Send along the 6-confirmation channel so that announcement sigs can
// be exchanged.
alice.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
bob.mockNotifier.sixConfChannel <- &chainntnfs.TxConfirmation{
Tx: fundingTx,
}
assertAnnouncementSignatures(t, alice, bob)
// Assert that the channel state is deleted from the fundingmanager's
// datastore.
assertNoChannelState(t, alice, bob, fundingOp)
// The forwarding policy for the channel announcement should
// have been deleted from the database, as the channel is announced.
assertNoFwdingPolicy(t, alice, bob, aliceChannelReady.ChanID)
}
// TestCommitmentTypeFundmaxSanityCheck was introduced as a way of reminding
// developers of new channel commitment types to also consider the channel
// opening behavior with a specified fundmax flag. To give a hypothetical
// example, if ANCHOR types had been introduced after the fundmax flag had been
// activated, the developer would have had to code for the anchor reserve in the
// funding manager in the context of public and private channels. Otherwise
// inconsistent bahvior would have resulted when specifying fundmax for
// different types of channel openings.
// To ensure consistency this test compares a map of locally defined channel
// commitment types to the list of channel types that are defined in the proto
// files. It fails if the proto files contain additional commitment types. Once
// the developer considered the new channel type behavior it can be added in
// this test to the map `allCommitmentTypes`.
func TestCommitmentTypeFundmaxSanityCheck(t *testing.T) {
t.Parallel()
allCommitmentTypes := map[string]int{
"UNKNOWN_COMMITMENT_TYPE": 0,
"LEGACY": 1,
"STATIC_REMOTE_KEY": 2,
"ANCHORS": 3,
"SCRIPT_ENFORCED_LEASE": 4,
}
for commitmentType := range lnrpc.CommitmentType_value {
if _, ok := allCommitmentTypes[commitmentType]; !ok {
t.Fatalf("Commitment type %s hasn't been considered "+
"in the context of the --fundmax flag for "+
"channel openings.", commitmentType)
}
}
}
// TestFundingManagerNoEchoChanType tests that the funding flow is aborted if
// the peer fails to echo back the channel type in AcceptChannel.
func TestFundingManagerNoEchoChanType(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
t.Cleanup(func() {
tearDownFundingManagers(t, alice, bob)
})
// Alice and Bob will have the same set of feature bits in our test.
featureBits := []lnwire.FeatureBit{
lnwire.ExplicitChannelTypeOptional,
lnwire.StaticRemoteKeyOptional,
lnwire.AnchorsZeroFeeHtlcTxOptional,
}
alice.localFeatures = featureBits
alice.remoteFeatures = featureBits
bob.localFeatures = featureBits
bob.remoteFeatures = featureBits
expectedChanType := (*lnwire.ChannelType)(lnwire.NewRawFeatureVector(
lnwire.StaticRemoteKeyRequired,
lnwire.AnchorsZeroFeeHtlcTxRequired,
))
// Create a funding request and start the workflow.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
errChan := make(chan error, 1)
initReq := &InitFundingMsg{
Peer: bob,
TargetPubkey: bob.privKey.PubKey(),
ChainHash: *fundingNetParams.GenesisHash,
LocalFundingAmt: 500000,
Updates: updateChan,
Err: errChan,
}
alice.fundingMgr.InitFundingWorkflow(initReq)
// Alice should have sent the OpenChannel message to Bob.
openChanMsg := expectOpenChannelMsg(t, alice.msgChan)
require.Equal(t, expectedChanType, openChanMsg.ChannelType)
// Let Bob handle the OpenChannel message.
bob.fundingMgr.ProcessFundingMsg(openChanMsg, alice)
acceptChanMsg, _ := assertFundingMsgSent(t, bob.msgChan,
"AcceptChannel").(*lnwire.AcceptChannel)
require.Equal(t, expectedChanType, acceptChanMsg.ChannelType)
// Drop the channel type and ensure Alice responds with an error.
acceptChanMsg.ChannelType = nil
alice.fundingMgr.ProcessFundingMsg(acceptChanMsg, bob)
assertFundingMsgSent(t, alice.msgChan, "Error")
}