netann/chan_status_manager_test: adds ChanStatusManager unit tests
This commit is contained in:
parent
53797b7dc7
commit
cd467195d7
816
netann/chan_status_manager_test.go
Normal file
816
netann/chan_status_manager_test.go
Normal file
@ -0,0 +1,816 @@
|
|||||||
|
package netann_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcec"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"github.com/lightningnetwork/lnd/netann"
|
||||||
|
)
|
||||||
|
|
||||||
|
// randOutpoint creates a random wire.Outpoint.
|
||||||
|
func randOutpoint(t *testing.T) wire.OutPoint {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
var buf [36]byte
|
||||||
|
_, err := io.ReadFull(rand.Reader, buf[:])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to generate random outpoint: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
op := wire.OutPoint{}
|
||||||
|
copy(op.Hash[:], buf[:32])
|
||||||
|
op.Index = binary.BigEndian.Uint32(buf[32:])
|
||||||
|
|
||||||
|
return op
|
||||||
|
}
|
||||||
|
|
||||||
|
var shortChanIDs uint64
|
||||||
|
|
||||||
|
// createChannel generates a channeldb.OpenChannel with a random chanpoint and
|
||||||
|
// short channel id.
|
||||||
|
func createChannel(t *testing.T) *channeldb.OpenChannel {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
sid := atomic.AddUint64(&shortChanIDs, 1)
|
||||||
|
|
||||||
|
return &channeldb.OpenChannel{
|
||||||
|
ShortChannelID: lnwire.NewShortChanIDFromInt(sid),
|
||||||
|
ChannelFlags: lnwire.FFAnnounceChannel,
|
||||||
|
FundingOutpoint: randOutpoint(t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// createEdgePolicies generates an edge info and two directional edge policies.
|
||||||
|
// The remote party's public key is generated randomly, and then sorted against
|
||||||
|
// our `pubkey` with the direction bit set appropriately in the policies. Our
|
||||||
|
// update will be created with the disabled bit set if startEnabled is false.
|
||||||
|
func createEdgePolicies(t *testing.T, channel *channeldb.OpenChannel,
|
||||||
|
pubkey *btcec.PublicKey, startEnabled bool) (*channeldb.ChannelEdgeInfo,
|
||||||
|
*channeldb.ChannelEdgePolicy, *channeldb.ChannelEdgePolicy) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
pubkey1 [33]byte
|
||||||
|
pubkey2 [33]byte
|
||||||
|
dir1 lnwire.ChanUpdateChanFlags
|
||||||
|
dir2 lnwire.ChanUpdateChanFlags
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set pubkey1 to OUR pubkey.
|
||||||
|
copy(pubkey1[:], pubkey.SerializeCompressed())
|
||||||
|
|
||||||
|
// Set the disabled bit appropriately on our update.
|
||||||
|
if !startEnabled {
|
||||||
|
dir1 |= lnwire.ChanUpdateDisabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate and set pubkey2 for THEIR pubkey.
|
||||||
|
privKey2, err := btcec.NewPrivateKey(btcec.S256())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to generate key pair: %v", err)
|
||||||
|
}
|
||||||
|
copy(pubkey2[:], privKey2.PubKey().SerializeCompressed())
|
||||||
|
|
||||||
|
// Set pubkey1 to the lower of the two pubkeys.
|
||||||
|
if bytes.Compare(pubkey2[:], pubkey1[:]) < 0 {
|
||||||
|
pubkey1, pubkey2 = pubkey2, pubkey1
|
||||||
|
dir1, dir2 = dir2, dir1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that the ordering has been established, set pubkey2's direction
|
||||||
|
// bit.
|
||||||
|
dir2 |= lnwire.ChanUpdateDirection
|
||||||
|
|
||||||
|
return &channeldb.ChannelEdgeInfo{
|
||||||
|
ChannelPoint: channel.FundingOutpoint,
|
||||||
|
NodeKey1Bytes: pubkey1,
|
||||||
|
NodeKey2Bytes: pubkey2,
|
||||||
|
},
|
||||||
|
&channeldb.ChannelEdgePolicy{
|
||||||
|
ChannelID: channel.ShortChanID().ToUint64(),
|
||||||
|
ChannelFlags: dir1,
|
||||||
|
LastUpdate: time.Now(),
|
||||||
|
SigBytes: make([]byte, 64),
|
||||||
|
},
|
||||||
|
&channeldb.ChannelEdgePolicy{
|
||||||
|
ChannelID: channel.ShortChanID().ToUint64(),
|
||||||
|
ChannelFlags: dir2,
|
||||||
|
LastUpdate: time.Now(),
|
||||||
|
SigBytes: make([]byte, 64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockGraph struct {
|
||||||
|
pubKey *btcec.PublicKey
|
||||||
|
mu sync.Mutex
|
||||||
|
channels []*channeldb.OpenChannel
|
||||||
|
chanInfos map[wire.OutPoint]*channeldb.ChannelEdgeInfo
|
||||||
|
chanPols1 map[wire.OutPoint]*channeldb.ChannelEdgePolicy
|
||||||
|
chanPols2 map[wire.OutPoint]*channeldb.ChannelEdgePolicy
|
||||||
|
sidToCid map[lnwire.ShortChannelID]wire.OutPoint
|
||||||
|
|
||||||
|
updates chan *lnwire.ChannelUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockGraph(t *testing.T, numChannels int,
|
||||||
|
startActive, startEnabled bool, pubKey *btcec.PublicKey) *mockGraph {
|
||||||
|
|
||||||
|
g := &mockGraph{
|
||||||
|
channels: make([]*channeldb.OpenChannel, 0, numChannels),
|
||||||
|
chanInfos: make(map[wire.OutPoint]*channeldb.ChannelEdgeInfo),
|
||||||
|
chanPols1: make(map[wire.OutPoint]*channeldb.ChannelEdgePolicy),
|
||||||
|
chanPols2: make(map[wire.OutPoint]*channeldb.ChannelEdgePolicy),
|
||||||
|
sidToCid: make(map[lnwire.ShortChannelID]wire.OutPoint),
|
||||||
|
updates: make(chan *lnwire.ChannelUpdate, 2*numChannels),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < numChannels; i++ {
|
||||||
|
c := createChannel(t)
|
||||||
|
|
||||||
|
info, pol1, pol2 := createEdgePolicies(
|
||||||
|
t, c, pubKey, startEnabled,
|
||||||
|
)
|
||||||
|
|
||||||
|
g.addChannel(c)
|
||||||
|
g.addEdgePolicy(c, info, pol1, pol2)
|
||||||
|
}
|
||||||
|
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *mockGraph) FetchAllOpenChannels() ([]*channeldb.OpenChannel, error) {
|
||||||
|
return g.chans(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *mockGraph) FetchChannelEdgesByOutpoint(
|
||||||
|
op *wire.OutPoint) (*channeldb.ChannelEdgeInfo,
|
||||||
|
*channeldb.ChannelEdgePolicy, *channeldb.ChannelEdgePolicy, error) {
|
||||||
|
|
||||||
|
g.mu.Lock()
|
||||||
|
defer g.mu.Unlock()
|
||||||
|
|
||||||
|
info, ok := g.chanInfos[*op]
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, nil, channeldb.ErrEdgeNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
pol1 := g.chanPols1[*op]
|
||||||
|
pol2 := g.chanPols2[*op]
|
||||||
|
|
||||||
|
return info, pol1, pol2, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *mockGraph) ApplyChannelUpdate(update *lnwire.ChannelUpdate) error {
|
||||||
|
g.mu.Lock()
|
||||||
|
defer g.mu.Unlock()
|
||||||
|
|
||||||
|
outpoint, ok := g.sidToCid[update.ShortChannelID]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("unknown short channel id: %v",
|
||||||
|
update.ShortChannelID)
|
||||||
|
}
|
||||||
|
|
||||||
|
pol1 := g.chanPols1[outpoint]
|
||||||
|
pol2 := g.chanPols2[outpoint]
|
||||||
|
|
||||||
|
// Determine which policy we should update by making the flags on the
|
||||||
|
// policies and updates, and seeing which match up.
|
||||||
|
var update1 bool
|
||||||
|
switch {
|
||||||
|
case update.ChannelFlags&lnwire.ChanUpdateDirection ==
|
||||||
|
pol1.ChannelFlags&lnwire.ChanUpdateDirection:
|
||||||
|
update1 = true
|
||||||
|
|
||||||
|
case update.ChannelFlags&lnwire.ChanUpdateDirection ==
|
||||||
|
pol2.ChannelFlags&lnwire.ChanUpdateDirection:
|
||||||
|
update1 = false
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unable to find policy to update")
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp := time.Unix(int64(update.Timestamp), 0)
|
||||||
|
|
||||||
|
policy := &channeldb.ChannelEdgePolicy{
|
||||||
|
ChannelID: update.ShortChannelID.ToUint64(),
|
||||||
|
ChannelFlags: update.ChannelFlags,
|
||||||
|
LastUpdate: timestamp,
|
||||||
|
SigBytes: make([]byte, 64),
|
||||||
|
}
|
||||||
|
|
||||||
|
if update1 {
|
||||||
|
g.chanPols1[outpoint] = policy
|
||||||
|
} else {
|
||||||
|
g.chanPols2[outpoint] = policy
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send the update to network. This channel should be sufficiently
|
||||||
|
// buffered to avoid deadlocking.
|
||||||
|
g.updates <- update
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *mockGraph) chans() []*channeldb.OpenChannel {
|
||||||
|
g.mu.Lock()
|
||||||
|
defer g.mu.Unlock()
|
||||||
|
|
||||||
|
channels := make([]*channeldb.OpenChannel, 0, len(g.channels))
|
||||||
|
for _, channel := range g.channels {
|
||||||
|
channels = append(channels, channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
return channels
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *mockGraph) addChannel(channel *channeldb.OpenChannel) {
|
||||||
|
g.mu.Lock()
|
||||||
|
defer g.mu.Unlock()
|
||||||
|
|
||||||
|
g.channels = append(g.channels, channel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *mockGraph) addEdgePolicy(c *channeldb.OpenChannel,
|
||||||
|
info *channeldb.ChannelEdgeInfo,
|
||||||
|
pol1, pol2 *channeldb.ChannelEdgePolicy) {
|
||||||
|
|
||||||
|
g.mu.Lock()
|
||||||
|
defer g.mu.Unlock()
|
||||||
|
|
||||||
|
g.chanInfos[c.FundingOutpoint] = info
|
||||||
|
g.chanPols1[c.FundingOutpoint] = pol1
|
||||||
|
g.chanPols2[c.FundingOutpoint] = pol2
|
||||||
|
g.sidToCid[c.ShortChanID()] = c.FundingOutpoint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *mockGraph) removeChannel(channel *channeldb.OpenChannel) {
|
||||||
|
g.mu.Lock()
|
||||||
|
defer g.mu.Unlock()
|
||||||
|
|
||||||
|
for i, c := range g.channels {
|
||||||
|
if c.FundingOutpoint != channel.FundingOutpoint {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
g.channels = append(g.channels[:i], g.channels[i+1:]...)
|
||||||
|
delete(g.chanInfos, c.FundingOutpoint)
|
||||||
|
delete(g.chanPols1, c.FundingOutpoint)
|
||||||
|
delete(g.chanPols2, c.FundingOutpoint)
|
||||||
|
delete(g.sidToCid, c.ShortChanID())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockSwitch struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
isActive map[lnwire.ChannelID]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockSwitch() *mockSwitch {
|
||||||
|
return &mockSwitch{
|
||||||
|
isActive: make(map[lnwire.ChannelID]bool),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *mockSwitch) HasActiveLink(chanID lnwire.ChannelID) bool {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
// If the link is found, we will returns it's active status. In the
|
||||||
|
// real switch, it returns EligibleToForward().
|
||||||
|
active, ok := s.isActive[chanID]
|
||||||
|
if ok {
|
||||||
|
return active
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *mockSwitch) SetStatus(chanID lnwire.ChannelID, active bool) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
s.isActive[chanID] = active
|
||||||
|
}
|
||||||
|
|
||||||
|
func newManagerCfg(t *testing.T, numChannels int,
|
||||||
|
startEnabled bool) (*netann.ChanStatusConfig, *mockGraph, *mockSwitch) {
|
||||||
|
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
privKey, err := btcec.NewPrivateKey(btcec.S256())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to generate key pair: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
graph := newMockGraph(
|
||||||
|
t, numChannels, startEnabled, startEnabled, privKey.PubKey(),
|
||||||
|
)
|
||||||
|
htlcSwitch := newMockSwitch()
|
||||||
|
|
||||||
|
cfg := &netann.ChanStatusConfig{
|
||||||
|
ChanStatusSampleInterval: 50 * time.Millisecond,
|
||||||
|
ChanEnableTimeout: 500 * time.Millisecond,
|
||||||
|
ChanDisableTimeout: time.Second,
|
||||||
|
OurPubKey: privKey.PubKey(),
|
||||||
|
MessageSigner: netann.NewNodeSigner(privKey),
|
||||||
|
IsChannelActive: htlcSwitch.HasActiveLink,
|
||||||
|
ApplyChannelUpdate: graph.ApplyChannelUpdate,
|
||||||
|
DB: graph,
|
||||||
|
Graph: graph,
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, graph, htlcSwitch
|
||||||
|
}
|
||||||
|
|
||||||
|
type testHarness struct {
|
||||||
|
t *testing.T
|
||||||
|
numChannels int
|
||||||
|
graph *mockGraph
|
||||||
|
htlcSwitch *mockSwitch
|
||||||
|
mgr *netann.ChanStatusManager
|
||||||
|
ourPubKey *btcec.PublicKey
|
||||||
|
safeDisableTimeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// newHarness returns a new testHarness for testing a ChanStatusManager. The
|
||||||
|
// mockGraph will be populated with numChannels channels. The startActive and
|
||||||
|
// startEnabled govern the initial state of the channels wrt the htlcswitch and
|
||||||
|
// the network, respectively.
|
||||||
|
func newHarness(t *testing.T, numChannels int,
|
||||||
|
startActive, startEnabled bool) testHarness {
|
||||||
|
|
||||||
|
cfg, graph, htlcSwitch := newManagerCfg(t, numChannels, startEnabled)
|
||||||
|
|
||||||
|
mgr, err := netann.NewChanStatusManager(cfg)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to create chan status manager: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mgr.Start()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unable to start chan status manager: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
h := testHarness{
|
||||||
|
t: t,
|
||||||
|
numChannels: numChannels,
|
||||||
|
graph: graph,
|
||||||
|
htlcSwitch: htlcSwitch,
|
||||||
|
mgr: mgr,
|
||||||
|
ourPubKey: cfg.OurPubKey,
|
||||||
|
safeDisableTimeout: (3 * cfg.ChanDisableTimeout) / 2, // 1.5x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize link status as requested.
|
||||||
|
if startActive {
|
||||||
|
h.markActive(h.graph.channels)
|
||||||
|
} else {
|
||||||
|
h.markInactive(h.graph.channels)
|
||||||
|
}
|
||||||
|
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
// markActive updates the active status of the passed channels within the mock
|
||||||
|
// switch to active.
|
||||||
|
func (h *testHarness) markActive(channels []*channeldb.OpenChannel) {
|
||||||
|
h.t.Helper()
|
||||||
|
|
||||||
|
for _, channel := range channels {
|
||||||
|
chanID := lnwire.NewChanIDFromOutPoint(&channel.FundingOutpoint)
|
||||||
|
h.htlcSwitch.SetStatus(chanID, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// markInactive updates the active status of the passed channels within the mock
|
||||||
|
// switch to inactive.
|
||||||
|
func (h *testHarness) markInactive(channels []*channeldb.OpenChannel) {
|
||||||
|
h.t.Helper()
|
||||||
|
|
||||||
|
for _, channel := range channels {
|
||||||
|
chanID := lnwire.NewChanIDFromOutPoint(&channel.FundingOutpoint)
|
||||||
|
h.htlcSwitch.SetStatus(chanID, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertEnables requests enables for all of the passed channels, and asserts
|
||||||
|
// that the errors returned from RequestEnable matches expErr.
|
||||||
|
func (h *testHarness) assertEnables(channels []*channeldb.OpenChannel, expErr error) {
|
||||||
|
h.t.Helper()
|
||||||
|
|
||||||
|
for _, channel := range channels {
|
||||||
|
h.assertEnable(channel.FundingOutpoint, expErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertDisables requests disables for all of the passed channels, and asserts
|
||||||
|
// that the errors returned from RequestDisable matches expErr.
|
||||||
|
func (h *testHarness) assertDisables(channels []*channeldb.OpenChannel, expErr error) {
|
||||||
|
h.t.Helper()
|
||||||
|
|
||||||
|
for _, channel := range channels {
|
||||||
|
h.assertDisable(channel.FundingOutpoint, expErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertEnable requests an enable for the given outpoint, and asserts that the
|
||||||
|
// returned error matches expErr.
|
||||||
|
func (h *testHarness) assertEnable(outpoint wire.OutPoint, expErr error) {
|
||||||
|
h.t.Helper()
|
||||||
|
|
||||||
|
err := h.mgr.RequestEnable(outpoint)
|
||||||
|
if err != expErr {
|
||||||
|
h.t.Fatalf("expected enable error: %v, got %v", expErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertDisable requests a disable for the given outpoint, and asserts that the
|
||||||
|
// returned error matches expErr.
|
||||||
|
func (h *testHarness) assertDisable(outpoint wire.OutPoint, expErr error) {
|
||||||
|
h.t.Helper()
|
||||||
|
|
||||||
|
err := h.mgr.RequestDisable(outpoint)
|
||||||
|
if err != expErr {
|
||||||
|
h.t.Fatalf("expected disable error: %v, got %v", expErr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertNoUpdates waits for the specified duration, and asserts that no updates
|
||||||
|
// are announced on the network.
|
||||||
|
func (h *testHarness) assertNoUpdates(duration time.Duration) {
|
||||||
|
h.t.Helper()
|
||||||
|
|
||||||
|
h.assertUpdates(nil, false, duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
// assertUpdates waits for the specified duration, asserting that an update
|
||||||
|
// are receive on the network for each of the passed OpenChannels, and that all
|
||||||
|
// of their disable bits are set to match expEnabled. The expEnabled parameter
|
||||||
|
// is ignored if channels is nil.
|
||||||
|
func (h *testHarness) assertUpdates(channels []*channeldb.OpenChannel,
|
||||||
|
expEnabled bool, duration time.Duration) {
|
||||||
|
|
||||||
|
h.t.Helper()
|
||||||
|
|
||||||
|
// Compute an index of the expected short channel ids for which we want
|
||||||
|
// to received updates.
|
||||||
|
expSids := sidsFromChans(channels)
|
||||||
|
|
||||||
|
timeout := time.After(duration)
|
||||||
|
recvdSids := make(map[lnwire.ShortChannelID]struct{})
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case upd := <-h.graph.updates:
|
||||||
|
// Assert that the received short channel id is one that
|
||||||
|
// we expect. If no updates were expected, this will
|
||||||
|
// always fail on the first update received.
|
||||||
|
if _, ok := expSids[upd.ShortChannelID]; !ok {
|
||||||
|
h.t.Fatalf("received update for unexpected "+
|
||||||
|
"short chan id: %v", upd.ShortChannelID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the disabled bit is set properly.
|
||||||
|
enabled := upd.ChannelFlags&lnwire.ChanUpdateDisabled !=
|
||||||
|
lnwire.ChanUpdateDisabled
|
||||||
|
if expEnabled != enabled {
|
||||||
|
h.t.Fatalf("expected enabled: %v, actual: %v",
|
||||||
|
expEnabled, enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
recvdSids[upd.ShortChannelID] = struct{}{}
|
||||||
|
|
||||||
|
case <-timeout:
|
||||||
|
// Time is up, assert that the correct number of unique
|
||||||
|
// updates was received.
|
||||||
|
if len(recvdSids) == len(channels) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.t.Fatalf("expected %d updates, got %d",
|
||||||
|
len(channels), len(recvdSids))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sidsFromChans returns an index contain the short channel ids of each channel
|
||||||
|
// provided in the list of OpenChannels.
|
||||||
|
func sidsFromChans(
|
||||||
|
channels []*channeldb.OpenChannel) map[lnwire.ShortChannelID]struct{} {
|
||||||
|
|
||||||
|
sids := make(map[lnwire.ShortChannelID]struct{})
|
||||||
|
for _, channel := range channels {
|
||||||
|
sids[channel.ShortChanID()] = struct{}{}
|
||||||
|
}
|
||||||
|
return sids
|
||||||
|
}
|
||||||
|
|
||||||
|
type stateMachineTest struct {
|
||||||
|
name string
|
||||||
|
startEnabled bool
|
||||||
|
startActive bool
|
||||||
|
fn func(testHarness)
|
||||||
|
}
|
||||||
|
|
||||||
|
var stateMachineTests = []stateMachineTest{
|
||||||
|
{
|
||||||
|
name: "active and enabled is stable",
|
||||||
|
startActive: true,
|
||||||
|
startEnabled: true,
|
||||||
|
fn: func(h testHarness) {
|
||||||
|
// No updates should be sent because being active and
|
||||||
|
// enabled should be a stable state.
|
||||||
|
h.assertNoUpdates(h.safeDisableTimeout)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "inactive and disabled is stable",
|
||||||
|
startActive: false,
|
||||||
|
startEnabled: false,
|
||||||
|
fn: func(h testHarness) {
|
||||||
|
// No updates should be sent because being inactive and
|
||||||
|
// disabled should be a stable state.
|
||||||
|
h.assertNoUpdates(h.safeDisableTimeout)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "start disabled request enable",
|
||||||
|
startActive: true, // can't request enable unless active
|
||||||
|
startEnabled: false,
|
||||||
|
fn: func(h testHarness) {
|
||||||
|
// Request enables for all channels.
|
||||||
|
h.assertEnables(h.graph.chans(), nil)
|
||||||
|
// Expect to see them all enabled on the network.
|
||||||
|
h.assertUpdates(
|
||||||
|
h.graph.chans(), true, h.safeDisableTimeout,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "start enabled request disable",
|
||||||
|
startActive: true,
|
||||||
|
startEnabled: true,
|
||||||
|
fn: func(h testHarness) {
|
||||||
|
// Request disables for all channels.
|
||||||
|
h.assertDisables(h.graph.chans(), nil)
|
||||||
|
// Expect to see them all disabled on the network.
|
||||||
|
h.assertUpdates(
|
||||||
|
h.graph.chans(), false, h.safeDisableTimeout,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "request enable already enabled",
|
||||||
|
startActive: true,
|
||||||
|
startEnabled: true,
|
||||||
|
fn: func(h testHarness) {
|
||||||
|
// Request enables for already enabled channels.
|
||||||
|
h.assertEnables(h.graph.chans(), nil)
|
||||||
|
// Manager shouldn't send out any updates.
|
||||||
|
h.assertNoUpdates(h.safeDisableTimeout)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "request disabled already disabled",
|
||||||
|
startActive: false,
|
||||||
|
startEnabled: false,
|
||||||
|
fn: func(h testHarness) {
|
||||||
|
// Request disables for already enabled channels.
|
||||||
|
h.assertDisables(h.graph.chans(), nil)
|
||||||
|
// Manager shouldn't sent out any updates.
|
||||||
|
h.assertNoUpdates(h.safeDisableTimeout)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "detect and disable inactive",
|
||||||
|
startActive: true,
|
||||||
|
startEnabled: true,
|
||||||
|
fn: func(h testHarness) {
|
||||||
|
// Simulate disconnection and have links go inactive.
|
||||||
|
h.markInactive(h.graph.chans())
|
||||||
|
// Should see all channels passively disabled.
|
||||||
|
h.assertUpdates(
|
||||||
|
h.graph.chans(), false, h.safeDisableTimeout,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "quick flap stays active",
|
||||||
|
startActive: true,
|
||||||
|
startEnabled: true,
|
||||||
|
fn: func(h testHarness) {
|
||||||
|
// Simulate disconnection and have links go inactive.
|
||||||
|
h.markInactive(h.graph.chans())
|
||||||
|
// Allow 2 sample intervals to pass, but not long
|
||||||
|
// enough for a disable to occur.
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
// Simulate reconnect by making channels active.
|
||||||
|
h.markActive(h.graph.chans())
|
||||||
|
// Request that all channels be reenabled.
|
||||||
|
h.assertEnables(h.graph.chans(), nil)
|
||||||
|
// Pending disable should have been canceled, and
|
||||||
|
// no updates sent. Channels remain enabled on the
|
||||||
|
// network.
|
||||||
|
h.assertNoUpdates(h.safeDisableTimeout)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no passive enable from becoming active",
|
||||||
|
startActive: false,
|
||||||
|
startEnabled: false,
|
||||||
|
fn: func(h testHarness) {
|
||||||
|
// Simulate reconnect by making channels active.
|
||||||
|
h.markActive(h.graph.chans())
|
||||||
|
// No updates should be sent without explicit enable.
|
||||||
|
h.assertNoUpdates(h.safeDisableTimeout)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "enable inactive channel fails",
|
||||||
|
startActive: false,
|
||||||
|
startEnabled: false,
|
||||||
|
fn: func(h testHarness) {
|
||||||
|
// Request enable of inactive channels, expect error
|
||||||
|
// indicating that channel was not active.
|
||||||
|
h.assertEnables(
|
||||||
|
h.graph.chans(), netann.ErrEnableInactiveChan,
|
||||||
|
)
|
||||||
|
// No updates should be sent as a result of the failure.
|
||||||
|
h.assertNoUpdates(h.safeDisableTimeout)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "enable unknown channel fails",
|
||||||
|
startActive: false,
|
||||||
|
startEnabled: false,
|
||||||
|
fn: func(h testHarness) {
|
||||||
|
// Create channels unknown to the graph.
|
||||||
|
unknownChans := []*channeldb.OpenChannel{
|
||||||
|
createChannel(h.t),
|
||||||
|
createChannel(h.t),
|
||||||
|
createChannel(h.t),
|
||||||
|
}
|
||||||
|
// Request that they be enabled, which should return an
|
||||||
|
// error as the graph doesn't have an edge for them.
|
||||||
|
h.assertEnables(
|
||||||
|
unknownChans, channeldb.ErrEdgeNotFound,
|
||||||
|
)
|
||||||
|
// No updates should be sent as a result of the failure.
|
||||||
|
h.assertNoUpdates(h.safeDisableTimeout)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "disable unknown channel fails",
|
||||||
|
startActive: false,
|
||||||
|
startEnabled: false,
|
||||||
|
fn: func(h testHarness) {
|
||||||
|
// Create channels unknown to the graph.
|
||||||
|
unknownChans := []*channeldb.OpenChannel{
|
||||||
|
createChannel(h.t),
|
||||||
|
createChannel(h.t),
|
||||||
|
createChannel(h.t),
|
||||||
|
}
|
||||||
|
// Request that they be disabled, which should return an
|
||||||
|
// error as the graph doesn't have an edge for them.
|
||||||
|
h.assertDisables(
|
||||||
|
unknownChans, channeldb.ErrEdgeNotFound,
|
||||||
|
)
|
||||||
|
// No updates should be sent as a result of the failure.
|
||||||
|
h.assertNoUpdates(h.safeDisableTimeout)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add new channels",
|
||||||
|
startActive: false,
|
||||||
|
startEnabled: false,
|
||||||
|
fn: func(h testHarness) {
|
||||||
|
// Allow the manager to enter a steady state for the
|
||||||
|
// initial channel set.
|
||||||
|
h.assertNoUpdates(h.safeDisableTimeout)
|
||||||
|
|
||||||
|
// Add a new channels to the graph, but don't yet add
|
||||||
|
// the edge policies. We should see no updates sent
|
||||||
|
// since the manager can't access the policies.
|
||||||
|
newChans := []*channeldb.OpenChannel{
|
||||||
|
createChannel(h.t),
|
||||||
|
createChannel(h.t),
|
||||||
|
createChannel(h.t),
|
||||||
|
}
|
||||||
|
for _, c := range newChans {
|
||||||
|
h.graph.addChannel(c)
|
||||||
|
}
|
||||||
|
h.assertNoUpdates(h.safeDisableTimeout)
|
||||||
|
|
||||||
|
// Check that trying to enable the channel with unknown
|
||||||
|
// edges results in a failure.
|
||||||
|
h.assertEnables(newChans, channeldb.ErrEdgeNotFound)
|
||||||
|
|
||||||
|
// Now, insert edge policies for the channel into the
|
||||||
|
// graph, starting with the channel enabled, and mark
|
||||||
|
// the link active.
|
||||||
|
for _, c := range newChans {
|
||||||
|
info, pol1, pol2 := createEdgePolicies(
|
||||||
|
h.t, c, h.ourPubKey, true,
|
||||||
|
)
|
||||||
|
h.graph.addEdgePolicy(c, info, pol1, pol2)
|
||||||
|
}
|
||||||
|
h.markActive(newChans)
|
||||||
|
|
||||||
|
// We expect no updates to be sent since the channel is
|
||||||
|
// enabled and active.
|
||||||
|
h.assertNoUpdates(h.safeDisableTimeout)
|
||||||
|
|
||||||
|
// Finally, assert that enabling the channel doesn't
|
||||||
|
// return an error now that everything is in place.
|
||||||
|
h.assertEnables(newChans, nil)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "remove channels then disable",
|
||||||
|
startActive: true,
|
||||||
|
startEnabled: true,
|
||||||
|
fn: func(h testHarness) {
|
||||||
|
// Allow the manager to enter a steady state for the
|
||||||
|
// initial channel set.
|
||||||
|
h.assertNoUpdates(h.safeDisableTimeout)
|
||||||
|
|
||||||
|
// Select half of the current channels to remove.
|
||||||
|
channels := h.graph.chans()
|
||||||
|
rmChans := channels[:len(channels)/2]
|
||||||
|
|
||||||
|
// Mark the channel inactive and remove them from the
|
||||||
|
// graph. This should trigger the manager to attempt a
|
||||||
|
// mark the channel disabled, but will unable to do so
|
||||||
|
// because it can't find the edge policies.
|
||||||
|
h.markInactive(rmChans)
|
||||||
|
for _, c := range rmChans {
|
||||||
|
h.graph.removeChannel(c)
|
||||||
|
}
|
||||||
|
h.assertNoUpdates(h.safeDisableTimeout)
|
||||||
|
|
||||||
|
// Check that trying to enable the channel with unknown
|
||||||
|
// edges results in a failure.
|
||||||
|
h.assertDisables(rmChans, channeldb.ErrEdgeNotFound)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "disable channels then remove",
|
||||||
|
startActive: true,
|
||||||
|
startEnabled: true,
|
||||||
|
fn: func(h testHarness) {
|
||||||
|
// Allow the manager to enter a steady state for the
|
||||||
|
// initial channel set.
|
||||||
|
h.assertNoUpdates(h.safeDisableTimeout)
|
||||||
|
|
||||||
|
// Select half of the current channels to remove.
|
||||||
|
channels := h.graph.chans()
|
||||||
|
rmChans := channels[:len(channels)/2]
|
||||||
|
|
||||||
|
// Check that trying to enable the channel with unknown
|
||||||
|
// edges results in a failure.
|
||||||
|
h.assertDisables(rmChans, nil)
|
||||||
|
|
||||||
|
// Since the channels are still in the graph, we expect
|
||||||
|
// these channels to be disabled on the network.
|
||||||
|
h.assertUpdates(rmChans, false, h.safeDisableTimeout)
|
||||||
|
|
||||||
|
// Finally, remove the channels from the graph and
|
||||||
|
// assert no more updates are sent.
|
||||||
|
for _, c := range rmChans {
|
||||||
|
h.graph.removeChannel(c)
|
||||||
|
}
|
||||||
|
h.assertNoUpdates(h.safeDisableTimeout)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestChanStatusManagerStateMachine tests the possible state transitions that
|
||||||
|
// can be taken by the ChanStatusManager.
|
||||||
|
func TestChanStatusManagerStateMachine(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for _, test := range stateMachineTests {
|
||||||
|
tc := test
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const numChannels = 10
|
||||||
|
h := newHarness(
|
||||||
|
t, numChannels, tc.startActive, tc.startEnabled,
|
||||||
|
)
|
||||||
|
defer h.mgr.Stop()
|
||||||
|
|
||||||
|
tc.fn(h)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user