circuitbreaker/process_test.go

563 lines
12 KiB
Go
Raw Normal View History

2020-10-13 16:56:51 +02:00
package main
import (
"context"
"testing"
"time"
2023-01-03 13:01:26 +01:00
"github.com/lightningnetwork/lnd/routing/route"
2020-10-13 16:56:51 +02:00
"github.com/stretchr/testify/require"
2023-02-03 13:10:57 +01:00
"go.uber.org/zap/zaptest"
2020-10-13 16:56:51 +02:00
)
func TestProcess(t *testing.T) {
2022-10-13 14:06:50 +02:00
defer Timeout()()
t.Run("settle", func(t *testing.T) {
testProcess(t, resolveEventSettle)
})
t.Run("forward fail", func(t *testing.T) {
testProcess(t, resolveEventForwardFail)
})
t.Run("link fail", func(t *testing.T) {
testProcess(t, resolveEventLinkFail)
})
}
type resolveEvent int
const (
resolveEventSettle resolveEvent = iota
resolveEventForwardFail
resolveEventLinkFail
)
func testProcess(t *testing.T, event resolveEvent) {
client := newLndclientMock(testChannels, nil)
2020-10-13 16:56:51 +02:00
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db, cleanup := setupTestDb(t, defaultFwdHistoryLimit)
2023-08-31 11:50:44 +02:00
defer cleanup()
2023-02-03 13:15:09 +01:00
log := zaptest.NewLogger(t).Sugar()
2023-01-03 13:01:26 +01:00
cfg := &Limits{
PerPeer: map[route.Vertex]Limit{
{2}: {
MaxHourlyRate: 60,
MaxPending: 1,
},
{3}: {
MaxHourlyRate: 60,
MaxPending: 1,
},
},
}
2023-08-31 11:50:44 +02:00
p := NewProcess(client, log, cfg, db)
2022-11-29 14:54:39 +01:00
resolved := make(chan struct{})
p.resolvedCallback = func() {
close(resolved)
}
2020-10-13 16:56:51 +02:00
exit := make(chan error)
go func() {
2023-01-03 13:01:26 +01:00
exit <- p.Run(ctx)
2020-10-13 16:56:51 +02:00
}()
2023-01-03 13:01:26 +01:00
key := circuitKey{
channel: 2,
htlc: 5,
2020-10-13 16:56:51 +02:00
}
2023-01-03 13:01:26 +01:00
client.htlcInterceptorRequests <- &interceptedEvent{
circuitKey: key,
2020-10-13 16:56:51 +02:00
}
resp := <-client.htlcInterceptorResponses
2023-01-03 13:01:26 +01:00
require.True(t, resp.resume)
2020-10-13 16:56:51 +02:00
2023-01-03 13:01:26 +01:00
htlcEvent := &resolvedEvent{
incomingCircuitKey: key,
outgoingCircuitKey: outgoingKey,
2020-10-13 16:56:51 +02:00
}
2022-10-13 14:06:50 +02:00
switch event {
case resolveEventForwardFail:
2023-01-03 13:01:26 +01:00
htlcEvent.settled = false
2022-10-13 14:06:50 +02:00
case resolveEventLinkFail:
2023-01-03 13:01:26 +01:00
htlcEvent.settled = false
2022-10-13 14:06:50 +02:00
case resolveEventSettle:
2023-01-03 13:01:26 +01:00
htlcEvent.settled = true
2022-10-13 14:06:50 +02:00
}
client.htlcEvents <- htlcEvent
2022-10-13 13:55:44 +02:00
<-resolved
2020-10-13 16:56:51 +02:00
cancel()
2022-11-29 11:42:18 +01:00
require.ErrorIs(t, <-exit, context.Canceled)
2020-10-13 16:56:51 +02:00
}
2021-09-11 13:51:44 +02:00
2022-11-29 17:44:55 +01:00
func TestLimits(t *testing.T) {
for _, mode := range []Mode{ModeFail, ModeQueue, ModeQueuePeerInitiated} {
t.Run(mode.String(), func(t *testing.T) {
t.Run("rate limit", func(t *testing.T) { testRateLimit(t, mode) })
t.Run("max pending", func(t *testing.T) { testMaxPending(t, mode) })
})
}
}
func testRateLimit(t *testing.T, mode Mode) {
2022-10-13 14:06:50 +02:00
defer Timeout()()
db, cleanup := setupTestDb(t, defaultFwdHistoryLimit)
2023-08-31 11:50:44 +02:00
defer cleanup()
2023-01-03 13:01:26 +01:00
cfg := &Limits{
PerPeer: map[route.Vertex]Limit{
{2}: {
MaxHourlyRate: 1800,
Mode: mode,
},
{3}: {
MaxHourlyRate: 1800,
Mode: mode,
},
2021-09-11 13:51:44 +02:00
},
}
client := newLndclientMock(testChannels, nil)
2021-09-11 13:51:44 +02:00
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
2023-02-03 13:15:09 +01:00
log := zaptest.NewLogger(t).Sugar()
2023-01-03 13:01:26 +01:00
2023-08-31 11:50:44 +02:00
p := NewProcess(client, log, cfg, db)
2023-01-03 13:01:26 +01:00
p.burstSize = 2
2022-11-29 14:54:39 +01:00
2021-09-11 13:51:44 +02:00
exit := make(chan error)
go func() {
2023-01-03 13:01:26 +01:00
exit <- p.Run(ctx)
2021-09-11 13:51:44 +02:00
}()
2022-11-29 17:44:55 +01:00
var chanId uint64 = 2
if mode == ModeQueuePeerInitiated {
// We are the initiator of the channel. Not queueing is expected in this
// mode.
chanId = 3
}
2023-01-03 13:01:26 +01:00
key := circuitKey{
channel: chanId,
htlc: 5,
2021-09-11 13:51:44 +02:00
}
2023-01-03 13:01:26 +01:00
interceptReq := &interceptedEvent{
circuitKey: key,
2021-09-11 13:51:44 +02:00
}
// First htlc accepted.
client.htlcInterceptorRequests <- interceptReq
resp := <-client.htlcInterceptorResponses
2023-01-03 13:01:26 +01:00
require.True(t, resp.resume)
2021-09-11 13:51:44 +02:00
// Second htlc right after is also accepted because of burst size 2.
2023-01-03 13:01:26 +01:00
interceptReq.circuitKey.htlc++
2021-09-11 13:51:44 +02:00
client.htlcInterceptorRequests <- interceptReq
resp = <-client.htlcInterceptorResponses
2023-01-03 13:01:26 +01:00
require.True(t, resp.resume)
2021-09-11 13:51:44 +02:00
2022-11-29 17:44:55 +01:00
// Third htlc again right after should hit the rate limit.
2023-01-03 13:01:26 +01:00
interceptReq.circuitKey.htlc++
2021-09-11 13:51:44 +02:00
client.htlcInterceptorRequests <- interceptReq
2022-11-29 17:44:55 +01:00
interceptStart := time.Now()
2021-09-11 13:51:44 +02:00
resp = <-client.htlcInterceptorResponses
2022-11-29 17:44:55 +01:00
if mode == ModeQueue {
2023-01-03 13:01:26 +01:00
require.True(t, resp.resume)
2022-11-29 17:44:55 +01:00
require.GreaterOrEqual(t, time.Since(interceptStart), time.Second)
} else {
2023-01-03 13:01:26 +01:00
require.False(t, resp.resume)
2022-11-29 15:18:13 +01:00
2023-01-03 13:01:26 +01:00
htlcEvent := &resolvedEvent{
incomingCircuitKey: key,
outgoingCircuitKey: outgoingKey,
settled: false,
2022-11-29 17:44:55 +01:00
}
2022-11-29 15:18:13 +01:00
2022-11-29 17:44:55 +01:00
client.htlcEvents <- htlcEvent
// Allow some time for the peer controller to process the failed forward
// event.
time.Sleep(time.Second)
}
2022-11-29 15:18:13 +01:00
2021-09-11 13:51:44 +02:00
cancel()
2022-11-29 11:42:18 +01:00
require.ErrorIs(t, <-exit, context.Canceled)
2021-09-11 13:51:44 +02:00
}
2022-12-02 14:36:33 +01:00
2022-11-29 17:44:55 +01:00
func testMaxPending(t *testing.T, mode Mode) {
2022-12-02 14:36:33 +01:00
defer Timeout()()
db, cleanup := setupTestDb(t, defaultFwdHistoryLimit)
2023-08-31 11:50:44 +02:00
defer cleanup()
2023-01-03 13:01:26 +01:00
cfg := &Limits{
PerPeer: map[route.Vertex]Limit{
{2}: {
MaxHourlyRate: 60,
MaxPending: 1,
Mode: mode,
},
{3}: {
MaxHourlyRate: 60,
MaxPending: 1,
Mode: mode,
},
2022-12-02 14:36:33 +01:00
},
}
client := newLndclientMock(testChannels, nil)
2022-12-02 14:36:33 +01:00
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
2023-02-03 13:15:09 +01:00
log := zaptest.NewLogger(t).Sugar()
2023-01-03 13:01:26 +01:00
2023-08-31 11:50:44 +02:00
p := NewProcess(client, log, cfg, db)
2023-01-03 13:01:26 +01:00
p.burstSize = 2
2022-12-02 14:36:33 +01:00
exit := make(chan error)
go func() {
2023-01-03 13:01:26 +01:00
exit <- p.Run(ctx)
2022-12-02 14:36:33 +01:00
}()
2022-11-29 17:44:55 +01:00
var chanId uint64 = 2
if mode == ModeQueuePeerInitiated {
// We are the initiator of the channel. Not queueing is expected in this
// mode.
chanId = 3
}
2023-01-03 13:01:26 +01:00
key := circuitKey{
channel: chanId,
htlc: 5,
2022-12-02 14:36:33 +01:00
}
2023-01-03 13:01:26 +01:00
interceptReq := &interceptedEvent{
circuitKey: key,
2022-12-02 14:36:33 +01:00
}
// First htlc accepted.
client.htlcInterceptorRequests <- interceptReq
resp := <-client.htlcInterceptorResponses
2023-01-03 13:01:26 +01:00
require.True(t, resp.resume)
2022-12-02 14:36:33 +01:00
2022-11-29 17:44:55 +01:00
// Second htlc should be hitting the max pending htlcs limit.
2023-01-03 13:01:26 +01:00
interceptReq.circuitKey.htlc++
2022-12-02 14:36:33 +01:00
client.htlcInterceptorRequests <- interceptReq
2022-11-29 17:44:55 +01:00
if mode == ModeQueue {
select {
case <-client.htlcInterceptorResponses:
require.Fail(t, "unexpected response")
case <-time.After(time.Second):
}
} else {
resp = <-client.htlcInterceptorResponses
2023-01-03 13:01:26 +01:00
require.False(t, resp.resume)
2022-11-29 17:44:55 +01:00
}
2022-12-02 14:36:33 +01:00
cancel()
require.ErrorIs(t, <-exit, context.Canceled)
}
2023-02-03 13:10:57 +01:00
func TestNewPeer(t *testing.T) {
// Initialize lnd with test channels.
client := newLndclientMock(testChannels, nil)
2023-02-03 13:10:57 +01:00
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db, cleanup := setupTestDb(t, defaultFwdHistoryLimit)
2023-08-31 11:50:44 +02:00
defer cleanup()
2023-02-03 13:10:57 +01:00
log := zaptest.NewLogger(t).Sugar()
cfg := &Limits{}
2023-08-31 11:50:44 +02:00
p := NewProcess(client, log, cfg, db)
2023-02-03 13:10:57 +01:00
// Setup quick peer refresh.
p.peerRefreshInterval = 100 * time.Millisecond
exit := make(chan error)
go func() {
exit <- p.Run(ctx)
}()
state, err := p.getRateCounters(ctx)
require.NoError(t, err)
require.Len(t, state, 3)
2023-02-03 13:10:57 +01:00
// Add a new peer.
log.Infow("Add a new peer")
client.channels[100] = &channel{peer: route.Vertex{100}}
// Wait for the peer to be reported.
require.Eventually(t, func() bool {
state, err := p.getRateCounters(ctx)
require.NoError(t, err)
return len(state) == 4
2023-02-03 13:10:57 +01:00
}, time.Second, 100*time.Millisecond)
cancel()
require.ErrorIs(t, <-exit, context.Canceled)
}
2023-02-13 14:28:12 +01:00
func TestBlocked(t *testing.T) {
defer Timeout()()
db, cleanup := setupTestDb(t, defaultFwdHistoryLimit)
2023-08-31 11:50:44 +02:00
defer cleanup()
2023-02-13 14:28:12 +01:00
cfg := &Limits{
PerPeer: map[route.Vertex]Limit{
{2}: {
MaxHourlyRate: 1800,
Mode: ModeBlock,
},
},
}
client := newLndclientMock(testChannels, nil)
2023-02-13 14:28:12 +01:00
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
log := zaptest.NewLogger(t).Sugar()
2023-08-31 11:50:44 +02:00
p := NewProcess(client, log, cfg, db)
2023-02-13 14:28:12 +01:00
exit := make(chan error)
go func() {
exit <- p.Run(ctx)
}()
var chanId uint64 = 2
key := circuitKey{
channel: chanId,
htlc: 5,
}
interceptReq := &interceptedEvent{
circuitKey: key,
}
// Htlc blocked.
client.htlcInterceptorRequests <- interceptReq
resp := <-client.htlcInterceptorResponses
require.False(t, resp.resume)
cancel()
require.ErrorIs(t, <-exit, context.Canceled)
}
// TestChannelNotFound tests that we'll successfully exit when we cannot lookup the
// channel that a htlc belongs to.
func TestChannelNotFound(t *testing.T) {
client := newLndclientMock(testChannels, nil)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db, cleanup := setupTestDb(t, defaultFwdHistoryLimit)
defer cleanup()
log := zaptest.NewLogger(t).Sugar()
cfg := &Limits{}
p := NewProcess(client, log, cfg, db)
exit := make(chan error)
go func() {
exit <- p.Run(ctx)
}()
// Next, send a htlc that is from an unknown channel.
key := circuitKey{
channel: 99,
htlc: 4,
}
client.htlcInterceptorRequests <- &interceptedEvent{
circuitKey: key,
}
select {
case err := <-exit:
require.ErrorIs(t, err, errChannelNotFound)
case <-time.After(time.Second * 10):
t.Fatalf("timeout on process error")
}
}
// TestOutgoingChannelNotFound tests the case where the outgoing channel for a htlc is
// not found in two cases:
// 1. The HTLC was settled: the channel must exist, so we fail if it's not found
// 2. The HTLC was failed: the outgoing channel could be bogus, so we handle the error
func TestOutgoingChannelNotFound(t *testing.T) {
tests := []struct {
name string
settled bool
outgoingFound bool
err error
}{
{
name: "outgoing found, settled",
settled: true,
outgoingFound: true,
},
{
name: "outgoing found, not settled",
settled: false,
outgoingFound: true,
},
{
name: "outgoing not found, settled",
settled: true,
outgoingFound: false,
err: errChannelNotFound,
},
{
name: "outgoing not found, not settled",
settled: false,
outgoingFound: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
testLookupOutgoingChannel(
t, test.settled, test.outgoingFound, test.err,
)
})
}
}
func testLookupOutgoingChannel(t *testing.T, settled, outgoingFound bool,
exitErr error) {
client := newLndclientMock(testChannels, nil)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db, cleanup := setupTestDb(t, defaultFwdHistoryLimit)
defer cleanup()
log := zaptest.NewLogger(t).Sugar()
cfg := &Limits{}
p := NewProcess(client, log, cfg, db)
resolved := make(chan struct{})
p.resolvedCallback = func() {
close(resolved)
}
exit := make(chan error)
go func() {
exit <- p.Run(ctx)
}()
// Send a htlc with a known incoming channel.
key := circuitKey{
channel: 2,
htlc: 5,
}
client.htlcInterceptorRequests <- &interceptedEvent{
circuitKey: key,
}
resp := <-client.htlcInterceptorResponses
require.True(t, resp.resume)
// Set the outgoing channel based on whether we want it to be found by our
// lookup or not.
outgoingKey := outgoingKey
if !outgoingFound {
outgoingKey.channel = 9999
}
htlcEvent := &resolvedEvent{
incomingCircuitKey: key,
outgoingCircuitKey: outgoingKey,
settled: settled,
}
client.htlcEvents <- htlcEvent
// If expected, assert that we exit with an error, otherwise ensure that the htlc
// is settled and we exit cleanly.
if exitErr != nil {
require.ErrorIs(t, <-exit, exitErr)
} else {
<-resolved
cancel()
require.ErrorIs(t, <-exit, context.Canceled)
}
}
// TestClosedChannelHtlc tests that we can handle intercepted htlcs that are associated
// with closed channels.
func TestClosedChannelHtlc(t *testing.T) {
// Initialize lnd with a closed channel.
var testClosedChannels = map[uint64]*channel{
5: {peer: route.Vertex{2}},
}
client := newLndclientMock(testChannels, testClosedChannels)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
db, cleanup := setupTestDb(t, defaultFwdHistoryLimit)
defer cleanup()
log := zaptest.NewLogger(t).Sugar()
cfg := &Limits{}
p := NewProcess(client, log, cfg, db)
exit := make(chan error)
go func() {
exit <- p.Run(ctx)
}()
// Send a htlc that is from a closed channel, it should be given the go-ahead to
// resume.
key := circuitKey{
channel: 5,
htlc: 3,
}
client.htlcInterceptorRequests <- &interceptedEvent{
circuitKey: key,
}
resp := <-client.htlcInterceptorResponses
require.Equal(t, key, resp.key)
cancel()
require.ErrorIs(t, <-exit, context.Canceled)
}