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