mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 09:53:54 +01:00
cdad5d988d
[custom channels 3/5]: Extract PART3 from mega staging branch
795 lines
22 KiB
Go
795 lines
22 KiB
Go
package peer
|
|
|
|
import (
|
|
"bytes"
|
|
crand "crypto/rand"
|
|
"encoding/binary"
|
|
"io"
|
|
"math/rand"
|
|
"net"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
"github.com/lightningnetwork/lnd/channelnotifier"
|
|
"github.com/lightningnetwork/lnd/fn"
|
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
|
"github.com/lightningnetwork/lnd/input"
|
|
"github.com/lightningnetwork/lnd/keychain"
|
|
"github.com/lightningnetwork/lnd/lntest/channels"
|
|
"github.com/lightningnetwork/lnd/lntest/mock"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/lightningnetwork/lnd/netann"
|
|
"github.com/lightningnetwork/lnd/pool"
|
|
"github.com/lightningnetwork/lnd/queue"
|
|
"github.com/lightningnetwork/lnd/shachain"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
const (
|
|
broadcastHeight = 100
|
|
|
|
// timeout is a timeout value to use for tests which need to wait for
|
|
// a return value on a channel.
|
|
timeout = time.Second * 5
|
|
|
|
// testCltvRejectDelta is the minimum delta between expiry and current
|
|
// height below which htlcs are rejected.
|
|
testCltvRejectDelta = 13
|
|
)
|
|
|
|
var (
|
|
testKeyLoc = keychain.KeyLocator{Family: keychain.KeyFamilyNodeKey}
|
|
)
|
|
|
|
// noUpdate is a function which can be used as a parameter in
|
|
// createTestPeerWithChannel to call the setup code with no custom values on
|
|
// the channels set up.
|
|
var noUpdate = func(a, b *channeldb.OpenChannel) {}
|
|
|
|
type peerTestCtx struct {
|
|
peer *Brontide
|
|
channel *lnwallet.LightningChannel
|
|
notifier *mock.ChainNotifier
|
|
publishTx <-chan *wire.MsgTx
|
|
mockSwitch *mockMessageSwitch
|
|
db *channeldb.DB
|
|
privKey *btcec.PrivateKey
|
|
mockConn *mockMessageConn
|
|
customChan chan *customMsg
|
|
chanStatusMgr *netann.ChanStatusManager
|
|
}
|
|
|
|
// createTestPeerWithChannel creates a channel between two nodes, and returns a
|
|
// peer for one of the nodes, together with the channel seen from both nodes.
|
|
// It takes an updateChan function which can be used to modify the default
|
|
// values on the channel states for each peer.
|
|
func createTestPeerWithChannel(t *testing.T, updateChan func(a,
|
|
b *channeldb.OpenChannel)) (*peerTestCtx, error) {
|
|
|
|
params := createTestPeer(t)
|
|
|
|
var (
|
|
publishTx = params.publishTx
|
|
mockSwitch = params.mockSwitch
|
|
alicePeer = params.peer
|
|
notifier = params.notifier
|
|
aliceKeyPriv = params.privKey
|
|
dbAlice = params.db
|
|
chanStatusMgr = params.chanStatusMgr
|
|
)
|
|
|
|
err := chanStatusMgr.Start()
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
require.NoError(t, chanStatusMgr.Stop())
|
|
})
|
|
|
|
aliceKeyPub := alicePeer.IdentityKey()
|
|
estimator := alicePeer.cfg.FeeEstimator
|
|
|
|
channelCapacity := btcutil.Amount(10 * 1e8)
|
|
channelBal := channelCapacity / 2
|
|
aliceDustLimit := btcutil.Amount(200)
|
|
bobDustLimit := btcutil.Amount(1300)
|
|
csvTimeoutAlice := uint32(5)
|
|
csvTimeoutBob := uint32(4)
|
|
isAliceInitiator := true
|
|
|
|
prevOut := &wire.OutPoint{
|
|
Hash: channels.TestHdSeed,
|
|
Index: 0,
|
|
}
|
|
fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
|
|
|
|
bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(
|
|
channels.BobsPrivKey,
|
|
)
|
|
|
|
aliceCfg := channeldb.ChannelConfig{
|
|
ChannelStateBounds: channeldb.ChannelStateBounds{
|
|
MaxPendingAmount: lnwire.MilliSatoshi(rand.Int63()),
|
|
ChanReserve: btcutil.Amount(rand.Int63()),
|
|
MinHTLC: lnwire.MilliSatoshi(rand.Int63()),
|
|
MaxAcceptedHtlcs: uint16(rand.Int31()),
|
|
},
|
|
CommitmentParams: channeldb.CommitmentParams{
|
|
DustLimit: aliceDustLimit,
|
|
CsvDelay: uint16(csvTimeoutAlice),
|
|
},
|
|
MultiSigKey: keychain.KeyDescriptor{
|
|
PubKey: aliceKeyPub,
|
|
},
|
|
RevocationBasePoint: keychain.KeyDescriptor{
|
|
PubKey: aliceKeyPub,
|
|
},
|
|
PaymentBasePoint: keychain.KeyDescriptor{
|
|
PubKey: aliceKeyPub,
|
|
},
|
|
DelayBasePoint: keychain.KeyDescriptor{
|
|
PubKey: aliceKeyPub,
|
|
},
|
|
HtlcBasePoint: keychain.KeyDescriptor{
|
|
PubKey: aliceKeyPub,
|
|
},
|
|
}
|
|
bobCfg := channeldb.ChannelConfig{
|
|
ChannelStateBounds: channeldb.ChannelStateBounds{
|
|
MaxPendingAmount: lnwire.MilliSatoshi(rand.Int63()),
|
|
ChanReserve: btcutil.Amount(rand.Int63()),
|
|
MinHTLC: lnwire.MilliSatoshi(rand.Int63()),
|
|
MaxAcceptedHtlcs: uint16(rand.Int31()),
|
|
},
|
|
CommitmentParams: channeldb.CommitmentParams{
|
|
DustLimit: bobDustLimit,
|
|
CsvDelay: uint16(csvTimeoutBob),
|
|
},
|
|
MultiSigKey: keychain.KeyDescriptor{
|
|
PubKey: bobKeyPub,
|
|
},
|
|
RevocationBasePoint: keychain.KeyDescriptor{
|
|
PubKey: bobKeyPub,
|
|
},
|
|
PaymentBasePoint: keychain.KeyDescriptor{
|
|
PubKey: bobKeyPub,
|
|
},
|
|
DelayBasePoint: keychain.KeyDescriptor{
|
|
PubKey: bobKeyPub,
|
|
},
|
|
HtlcBasePoint: keychain.KeyDescriptor{
|
|
PubKey: bobKeyPub,
|
|
},
|
|
}
|
|
|
|
bobRoot, err := chainhash.NewHash(bobKeyPriv.Serialize())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bobPreimageProducer := shachain.NewRevocationProducer(*bobRoot)
|
|
bobFirstRevoke, err := bobPreimageProducer.AtIndex(0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
bobCommitPoint := input.ComputeCommitmentPoint(bobFirstRevoke[:])
|
|
|
|
aliceRoot, err := chainhash.NewHash(aliceKeyPriv.Serialize())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
alicePreimageProducer := shachain.NewRevocationProducer(*aliceRoot)
|
|
aliceFirstRevoke, err := alicePreimageProducer.AtIndex(0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
aliceCommitPoint := input.ComputeCommitmentPoint(aliceFirstRevoke[:])
|
|
|
|
aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns(
|
|
channelBal, channelBal, &aliceCfg, &bobCfg, aliceCommitPoint,
|
|
bobCommitPoint, *fundingTxIn, channeldb.SingleFunderTweaklessBit,
|
|
isAliceInitiator, 0,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dbBob, err := channeldb.Open(t.TempDir())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
t.Cleanup(func() {
|
|
require.NoError(t, dbBob.Close())
|
|
})
|
|
|
|
feePerKw, err := estimator.EstimateFeePerKW(1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO(roasbeef): need to factor in commit fee?
|
|
aliceCommit := channeldb.ChannelCommitment{
|
|
CommitHeight: 0,
|
|
LocalBalance: lnwire.NewMSatFromSatoshis(channelBal),
|
|
RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal),
|
|
FeePerKw: btcutil.Amount(feePerKw),
|
|
CommitFee: feePerKw.FeeForWeight(input.CommitWeight),
|
|
CommitTx: aliceCommitTx,
|
|
CommitSig: bytes.Repeat([]byte{1}, 71),
|
|
}
|
|
bobCommit := channeldb.ChannelCommitment{
|
|
CommitHeight: 0,
|
|
LocalBalance: lnwire.NewMSatFromSatoshis(channelBal),
|
|
RemoteBalance: lnwire.NewMSatFromSatoshis(channelBal),
|
|
FeePerKw: btcutil.Amount(feePerKw),
|
|
CommitFee: feePerKw.FeeForWeight(input.CommitWeight),
|
|
CommitTx: bobCommitTx,
|
|
CommitSig: bytes.Repeat([]byte{1}, 71),
|
|
}
|
|
|
|
var chanIDBytes [8]byte
|
|
if _, err := io.ReadFull(crand.Reader, chanIDBytes[:]); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
shortChanID := lnwire.NewShortChanIDFromInt(
|
|
binary.BigEndian.Uint64(chanIDBytes[:]),
|
|
)
|
|
|
|
aliceChannelState := &channeldb.OpenChannel{
|
|
LocalChanCfg: aliceCfg,
|
|
RemoteChanCfg: bobCfg,
|
|
IdentityPub: aliceKeyPub,
|
|
FundingOutpoint: *prevOut,
|
|
ShortChannelID: shortChanID,
|
|
ChanType: channeldb.SingleFunderTweaklessBit,
|
|
IsInitiator: isAliceInitiator,
|
|
Capacity: channelCapacity,
|
|
RemoteCurrentRevocation: bobCommitPoint,
|
|
RevocationProducer: alicePreimageProducer,
|
|
RevocationStore: shachain.NewRevocationStore(),
|
|
LocalCommitment: aliceCommit,
|
|
RemoteCommitment: aliceCommit,
|
|
Db: dbAlice.ChannelStateDB(),
|
|
Packager: channeldb.NewChannelPackager(shortChanID),
|
|
FundingTxn: channels.TestFundingTx,
|
|
}
|
|
bobChannelState := &channeldb.OpenChannel{
|
|
LocalChanCfg: bobCfg,
|
|
RemoteChanCfg: aliceCfg,
|
|
IdentityPub: bobKeyPub,
|
|
FundingOutpoint: *prevOut,
|
|
ChanType: channeldb.SingleFunderTweaklessBit,
|
|
IsInitiator: !isAliceInitiator,
|
|
Capacity: channelCapacity,
|
|
RemoteCurrentRevocation: aliceCommitPoint,
|
|
RevocationProducer: bobPreimageProducer,
|
|
RevocationStore: shachain.NewRevocationStore(),
|
|
LocalCommitment: bobCommit,
|
|
RemoteCommitment: bobCommit,
|
|
Db: dbBob.ChannelStateDB(),
|
|
Packager: channeldb.NewChannelPackager(shortChanID),
|
|
}
|
|
|
|
// Set custom values on the channel states.
|
|
updateChan(aliceChannelState, bobChannelState)
|
|
|
|
aliceAddr := alicePeer.cfg.Addr.Address
|
|
if err := aliceChannelState.SyncPending(aliceAddr, 0); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
bobAddr := &net.TCPAddr{
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
Port: 18556,
|
|
}
|
|
|
|
if err := bobChannelState.SyncPending(bobAddr, 0); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
aliceSigner := input.NewMockSigner(
|
|
[]*btcec.PrivateKey{aliceKeyPriv}, nil,
|
|
)
|
|
bobSigner := input.NewMockSigner(
|
|
[]*btcec.PrivateKey{bobKeyPriv}, nil,
|
|
)
|
|
|
|
alicePool := lnwallet.NewSigPool(1, aliceSigner)
|
|
channelAlice, err := lnwallet.NewLightningChannel(
|
|
aliceSigner, aliceChannelState, alicePool,
|
|
lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}),
|
|
lnwallet.WithAuxSigner(&lnwallet.MockAuxSigner{}),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_ = alicePool.Start()
|
|
t.Cleanup(func() {
|
|
require.NoError(t, alicePool.Stop())
|
|
})
|
|
|
|
bobPool := lnwallet.NewSigPool(1, bobSigner)
|
|
channelBob, err := lnwallet.NewLightningChannel(
|
|
bobSigner, bobChannelState, bobPool,
|
|
lnwallet.WithLeafStore(&lnwallet.MockAuxLeafStore{}),
|
|
lnwallet.WithAuxSigner(&lnwallet.MockAuxSigner{}),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_ = bobPool.Start()
|
|
t.Cleanup(func() {
|
|
require.NoError(t, bobPool.Stop())
|
|
})
|
|
|
|
alicePeer.remoteFeatures = lnwire.NewFeatureVector(
|
|
nil, lnwire.Features,
|
|
)
|
|
|
|
chanID := lnwire.NewChanIDFromOutPoint(channelAlice.ChannelPoint())
|
|
alicePeer.activeChannels.Store(chanID, channelAlice)
|
|
|
|
alicePeer.wg.Add(1)
|
|
go alicePeer.channelManager()
|
|
|
|
return &peerTestCtx{
|
|
peer: alicePeer,
|
|
channel: channelBob,
|
|
notifier: notifier,
|
|
publishTx: publishTx,
|
|
mockSwitch: mockSwitch,
|
|
mockConn: params.mockConn,
|
|
}, nil
|
|
}
|
|
|
|
// mockMessageSwitch is a mock implementation of the messageSwitch interface
|
|
// used for testing without relying on a *htlcswitch.Switch in unit tests.
|
|
type mockMessageSwitch struct {
|
|
links []htlcswitch.ChannelUpdateHandler
|
|
}
|
|
|
|
// BestHeight currently returns a dummy value.
|
|
func (m *mockMessageSwitch) BestHeight() uint32 {
|
|
return 0
|
|
}
|
|
|
|
// CircuitModifier currently returns a dummy value.
|
|
func (m *mockMessageSwitch) CircuitModifier() htlcswitch.CircuitModifier {
|
|
return nil
|
|
}
|
|
|
|
// RemoveLink currently does nothing.
|
|
func (m *mockMessageSwitch) RemoveLink(cid lnwire.ChannelID) {}
|
|
|
|
// CreateAndAddLink currently returns a dummy value.
|
|
func (m *mockMessageSwitch) CreateAndAddLink(cfg htlcswitch.ChannelLinkConfig,
|
|
lnChan *lnwallet.LightningChannel) error {
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetLinksByInterface returns the active links.
|
|
func (m *mockMessageSwitch) GetLinksByInterface(pub [33]byte) (
|
|
[]htlcswitch.ChannelUpdateHandler, error) {
|
|
|
|
return m.links, nil
|
|
}
|
|
|
|
// mockUpdateHandler is a mock implementation of the ChannelUpdateHandler
|
|
// interface. It is used in mockMessageSwitch's GetLinksByInterface method.
|
|
type mockUpdateHandler struct {
|
|
cid lnwire.ChannelID
|
|
isOutgoingAddBlocked atomic.Bool
|
|
isIncomingAddBlocked atomic.Bool
|
|
}
|
|
|
|
// newMockUpdateHandler creates a new mockUpdateHandler.
|
|
func newMockUpdateHandler(cid lnwire.ChannelID) *mockUpdateHandler {
|
|
return &mockUpdateHandler{
|
|
cid: cid,
|
|
}
|
|
}
|
|
|
|
// HandleChannelUpdate currently does nothing.
|
|
func (m *mockUpdateHandler) HandleChannelUpdate(msg lnwire.Message) {}
|
|
|
|
// ChanID returns the mockUpdateHandler's cid.
|
|
func (m *mockUpdateHandler) ChanID() lnwire.ChannelID { return m.cid }
|
|
|
|
// Bandwidth currently returns a dummy value.
|
|
func (m *mockUpdateHandler) Bandwidth() lnwire.MilliSatoshi { return 0 }
|
|
|
|
// EligibleToForward currently returns a dummy value.
|
|
func (m *mockUpdateHandler) EligibleToForward() bool { return false }
|
|
|
|
// MayAddOutgoingHtlc currently returns nil.
|
|
func (m *mockUpdateHandler) MayAddOutgoingHtlc(lnwire.MilliSatoshi) error { return nil }
|
|
|
|
type mockMessageConn struct {
|
|
t *testing.T
|
|
|
|
// MessageConn embeds our interface so that the mock does not need to
|
|
// implement every function. The mock will panic if an unspecified function
|
|
// is called.
|
|
MessageConn
|
|
|
|
// writtenMessages is a channel that our mock pushes written messages into.
|
|
writtenMessages chan []byte
|
|
|
|
readMessages chan []byte
|
|
curReadMessage []byte
|
|
|
|
// writeRaceDetectingCounter is incremented on any function call
|
|
// associated with writing to the connection. The race detector will
|
|
// trigger on this counter if a data race exists.
|
|
writeRaceDetectingCounter int
|
|
|
|
// readRaceDetectingCounter is incremented on any function call
|
|
// associated with reading from the connection. The race detector will
|
|
// trigger on this counter if a data race exists.
|
|
readRaceDetectingCounter int
|
|
}
|
|
|
|
func (m *mockUpdateHandler) EnableAdds(dir htlcswitch.LinkDirection) bool {
|
|
if dir == htlcswitch.Outgoing {
|
|
return m.isOutgoingAddBlocked.Swap(false)
|
|
}
|
|
|
|
return m.isIncomingAddBlocked.Swap(false)
|
|
}
|
|
|
|
func (m *mockUpdateHandler) DisableAdds(dir htlcswitch.LinkDirection) bool {
|
|
if dir == htlcswitch.Outgoing {
|
|
return !m.isOutgoingAddBlocked.Swap(true)
|
|
}
|
|
|
|
return !m.isIncomingAddBlocked.Swap(true)
|
|
}
|
|
|
|
func (m *mockUpdateHandler) IsFlushing(dir htlcswitch.LinkDirection) bool {
|
|
switch dir {
|
|
case htlcswitch.Outgoing:
|
|
return m.isOutgoingAddBlocked.Load()
|
|
case htlcswitch.Incoming:
|
|
return m.isIncomingAddBlocked.Load()
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (m *mockUpdateHandler) OnFlushedOnce(hook func()) {
|
|
hook()
|
|
}
|
|
func (m *mockUpdateHandler) OnCommitOnce(
|
|
_ htlcswitch.LinkDirection, hook func(),
|
|
) {
|
|
|
|
hook()
|
|
}
|
|
|
|
func newMockConn(t *testing.T, expectedMessages int) *mockMessageConn {
|
|
return &mockMessageConn{
|
|
t: t,
|
|
writtenMessages: make(chan []byte, expectedMessages),
|
|
readMessages: make(chan []byte, 1),
|
|
}
|
|
}
|
|
|
|
// SetWriteDeadline mocks setting write deadline for our conn.
|
|
func (m *mockMessageConn) SetWriteDeadline(time.Time) error {
|
|
m.writeRaceDetectingCounter++
|
|
return nil
|
|
}
|
|
|
|
// Flush mocks a message conn flush.
|
|
func (m *mockMessageConn) Flush() (int, error) {
|
|
m.writeRaceDetectingCounter++
|
|
return 0, nil
|
|
}
|
|
|
|
// WriteMessage mocks sending of a message on our connection. It will push
|
|
// the bytes sent into the mock's writtenMessages channel.
|
|
func (m *mockMessageConn) WriteMessage(msg []byte) error {
|
|
m.writeRaceDetectingCounter++
|
|
|
|
msgCopy := make([]byte, len(msg))
|
|
copy(msgCopy, msg)
|
|
|
|
select {
|
|
case m.writtenMessages <- msgCopy:
|
|
case <-time.After(timeout):
|
|
m.t.Fatalf("timeout sending message: %v", msgCopy)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// assertWrite asserts that our mock as had WriteMessage called with the byte
|
|
// slice we expect.
|
|
func (m *mockMessageConn) assertWrite(expected []byte) {
|
|
select {
|
|
case actual := <-m.writtenMessages:
|
|
require.Equal(m.t, expected, actual)
|
|
|
|
case <-time.After(timeout):
|
|
m.t.Fatalf("timeout waiting for write: %v", expected)
|
|
}
|
|
}
|
|
|
|
func (m *mockMessageConn) SetReadDeadline(t time.Time) error {
|
|
m.readRaceDetectingCounter++
|
|
return nil
|
|
}
|
|
|
|
func (m *mockMessageConn) ReadNextHeader() (uint32, error) {
|
|
m.readRaceDetectingCounter++
|
|
m.curReadMessage = <-m.readMessages
|
|
return uint32(len(m.curReadMessage)), nil
|
|
}
|
|
|
|
func (m *mockMessageConn) ReadNextBody(buf []byte) ([]byte, error) {
|
|
m.readRaceDetectingCounter++
|
|
return m.curReadMessage, nil
|
|
}
|
|
|
|
func (m *mockMessageConn) RemoteAddr() net.Addr {
|
|
return nil
|
|
}
|
|
|
|
func (m *mockMessageConn) LocalAddr() net.Addr {
|
|
return nil
|
|
}
|
|
|
|
func (m *mockMessageConn) Close() error {
|
|
return nil
|
|
}
|
|
|
|
// createTestPeer creates a new peer for testing and returns a context struct
|
|
// containing necessary handles and mock objects for conducting tests on peer
|
|
// functionalities.
|
|
func createTestPeer(t *testing.T) *peerTestCtx {
|
|
nodeKeyLocator := keychain.KeyLocator{
|
|
Family: keychain.KeyFamilyNodeKey,
|
|
}
|
|
|
|
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(
|
|
channels.AlicesPrivKey,
|
|
)
|
|
|
|
aliceKeySigner := keychain.NewPrivKeyMessageSigner(
|
|
aliceKeyPriv, nodeKeyLocator,
|
|
)
|
|
|
|
aliceAddr := &net.TCPAddr{
|
|
IP: net.ParseIP("127.0.0.1"),
|
|
Port: 18555,
|
|
}
|
|
cfgAddr := &lnwire.NetAddress{
|
|
IdentityKey: aliceKeyPub,
|
|
Address: aliceAddr,
|
|
ChainNet: wire.SimNet,
|
|
}
|
|
|
|
errBuffer, err := queue.NewCircularBuffer(ErrorBufferSize)
|
|
require.NoError(t, err)
|
|
|
|
chainIO := &mock.ChainIO{
|
|
BestHeight: broadcastHeight,
|
|
}
|
|
|
|
publishTx := make(chan *wire.MsgTx)
|
|
wallet := &lnwallet.LightningWallet{
|
|
WalletController: &mock.WalletController{
|
|
RootKey: aliceKeyPriv,
|
|
PublishedTransactions: publishTx,
|
|
},
|
|
}
|
|
|
|
const chanActiveTimeout = time.Minute
|
|
|
|
dbAlice, err := channeldb.Open(t.TempDir())
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
require.NoError(t, dbAlice.Close())
|
|
})
|
|
|
|
nodeSignerAlice := netann.NewNodeSigner(aliceKeySigner)
|
|
|
|
chanStatusMgr, err := netann.NewChanStatusManager(&netann.
|
|
ChanStatusConfig{
|
|
ChanStatusSampleInterval: 30 * time.Second,
|
|
ChanEnableTimeout: chanActiveTimeout,
|
|
ChanDisableTimeout: 2 * time.Minute,
|
|
DB: dbAlice.ChannelStateDB(),
|
|
Graph: dbAlice.ChannelGraph(),
|
|
MessageSigner: nodeSignerAlice,
|
|
OurPubKey: aliceKeyPub,
|
|
OurKeyLoc: testKeyLoc,
|
|
IsChannelActive: func(lnwire.ChannelID) bool {
|
|
return true
|
|
},
|
|
ApplyChannelUpdate: func(*lnwire.ChannelUpdate1,
|
|
*wire.OutPoint, bool) error {
|
|
|
|
return nil
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
interceptableSwitchNotifier := &mock.ChainNotifier{
|
|
EpochChan: make(chan *chainntnfs.BlockEpoch, 1),
|
|
}
|
|
interceptableSwitchNotifier.EpochChan <- &chainntnfs.BlockEpoch{
|
|
Height: 1,
|
|
}
|
|
|
|
interceptableSwitch, err := htlcswitch.NewInterceptableSwitch(
|
|
&htlcswitch.InterceptableSwitchConfig{
|
|
CltvRejectDelta: testCltvRejectDelta,
|
|
CltvInterceptDelta: testCltvRejectDelta + 3,
|
|
Notifier: interceptableSwitchNotifier,
|
|
},
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
// TODO(yy): create interface for lnwallet.LightningChannel so we can
|
|
// easily mock it without the following setups.
|
|
notifier := &mock.ChainNotifier{
|
|
SpendChan: make(chan *chainntnfs.SpendDetail),
|
|
EpochChan: make(chan *chainntnfs.BlockEpoch),
|
|
ConfChan: make(chan *chainntnfs.TxConfirmation),
|
|
}
|
|
|
|
mockSwitch := &mockMessageSwitch{}
|
|
|
|
// TODO(yy): change ChannelNotifier to be an interface.
|
|
channelNotifier := channelnotifier.New(dbAlice.ChannelStateDB())
|
|
require.NoError(t, channelNotifier.Start())
|
|
t.Cleanup(func() {
|
|
require.NoError(t, channelNotifier.Stop(),
|
|
"stop channel notifier failed")
|
|
})
|
|
|
|
writeBufferPool := pool.NewWriteBuffer(
|
|
pool.DefaultWriteBufferGCInterval,
|
|
pool.DefaultWriteBufferExpiryInterval,
|
|
)
|
|
|
|
writePool := pool.NewWrite(
|
|
writeBufferPool, 1, timeout,
|
|
)
|
|
require.NoError(t, writePool.Start())
|
|
|
|
readBufferPool := pool.NewReadBuffer(
|
|
pool.DefaultReadBufferGCInterval,
|
|
pool.DefaultReadBufferExpiryInterval,
|
|
)
|
|
|
|
readPool := pool.NewRead(
|
|
readBufferPool, 1, timeout,
|
|
)
|
|
require.NoError(t, readPool.Start())
|
|
|
|
mockConn := newMockConn(t, 1)
|
|
|
|
receivedCustomChan := make(chan *customMsg)
|
|
|
|
var pubKey [33]byte
|
|
copy(pubKey[:], aliceKeyPub.SerializeCompressed())
|
|
|
|
estimator := chainfee.NewStaticEstimator(12500, 0)
|
|
|
|
cfg := &Config{
|
|
Addr: cfgAddr,
|
|
PubKeyBytes: pubKey,
|
|
ErrorBuffer: errBuffer,
|
|
ChainIO: chainIO,
|
|
Switch: mockSwitch,
|
|
ChanActiveTimeout: chanActiveTimeout,
|
|
InterceptSwitch: interceptableSwitch,
|
|
ChannelDB: dbAlice.ChannelStateDB(),
|
|
FeeEstimator: estimator,
|
|
Wallet: wallet,
|
|
ChainNotifier: notifier,
|
|
ChanStatusMgr: chanStatusMgr,
|
|
Features: lnwire.NewFeatureVector(
|
|
nil, lnwire.Features,
|
|
),
|
|
DisconnectPeer: func(b *btcec.PublicKey) error {
|
|
return nil
|
|
},
|
|
ChannelNotifier: channelNotifier,
|
|
PrunePersistentPeerConnection: func([33]byte) {},
|
|
LegacyFeatures: lnwire.EmptyFeatureVector(),
|
|
WritePool: writePool,
|
|
ReadPool: readPool,
|
|
Conn: mockConn,
|
|
HandleCustomMessage: func(
|
|
peer [33]byte, msg *lnwire.Custom) error {
|
|
|
|
receivedCustomChan <- &customMsg{
|
|
peer: peer,
|
|
msg: *msg,
|
|
}
|
|
|
|
return nil
|
|
},
|
|
PongBuf: make([]byte, lnwire.MaxPongBytes),
|
|
FetchLastChanUpdate: func(chanID lnwire.ShortChannelID,
|
|
) (*lnwire.ChannelUpdate1, error) {
|
|
|
|
return &lnwire.ChannelUpdate1{}, nil
|
|
},
|
|
}
|
|
|
|
alicePeer := NewBrontide(*cfg)
|
|
|
|
return &peerTestCtx{
|
|
publishTx: publishTx,
|
|
mockSwitch: mockSwitch,
|
|
peer: alicePeer,
|
|
notifier: notifier,
|
|
db: dbAlice,
|
|
privKey: aliceKeyPriv,
|
|
mockConn: mockConn,
|
|
customChan: receivedCustomChan,
|
|
chanStatusMgr: chanStatusMgr,
|
|
}
|
|
}
|
|
|
|
// startPeer invokes the `Start` method on the specified peer and handles any
|
|
// initial startup messages for testing.
|
|
func startPeer(t *testing.T, mockConn *mockMessageConn,
|
|
peer *Brontide) <-chan struct{} {
|
|
|
|
// Start the peer in a goroutine so that we can handle and test for
|
|
// startup messages. Successfully sending and receiving init message,
|
|
// indicates a successful startup.
|
|
done := make(chan struct{})
|
|
go func() {
|
|
require.NoError(t, peer.Start())
|
|
close(done)
|
|
}()
|
|
|
|
// Receive the init message that should be the first message received on
|
|
// startup.
|
|
rawMsg, err := fn.RecvOrTimeout[[]byte](
|
|
mockConn.writtenMessages, timeout,
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
msgReader := bytes.NewReader(rawMsg)
|
|
nextMsg, err := lnwire.ReadMessage(msgReader, 0)
|
|
require.NoError(t, err)
|
|
|
|
_, ok := nextMsg.(*lnwire.Init)
|
|
require.True(t, ok)
|
|
|
|
// Write the reply for the init message to complete the startup.
|
|
initReplyMsg := lnwire.NewInitMessage(
|
|
lnwire.NewRawFeatureVector(
|
|
lnwire.DataLossProtectRequired,
|
|
lnwire.GossipQueriesOptional,
|
|
),
|
|
lnwire.NewRawFeatureVector(),
|
|
)
|
|
|
|
var b bytes.Buffer
|
|
_, err = lnwire.WriteMessage(&b, initReplyMsg, 0)
|
|
require.NoError(t, err)
|
|
|
|
ok = fn.SendOrQuit[[]byte, struct{}](
|
|
mockConn.readMessages, b.Bytes(), make(chan struct{}),
|
|
)
|
|
require.True(t, ok)
|
|
|
|
return done
|
|
}
|