lnd version, "hacked" to enable seedless restore from xprv + scb
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

3676 lines
111 KiB

// +build !rpctest
package funding
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"math/big"
"net"
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/chanacceptor"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channelnotifier"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/htlcswitch"
"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/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 = 10000
)
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(btcec.S256(),
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(btcec.S256(),
bobPrivKeyBytes[:])
bobTCPAddr, _ = net.ResolveTCPAddr("tcp", "10.0.0.2:9000")
bobAddr = &lnwire.NetAddress{
IdentityKey: bobPubKey,
Address: bobTCPAddr,
}
testSig = &btcec.Signature{
R: new(big.Int),
S: new(big.Int),
}
_, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10)
_, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10)
fundingNetParams = chainreg.BitcoinTestNetParams
)
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) (*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,
}
}
type newChannelMsg struct {
channel *channeldb.OpenChannel
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{}
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(nil, nil)
}
func (n *testNode) RemoteFeatures() *lnwire.FeatureVector {
return lnwire.NewFeatureVector(
lnwire.NewRawFeatureVector(n.remoteFeatures...), nil,
)
}
func (n *testNode) AddNewChannel(channel *channeldb.OpenChannel,
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 createTestWallet(cdb *channeldb.DB, 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.DefaultBtcChannelConstraints,
})
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),
}
sentMessages := make(chan lnwire.Message)
sentAnnouncements := make(chan lnwire.Message)
publTxChan := make(chan *wire.MsgTx, 1)
shutdownChan := 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")
cdb, err := channeldb.Open(dbDir)
if err != nil {
return nil, err
}
keyRing := &mock.SecretKeyRing{
RootKey: alicePrivKey,
}
lnw, err := createTestWallet(
cdb, netParams, chainNotifier, wc, signer, keyRing, bio,
estimator,
)
if err != nil {
t.Fatalf("unable to create test ln wallet: %v", err)
}
var chanIDSeed [32]byte
chainedAcceptor := chanacceptor.NewChainedAcceptor()
fundingCfg := Config{
IDKey: privKey.PubKey(),
Wallet: lnw,
Notifier: chainNotifier,
FeeEstimator: estimator,
SignMessage: func(pubKey *btcec.PublicKey,
msg []byte) (input.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(chanID lnwire.ChannelID) (
*channeldb.OpenChannel, error) {
dbChannels, err := cdb.FetchAllChannels()
if err != nil {
return nil, err
}
for _, channel := range dbChannels {
if chanID.IsChanPoint(&channel.FundingOutpoint) {
return channel, nil
}
}
return nil, fmt.Errorf("unable to find channel")
},
DefaultRoutingPolicy: htlcswitch.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 {
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(),
}
for _, op := range options {
op(&fundingCfg)
}
f, err := NewFundingManager(fundingCfg)
if err != nil {
t.Fatalf("failed creating fundingManager: %v", err)
}
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,
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 := chanacceptor.NewChainedAcceptor()
f, err := NewFundingManager(Config{
IDKey: oldCfg.IDKey,
Wallet: oldCfg.Wallet,
Notifier: oldCfg.Notifier,
FeeEstimator: oldCfg.FeeEstimator,
SignMessage: func(pubKey *btcec.PublicKey,
msg []byte) (input.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: htlcswitch.ForwardingPolicy{
MinHTLCOut: 5,
BaseFee: 100,
FeeRate: 1000,
TimeLockDelta: 10,
},
DefaultMinHtlcIn: 5,
RequiredRemoteMaxValue: oldCfg.RequiredRemoteMaxValue,
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,
})
if err != nil {
t.Fatalf("failed recreating aliceFundingManager: %v", err)
}
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) {
aliceTestDir, err := ioutil.TempDir("", "alicelnwallet")
if err != nil {
t.Fatalf("unable to create temp directory: %v", err)
}
alice, err := createTestFundingManager(
t, alicePrivKey, aliceAddr, aliceTestDir, options...,
)
if err != nil {
t.Fatalf("failed creating fundingManager: %v", err)
}
bobTestDir, err := ioutil.TempDir("", "boblnwallet")
if err != nil {
t.Fatalf("unable to create temp directory: %v", err)
}
bob, err := createTestFundingManager(
t, bobPrivKey, bobAddr, bobTestDir, options...,
)
if err != nil {
t.Fatalf("failed creating fundingManager: %v", err)
}
// 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)
}
os.RemoveAll(a.testDir)
os.RemoveAll(b.testDir)
}
// 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, numConfs,
updateChan, announceChan,
)
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, numConfs uint32,
updateChan chan *lnrpc.OpenStatusUpdate, announceChan bool) *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,
LocalFundingAmt: localFundingAmt,
PushAmt: lnwire.NewMSatFromSatoshis(pushAmt),
FundingFeePerKw: 1000,
Private: !announceChan,
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)
// 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 "FundingLocked":
sentMsg, ok = msg.(*lnwire.FundingLocked)
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 != ErrChannelNotFound {
t.Fatalf("unable to get channel state: %v", err)
}
// If we found the channel, check if it had the expected state.
if err != 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 assertFundingLockedSent(t *testing.T, alice, bob *testNode,
fundingOutPoint *wire.OutPoint) {
t.Helper()
assertDatabaseState(t, alice, fundingOutPoint, fundingLockedSent)
assertDatabaseState(t, bob, fundingOutPoint, fundingLockedSent)
}
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
// value.
func assertChannelAnnouncements(t *testing.T, alice, bob *testNode,
capacity btcutil.Amount, customMinHtlc []lnwire.MilliSatoshi,
customMaxHtlc []lnwire.MilliSatoshi) {
t.Helper()
// After the FundingLocked 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 did not 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
minHtlc := nodes[other].fundingMgr.cfg.
DefaultMinHtlcIn
// We might expect a custom MinHTLC value.
if len(customMinHtlc) > 0 {
if len(customMinHtlc) != 2 {
t.Fatalf("only 0 or 2 custom " +
"min htlc values " +
"currently supported")
}
minHtlc = customMinHtlc[j]
}
if m.HtlcMinimumMsat != minHtlc {
t.Fatalf("expected ChannelUpdate to "+
"advertise min HTLC %v, had %v",
minHtlc, m.HtlcMinimumMsat)
}
maxHtlc := alice.fundingMgr.cfg.RequiredRemoteMaxValue(
capacity,
)
// We might expect a custom MaxHltc value.
if len(customMaxHtlc) > 0 {
if len(customMaxHtlc) != 2 {
t.Fatalf("only 0 or 2 custom " +
"min htlc values " +
"currently supported")
}
maxHtlc = customMaxHtlc[j]
}
if m.MessageFlags != 1 {
t.Fatalf("expected message flags to "+
"be 1, was %v", m.MessageFlags)
}
if maxHtlc != m.HtlcMaximumMsat {
t.Fatalf("expected ChannelUpdate to "+
"advertise max HTLC %v, had %v",
maxHtlc,
m.HtlcMaximumMsat)
}
gotChannelUpdate = true
}
}
if !gotChannelAnnouncement {
t.Fatalf("did not get ChannelAnnouncement from node %d",
j)
}
if !gotChannelUpdate {
t.Fatalf("did not get ChannelUpdate from node %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 FundingLocked 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 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 == 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 assertHandleFundingLocked(t *testing.T, alice, bob *testNode) {
t.Helper()
// They should both send the new channel state to their peer.
select {
case c := <-alice.newChannels:
close(c.err)
case <-time.After(time.Second * 15):
t.Fatalf("alice did not send new channel to peer")
}
select {
case c := <-bob.newChannels:
close(c.err)
case <-time.After(time.Second * 15):
t.Fatalf("bob did not send new channel to peer")
}
}
func TestFundingManagerNormalWorkflow(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
defer 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
// fundingLocked to Bob.
fundingLockedAlice := assertFundingMsgSent(
t, alice.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
// And similarly Bob will send funding locked to Alice.
fundingLockedBob := assertFundingMsgSent(
t, bob.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
// Check that the state machine is updated accordingly
assertFundingLockedSent(t, alice, bob, fundingOutPoint)
// Make sure both fundingManagers send the expected channel
// announcements.
assertChannelAnnouncements(t, alice, bob, capacity, 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)
// Exchange the fundingLocked messages.
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob)
bob.fundingMgr.ProcessFundingMsg(fundingLockedAlice, alice)
// Check that they notify the breach arbiter and peer about the new
// channel.
assertHandleFundingLocked(t, alice, bob)
// 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)
}
// 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)
defer 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)
defer 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
// fundingLocked 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")
}
alice.fundingMgr.cfg.NotifyWhenOnline = func(peer [33]byte,
con chan<- lnpeer.Peer) {
// Intentionally empty.
}
// 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 fundingLocked 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 funding locked to Alice.
fundingLockedBob := assertFundingMsgSent(
t, bob.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
// Alice should still be markedOpen
assertDatabaseState(t, alice, fundingOutPoint, markedOpen)
// While Bob successfully sent fundingLocked.
assertDatabaseState(t, bob, fundingOutPoint, fundingLockedSent)
// We now recreate Alice's fundingManager with the correct sendMessage
// implementation, and expect it to retry sending the fundingLocked
// 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
}
fundingLockedAlice := assertFundingMsgSent(
t, alice.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
// The state should now be fundingLockedSent
assertDatabaseState(t, alice, fundingOutPoint, fundingLockedSent)
// 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 fundingLocked messages.
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob)
bob.fundingMgr.ProcessFundingMsg(fundingLockedAlice, alice)
// Check that they notify the breach arbiter and peer about the new
// channel.
assertHandleFundingLocked(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)
// 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)
}
// TestFundingManagerOfflinePeer checks that the fundingManager waits for the
// server to notify when the peer comes online, in case sending the
// fundingLocked message fails the first time.
func TestFundingManagerOfflinePeer(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
defer 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
// fundingLocked message to the other peer. If the funding node fails
// to send the fundingLocked 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 fundingLocked 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 funding locked to Alice
fundingLockedBob := assertFundingMsgSent(
t, bob.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
// Alice should still be markedOpen
assertDatabaseState(t, alice, fundingOutPoint, markedOpen)
// While Bob successfully sent fundingLocked.
assertDatabaseState(t, bob, fundingOutPoint, fundingLockedSent)
// 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)
}
// Restore the correct sendMessage implementation, and notify that Bob
// is back online.
bob.sendMessage = workingSendMessage
con <- bob
// This should make Alice send the fundingLocked.
fundingLockedAlice := assertFundingMsgSent(
t, alice.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
// The state should now be fundingLockedSent
assertDatabaseState(t, alice, fundingOutPoint, fundingLockedSent)
// Make sure both fundingManagers send the expected channel
// announcements.
assertChannelAnnouncements(t, alice, bob, capacity, 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)
// Exchange the fundingLocked messages.
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob)
bob.fundingMgr.ProcessFundingMsg(fundingLockedAlice, alice)
// Check that they notify the breach arbiter and peer about the new
// channel.
assertHandleFundingLocked(t, alice, bob)
// 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)
}
// 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)
defer 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)
defer 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)
defer 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)
defer 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()
if err != nil {
t.Fatalf("unable to fetch pending channels: %v", err)
}
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)
defer 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()
if err != nil {
t.Fatalf("unable to fetch pending channels: %v", err)
}
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)
}
// TestFundingManagerReceiveFundingLockedTwice checks that the fundingManager
// continues to operate as expected in case we receive a duplicate fundingLocked
// message.
func TestFundingManagerReceiveFundingLockedTwice(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
defer 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
// fundingLocked to Bob.
fundingLockedAlice := assertFundingMsgSent(
t, alice.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
// And similarly Bob will send funding locked to Alice.
fundingLockedBob := assertFundingMsgSent(
t, bob.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
// Check that the state machine is updated accordingly
assertFundingLockedSent(t, alice, bob, fundingOutPoint)
// Make sure both fundingManagers send the expected channel
// announcements.
assertChannelAnnouncements(t, alice, bob, capacity, 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)
// Send the fundingLocked message twice to Alice, and once to Bob.
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob)
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob)
bob.fundingMgr.ProcessFundingMsg(fundingLockedAlice, alice)
// Check that they notify the breach arbiter and peer about the new
// channel.
assertHandleFundingLocked(t, alice, bob)
// Alice should not send the channel state the second time, as the
// second funding locked 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 fundingLocked should also be ignored, since Alice should
// have updated her database at this point.
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob)
select {
case <-alice.newChannels:
t.Fatalf("alice sent new channel to peer a second time")
case <-time.After(time.Millisecond * 300):
// Expected
}
// 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)
}
// TestFundingManagerRestartAfterChanAnn checks that the fundingManager properly
// handles receiving a fundingLocked after the its own fundingLocked and channel
// announcement is sent and gets restarted.
func TestFundingManagerRestartAfterChanAnn(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
defer 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
// fundingLocked to Bob.
fundingLockedAlice := assertFundingMsgSent(
t, alice.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
// And similarly Bob will send funding locked to Alice.
fundingLockedBob := assertFundingMsgSent(
t, bob.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
// Check that the state machine is updated accordingly
assertFundingLockedSent(t, alice, bob, fundingOutPoint)
// Make sure both fundingManagers send the expected channel
// announcements.
assertChannelAnnouncements(t, alice, bob, capacity, 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 fundingLocked message. After restart, she will receive it, and
// we expect her to be able to handle it correctly.
recreateAliceFundingManager(t, alice)
// Exchange the fundingLocked messages.
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob)
bob.fundingMgr.ProcessFundingMsg(fundingLockedAlice, alice)
// Check that they notify the breach arbiter and peer about the new
// channel.
assertHandleFundingLocked(t, alice, bob)
// 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)
}
// TestFundingManagerRestartAfterReceivingFundingLocked checks that the
// fundingManager continues to operate as expected after it has received
// fundingLocked and then gets restarted.
func TestFundingManagerRestartAfterReceivingFundingLocked(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
defer 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
// fundingLocked to Bob.
fundingLockedAlice := assertFundingMsgSent(
t, alice.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
// And similarly Bob will send funding locked to Alice.
fundingLockedBob := assertFundingMsgSent(
t, bob.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
// Check that the state machine is updated accordingly
assertFundingLockedSent(t, alice, bob, fundingOutPoint)
// Let Alice immediately get the fundingLocked message.
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob)
// Also let Bob get the fundingLocked message.
bob.fundingMgr.ProcessFundingMsg(fundingLockedAlice, alice)
// Check that they notify the breach arbiter and peer about the new
// channel.
assertHandleFundingLocked(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)
// 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)
}
// 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)
defer 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
// fundingLocked to Bob.
fundingLockedAlice := assertFundingMsgSent(
t, alice.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
// And similarly Bob will send funding locked to Alice.
fundingLockedBob := assertFundingMsgSent(
t, bob.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
// Check that the state machine is updated accordingly
assertFundingLockedSent(t, alice, bob, fundingOutPoint)
// Make sure both fundingManagers send the expected channel
// announcements.
assertChannelAnnouncements(t, alice, bob, capacity, nil, nil)
// The funding transaction is now confirmed, wait for the
// OpenStatusUpdate_ChanOpen update
waitForOpenUpdate(t, updateChan)
// Exchange the fundingLocked messages.
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob)
bob.fundingMgr.ProcessFundingMsg(fundingLockedAlice, alice)
// Check that they notify the breach arbiter and peer about the new
// channel.
assertHandleFundingLocked(t, alice, bob)
// 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)
}
// 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)
defer 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
// fundingLocked to Bob.
fundingLockedAlice := assertFundingMsgSent(
t, alice.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
// And similarly Bob will send funding locked to Alice.
fundingLockedBob := assertFundingMsgSent(
t, bob.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
// Check that the state machine is updated accordingly
assertFundingLockedSent(t, alice, bob, fundingOutPoint)
// Make sure both fundingManagers send the expected channel
// announcements.
assertChannelAnnouncements(t, alice, bob, capacity, 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)
// Exchange the fundingLocked messages.
alice.fundingMgr.ProcessFundingMsg(fundingLockedBob, bob)
bob.fundingMgr.ProcessFundingMsg(fundingLockedAlice, alice)
// Check that they notify the breach arbiter and peer about the new
// channel.
assertHandleFundingLocked(t, alice, bob)
// 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)
}
// TestFundingManagerCustomChannelParameters checks that custom requirements we
// specify during the channel funding flow is preserved correcly on both sides.
func TestFundingManagerCustomChannelParameters(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
defer tearDownFundingManagers(t, alice, bob)
// This is the custom parameters we'll use.
const csvDelay = 67
const minHtlcIn = 1234
const maxValueInFlight = 50000
const fundingAmt = 5000000
// 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,
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)
}
// 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)
}
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
}
// Check that the custom channel parameters were properly set in the
// channel reservation.
resCtx, err := alice.fundingMgr.getReservationCtx(bobPubKey, chanID)
if err != nil {
t.Fatalf("unable to find ctx: %v", err)
}
// 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 maxium Bob can offer should be
// maxValueInFlight.
if err := assertMaxHtlc(resCtx,
maxValueAcceptChannel, maxValueInFlight); err != nil {
t.Fatal(err)
}
// Also make sure the parameters are properly set on Bob's end.
resCtx, err = bob.fundingMgr.getReservationCtx(alicePubKey, chanID)
if err != nil {
t.Fatalf("unable to find ctx: %v", err)
}
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)
}
// 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")
}
// Wait for Alice to published 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
// fundingLocked to Bob.
_ = assertFundingMsgSent(
t, alice.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
// And similarly Bob will send funding locked to Alice.
_ = assertFundingMsgSent(
t, bob.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
// 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}
assertChannelAnnouncements(t, alice, bob, capacity, minHtlcArr, maxHtlcArr)
// The funding transaction is now confirmed, wait for the
// OpenStatusUpdate_ChanOpen update
waitForOpenUpdate(t, updateChan)
}
// 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
},
)
defer 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 FundingLocked.
_ = assertFundingMsgSent(
t, alice.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
_ = assertFundingMsgSent(
t, bob.msgChan, "FundingLocked",
).(*lnwire.FundingLocked)
}
// 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
},
)
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: *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)
if !strings.Contains(err.Error(), "non-zero push amounts are disabled") {
t.Fatalf("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)
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: *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)
defer 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, 1,
updateChan, true,
)
// 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)
}
}
}
}
// 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() (lnwire.DeliveryAddress, error) {
return generatedScript, nil
}
tests := []struct {
name string
getScript func() (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: false,
},
{
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: false,
},
{
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)
defer 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")
}
}