lnd/htlcswitch/quiescer_test.go
Keagan McClelland ac0c24aa7b
htlcswitch: don't pass pending update counts into quiescer
This change simplifies some of the quiescer responsibilities in
favor of making the link check whether or not it has a clean state
to be able to send or receive an stfu. This change was made on the
basis that the only use the quiescer makes of this information is
to assess that it is or is not zero. Further the difficulty of
checking this condition in the link is barely more burdensome than
selecting the proper information to pass to the quiescer anyway.
2024-11-26 14:13:43 -07:00

374 lines
8.9 KiB
Go

package htlcswitch
import (
"bytes"
"testing"
"time"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
)
var cid = lnwire.ChannelID(bytes.Repeat([]byte{0x00}, 32))
type quiescerTestHarness struct {
quiescer *QuiescerLive
conn <-chan lnwire.Stfu
}
func initQuiescerTestHarness(
channelInitiator lntypes.ChannelParty) *quiescerTestHarness {
conn := make(chan lnwire.Stfu, 1)
harness := &quiescerTestHarness{
conn: conn,
}
quiescer, _ := NewQuiescer(QuiescerCfg{
chanID: cid,
channelInitiator: channelInitiator,
sendMsg: func(msg lnwire.Stfu) error {
conn <- msg
return nil
},
}).(*QuiescerLive)
harness.quiescer = quiescer
return harness
}
// TestQuiescerDoubleRecvInvalid ensures that we get an error response when we
// receive the Stfu message twice during the lifecycle of the quiescer.
func TestQuiescerDoubleRecvInvalid(t *testing.T) {
t.Parallel()
harness := initQuiescerTestHarness(lntypes.Local)
msg := lnwire.Stfu{
ChanID: cid,
Initiator: true,
}
err := harness.quiescer.RecvStfu(msg)
require.NoError(t, err)
err = harness.quiescer.RecvStfu(msg)
require.Error(t, err, ErrStfuAlreadyRcvd)
}
// TestQuiescenceRemoteInit ensures that we can successfully traverse the state
// graph of quiescence beginning with the Remote party initiating quiescence.
func TestQuiescenceRemoteInit(t *testing.T) {
t.Parallel()
harness := initQuiescerTestHarness(lntypes.Local)
msg := lnwire.Stfu{
ChanID: cid,
Initiator: true,
}
err := harness.quiescer.RecvStfu(msg)
require.NoError(t, err)
err = harness.quiescer.SendOwedStfu()
require.NoError(t, err)
select {
case msg := <-harness.conn:
require.False(t, msg.Initiator)
default:
t.Fatalf("stfu not sent when expected")
}
}
func TestQuiescenceLocalInit(t *testing.T) {
t.Parallel()
harness := initQuiescerTestHarness(lntypes.Local)
msg := lnwire.Stfu{
ChanID: cid,
Initiator: true,
}
stfuReq, stfuRes := fn.NewReq[fn.Unit, fn.Result[lntypes.ChannelParty]](
fn.Unit{},
)
harness.quiescer.InitStfu(stfuReq)
err := harness.quiescer.SendOwedStfu()
require.NoError(t, err)
select {
case msg := <-harness.conn:
require.True(t, msg.Initiator)
default:
t.Fatalf("stfu not sent when expected")
}
err = harness.quiescer.RecvStfu(msg)
require.NoError(t, err)
select {
case party := <-stfuRes:
require.Equal(t, fn.Ok(lntypes.Local), party)
default:
t.Fatalf("quiescence request not resolved")
}
}
// TestQuiescenceInitiator ensures that the quiescenceInitiator is the Remote
// party when we have a receive first traversal of the quiescer's state graph.
func TestQuiescenceInitiator(t *testing.T) {
t.Parallel()
// Remote Initiated
harness := initQuiescerTestHarness(lntypes.Local)
require.True(t, harness.quiescer.QuiescenceInitiator().IsErr())
// Receive
msg := lnwire.Stfu{
ChanID: cid,
Initiator: true,
}
require.NoError(t, harness.quiescer.RecvStfu(msg))
require.True(t, harness.quiescer.QuiescenceInitiator().IsErr())
// Send
require.NoError(t, harness.quiescer.SendOwedStfu())
require.Equal(
t, harness.quiescer.QuiescenceInitiator(),
fn.Ok(lntypes.Remote),
)
// Local Initiated
harness = initQuiescerTestHarness(lntypes.Local)
require.True(t, harness.quiescer.quiescenceInitiator().IsErr())
req, res := fn.NewReq[fn.Unit, fn.Result[lntypes.ChannelParty]](
fn.Unit{},
)
harness.quiescer.initStfu(req)
req2, res2 := fn.NewReq[fn.Unit, fn.Result[lntypes.ChannelParty]](
fn.Unit{},
)
harness.quiescer.initStfu(req2)
select {
case initiator := <-res2:
require.True(t, initiator.IsErr())
default:
t.Fatal("quiescence request not resolved")
}
require.NoError(
t, harness.quiescer.sendOwedStfu(),
)
require.True(t, harness.quiescer.quiescenceInitiator().IsErr())
msg = lnwire.Stfu{
ChanID: cid,
Initiator: false,
}
require.NoError(t, harness.quiescer.recvStfu(msg))
require.True(t, harness.quiescer.quiescenceInitiator().IsOk())
select {
case initiator := <-res:
require.Equal(t, fn.Ok(lntypes.Local), initiator)
default:
t.Fatal("quiescence request not resolved")
}
}
// TestQuiescenceCantReceiveUpdatesAfterStfu tests that we can receive channel
// updates prior to but not after we receive Stfu.
func TestQuiescenceCantReceiveUpdatesAfterStfu(t *testing.T) {
t.Parallel()
harness := initQuiescerTestHarness(lntypes.Local)
require.True(t, harness.quiescer.CanRecvUpdates())
msg := lnwire.Stfu{
ChanID: cid,
Initiator: true,
}
require.NoError(t, harness.quiescer.RecvStfu(msg))
require.False(t, harness.quiescer.CanRecvUpdates())
}
// TestQuiescenceCantSendUpdatesAfterStfu tests that we can send channel updates
// prior to but not after we send Stfu.
func TestQuiescenceCantSendUpdatesAfterStfu(t *testing.T) {
t.Parallel()
harness := initQuiescerTestHarness(lntypes.Local)
require.True(t, harness.quiescer.CanSendUpdates())
msg := lnwire.Stfu{
ChanID: cid,
Initiator: true,
}
err := harness.quiescer.RecvStfu(msg)
require.NoError(t, err)
err = harness.quiescer.SendOwedStfu()
require.NoError(t, err)
require.False(t, harness.quiescer.CanSendUpdates())
}
// TestQuiescenceStfuNotNeededAfterRecv tests that after we receive an Stfu we
// do not needStfu either before or after receiving it if we do not initiate
// quiescence.
func TestQuiescenceStfuNotNeededAfterRecv(t *testing.T) {
t.Parallel()
harness := initQuiescerTestHarness(lntypes.Local)
msg := lnwire.Stfu{
ChanID: cid,
Initiator: true,
}
require.False(t, harness.quiescer.NeedStfu())
require.NoError(t, harness.quiescer.RecvStfu(msg))
require.False(t, harness.quiescer.NeedStfu())
}
// TestQuiescenceInappropriateMakeStfuReturnsErr ensures that we cannot call
// makeStfu at times when it would be a protocol violation to send it.
func TestQuiescenceInappropriateMakeStfuReturnsErr(t *testing.T) {
t.Parallel()
harness := initQuiescerTestHarness(lntypes.Local)
msg := lnwire.Stfu{
ChanID: cid,
Initiator: true,
}
require.NoError(t, harness.quiescer.RecvStfu(msg))
require.True(t, harness.quiescer.MakeStfu().IsOk())
require.NoError(t, harness.quiescer.SendOwedStfu())
require.True(t, harness.quiescer.MakeStfu().IsErr())
}
// TestQuiescerTieBreaker ensures that if both parties attempt to claim the
// initiator role that the result of the negotiation breaks the tie using the
// channel initiator.
func TestQuiescerTieBreaker(t *testing.T) {
t.Parallel()
for _, initiator := range []lntypes.ChannelParty{
lntypes.Local, lntypes.Remote,
} {
harness := initQuiescerTestHarness(initiator)
msg := lnwire.Stfu{
ChanID: cid,
Initiator: true,
}
req, res := fn.NewReq[fn.Unit, fn.Result[lntypes.ChannelParty]](
fn.Unit{},
)
harness.quiescer.InitStfu(req)
require.NoError(t, harness.quiescer.RecvStfu(msg))
require.NoError(t, harness.quiescer.SendOwedStfu())
select {
case party := <-res:
require.Equal(t, fn.Ok(initiator), party)
default:
t.Fatal("quiescence party unavailable")
}
}
}
// TestQuiescerResume ensures that the hooks that are attached to the quiescer
// are called when we call the resume method and no earlier.
func TestQuiescerResume(t *testing.T) {
t.Parallel()
harness := initQuiescerTestHarness(lntypes.Local)
msg := lnwire.Stfu{
ChanID: cid,
Initiator: true,
}
require.NoError(t, harness.quiescer.RecvStfu(msg))
require.NoError(t, harness.quiescer.SendOwedStfu())
require.True(t, harness.quiescer.IsQuiescent())
var resumeHooksCalled = false
harness.quiescer.OnResume(func() {
resumeHooksCalled = true
})
require.False(t, resumeHooksCalled)
harness.quiescer.Resume()
require.True(t, resumeHooksCalled)
require.False(t, harness.quiescer.IsQuiescent())
}
func TestQuiescerTimeoutTriggers(t *testing.T) {
t.Parallel()
harness := initQuiescerTestHarness(lntypes.Local)
msg := lnwire.Stfu{
ChanID: cid,
Initiator: true,
}
timeoutGate := make(chan struct{})
harness.quiescer.cfg.timeoutDuration = time.Second
harness.quiescer.cfg.onTimeout = func() { close(timeoutGate) }
err := harness.quiescer.RecvStfu(msg)
require.NoError(t, err)
err = harness.quiescer.SendOwedStfu()
require.NoError(t, err)
select {
case <-timeoutGate:
case <-time.After(2 * harness.quiescer.cfg.timeoutDuration):
t.Fatal("quiescence timeout did not trigger")
}
}
func TestQuiescerTimeoutAborts(t *testing.T) {
t.Parallel()
harness := initQuiescerTestHarness(lntypes.Local)
msg := lnwire.Stfu{
ChanID: cid,
Initiator: true,
}
timeoutGate := make(chan struct{})
harness.quiescer.cfg.timeoutDuration = time.Second
harness.quiescer.cfg.onTimeout = func() { close(timeoutGate) }
err := harness.quiescer.RecvStfu(msg)
require.NoError(t, err)
err = harness.quiescer.SendOwedStfu()
require.NoError(t, err)
harness.quiescer.Resume()
select {
case <-timeoutGate:
t.Fatal("quiescence timeout triggered despite being resumed")
case <-time.After(2 * harness.quiescer.cfg.timeoutDuration):
}
}