mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 18:10:34 +01:00
b9ef26061a
This commit modifies the netann subsystem to use the peer's alias for ChannelUpdates where appropriate (i.e. in case we are sending the alias to the peer). It also modifies the loadActiveChannels function in the peer package to handle upgrading a channel when the scid-alias feature bit is turned on.
930 lines
25 KiB
Go
930 lines
25 KiB
Go
package netann_test
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
"github.com/lightningnetwork/lnd/keychain"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/lightningnetwork/lnd/netann"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var (
|
|
testKeyLoc = keychain.KeyLocator{Family: keychain.KeyFamilyNodeKey}
|
|
|
|
// testSigBytes specifies a testing signature with the minimal length.
|
|
testSigBytes = []byte{
|
|
0x30, 0x06, 0x02, 0x01, 0x00, 0x02, 0x01, 0x00,
|
|
}
|
|
)
|
|
|
|
// 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[:])
|
|
require.NoError(t, err, "unable to generate random outpoint")
|
|
|
|
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()
|
|
require.NoError(t, err, "unable to generate key pair")
|
|
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: testSigBytes,
|
|
},
|
|
&channeldb.ChannelEdgePolicy{
|
|
ChannelID: channel.ShortChanID().ToUint64(),
|
|
ChannelFlags: dir2,
|
|
LastUpdate: time.Now(),
|
|
SigBytes: testSigBytes,
|
|
}
|
|
}
|
|
|
|
type mockGraph struct {
|
|
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,
|
|
op *wire.OutPoint, private bool) 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: testSigBytes,
|
|
}
|
|
|
|
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))
|
|
channels = append(channels, g.channels...)
|
|
|
|
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()
|
|
require.NoError(t, err, "unable to generate key pair")
|
|
privKeySigner := keychain.NewPrivKeyMessageSigner(privKey, testKeyLoc)
|
|
|
|
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(),
|
|
OurKeyLoc: testKeyLoc,
|
|
MessageSigner: netann.NewNodeSigner(privKeySigner),
|
|
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)
|
|
require.NoError(t, err, "unable to create chan status manager")
|
|
|
|
err = mgr.Start()
|
|
require.NoError(t, err, "unable to start chan status manager")
|
|
|
|
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,
|
|
manual bool) {
|
|
|
|
h.t.Helper()
|
|
|
|
for _, channel := range channels {
|
|
h.assertEnable(channel.FundingOutpoint, expErr, manual)
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
manual bool) {
|
|
|
|
h.t.Helper()
|
|
|
|
for _, channel := range channels {
|
|
h.assertDisable(channel.FundingOutpoint, expErr, manual)
|
|
}
|
|
}
|
|
|
|
// assertAutos requests auto state management for all of the passed channels, and
|
|
// asserts that the errors returned from RequestAuto matches expErr.
|
|
func (h *testHarness) assertAutos(channels []*channeldb.OpenChannel,
|
|
expErr error) {
|
|
|
|
h.t.Helper()
|
|
|
|
for _, channel := range channels {
|
|
h.assertAuto(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,
|
|
manual bool) {
|
|
|
|
h.t.Helper()
|
|
|
|
err := h.mgr.RequestEnable(outpoint, manual)
|
|
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,
|
|
manual bool) {
|
|
|
|
h.t.Helper()
|
|
|
|
err := h.mgr.RequestDisable(outpoint, manual)
|
|
if err != expErr {
|
|
h.t.Fatalf("expected disable error: %v, got %v", expErr, err)
|
|
}
|
|
}
|
|
|
|
// assertAuto requests auto state management for the given outpoint, and asserts
|
|
// that the returned error matches expErr.
|
|
func (h *testHarness) assertAuto(outpoint wire.OutPoint, expErr error) {
|
|
h.t.Helper()
|
|
|
|
err := h.mgr.RequestAuto(outpoint)
|
|
if err != expErr {
|
|
h.t.Fatalf("expected 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, false)
|
|
// 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, false)
|
|
// 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, false)
|
|
// 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, false)
|
|
// 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 re-enabled.
|
|
h.assertEnables(h.graph.chans(), nil, false)
|
|
// 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, false,
|
|
)
|
|
// 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, false,
|
|
)
|
|
// 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, false,
|
|
)
|
|
// 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, false)
|
|
|
|
// 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, false)
|
|
},
|
|
},
|
|
{
|
|
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, false)
|
|
},
|
|
},
|
|
{
|
|
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, false)
|
|
|
|
// 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)
|
|
},
|
|
},
|
|
{
|
|
name: "request manual enable",
|
|
startActive: true,
|
|
startEnabled: false,
|
|
fn: func(h testHarness) {
|
|
// Request manual enables for all channels.
|
|
h.assertEnables(h.graph.chans(), nil, true)
|
|
|
|
// Expect to see them all enabled on the network.
|
|
h.assertUpdates(
|
|
h.graph.chans(), true, h.safeDisableTimeout,
|
|
)
|
|
|
|
// Subsequent request disables with manual = false should succeed.
|
|
h.assertDisables(
|
|
h.graph.chans(), nil, false,
|
|
)
|
|
|
|
// Expect to see them all disabled on the network again.
|
|
h.assertUpdates(
|
|
h.graph.chans(), false, h.safeDisableTimeout,
|
|
)
|
|
},
|
|
},
|
|
{
|
|
name: "request manual disable",
|
|
startActive: true,
|
|
startEnabled: true,
|
|
fn: func(h testHarness) {
|
|
// Request manual disables for all channels.
|
|
h.assertDisables(h.graph.chans(), nil, true)
|
|
|
|
// Expect to see them all disabled on the network.
|
|
h.assertUpdates(
|
|
h.graph.chans(), false, h.safeDisableTimeout,
|
|
)
|
|
|
|
// Request enables with manual = false should fail.
|
|
h.assertEnables(
|
|
h.graph.chans(), netann.ErrEnableManuallyDisabledChan, false,
|
|
)
|
|
|
|
// Request enables with manual = true should succeed.
|
|
h.assertEnables(h.graph.chans(), nil, true)
|
|
|
|
// Expect to see them all enabled on the network again.
|
|
h.assertUpdates(
|
|
h.graph.chans(), true, h.safeDisableTimeout,
|
|
)
|
|
},
|
|
},
|
|
{
|
|
name: "restore auto",
|
|
startActive: true,
|
|
startEnabled: true,
|
|
fn: func(h testHarness) {
|
|
// Request manual disables for all channels.
|
|
h.assertDisables(h.graph.chans(), nil, true)
|
|
|
|
// Expect to see them all disabled on the network.
|
|
h.assertUpdates(
|
|
h.graph.chans(), false, h.safeDisableTimeout,
|
|
)
|
|
|
|
// Request enables with manual = false should fail.
|
|
h.assertEnables(
|
|
h.graph.chans(), netann.ErrEnableManuallyDisabledChan, false,
|
|
)
|
|
|
|
// Request enables with manual = false should succeed after
|
|
// restoring auto state management.
|
|
h.assertAutos(h.graph.chans(), nil)
|
|
h.assertEnables(h.graph.chans(), nil, false)
|
|
|
|
// Expect to see them all enabled on the network again.
|
|
h.assertUpdates(
|
|
h.graph.chans(), true, 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)
|
|
})
|
|
}
|
|
}
|