lnd.xprv/routing/missioncontrol_test.go

207 lines
4.7 KiB
Go

package routing
import (
"io/ioutil"
"os"
"testing"
"time"
"github.com/coreos/bbolt"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)
var (
mcTestRoute = &route.Route{
SourcePubKey: route.Vertex{10},
Hops: []*route.Hop{
{
ChannelID: 1,
PubKeyBytes: route.Vertex{11},
AmtToForward: 1000,
LegacyPayload: true,
},
{
ChannelID: 2,
PubKeyBytes: route.Vertex{12},
LegacyPayload: true,
},
},
}
mcTestTime = time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC)
mcTestNode1 = mcTestRoute.Hops[0].PubKeyBytes
mcTestNode2 = mcTestRoute.Hops[1].PubKeyBytes
testPenaltyHalfLife = 30 * time.Minute
testAprioriHopProbability = 0.8
)
type mcTestContext struct {
t *testing.T
mc *MissionControl
now time.Time
db *bbolt.DB
dbPath string
pid uint64
}
func createMcTestContext(t *testing.T) *mcTestContext {
ctx := &mcTestContext{
t: t,
now: mcTestTime,
}
file, err := ioutil.TempFile("", "*.db")
if err != nil {
t.Fatal(err)
}
ctx.dbPath = file.Name()
ctx.db, err = bbolt.Open(ctx.dbPath, 0600, nil)
if err != nil {
t.Fatal(err)
}
ctx.restartMc()
return ctx
}
// restartMc creates a new instances of mission control on the same database.
func (ctx *mcTestContext) restartMc() {
mc, err := NewMissionControl(
ctx.db,
&MissionControlConfig{
PenaltyHalfLife: testPenaltyHalfLife,
AprioriHopProbability: testAprioriHopProbability,
},
)
if err != nil {
ctx.t.Fatal(err)
}
mc.now = func() time.Time { return ctx.now }
ctx.mc = mc
}
// cleanup closes the database and removes the temp file.
func (ctx *mcTestContext) cleanup() {
ctx.db.Close()
os.Remove(ctx.dbPath)
}
// Assert that mission control returns a probability for an edge.
func (ctx *mcTestContext) expectP(amt lnwire.MilliSatoshi, expected float64) {
ctx.t.Helper()
p := ctx.mc.GetProbability(mcTestNode1, mcTestNode2, amt)
if p != expected {
ctx.t.Fatalf("expected probability %v but got %v", expected, p)
}
}
// reportFailure reports a failure by using a test route.
func (ctx *mcTestContext) reportFailure(amt lnwire.MilliSatoshi,
failure lnwire.FailureMessage) {
mcTestRoute.Hops[0].AmtToForward = amt
errorSourceIdx := 1
ctx.mc.ReportPaymentFail(
ctx.pid, mcTestRoute, &errorSourceIdx, failure,
)
}
// reportSuccess reports a success by using a test route.
func (ctx *mcTestContext) reportSuccess() {
err := ctx.mc.ReportPaymentSuccess(ctx.pid, mcTestRoute)
if err != nil {
ctx.t.Fatal(err)
}
ctx.pid++
}
// TestMissionControl tests mission control probability estimation.
func TestMissionControl(t *testing.T) {
ctx := createMcTestContext(t)
defer ctx.cleanup()
ctx.now = testTime
testTime := time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC)
// Initial probability is expected to be 1.
ctx.expectP(1000, 0.8)
// Expect probability to be zero after reporting the edge as failed.
ctx.reportFailure(1000, lnwire.NewTemporaryChannelFailure(nil))
ctx.expectP(1000, 0)
// As we reported with a min penalization amt, a lower amt than reported
// should be unaffected.
ctx.expectP(500, testAprioriHopProbability)
// Edge decay started.
ctx.now = testTime.Add(30 * time.Minute)
ctx.expectP(1000, 0.4)
// Edge fails again, this time without a min penalization amt. The edge
// should be penalized regardless of amount.
ctx.reportFailure(0, lnwire.NewTemporaryChannelFailure(nil))
ctx.expectP(1000, 0)
ctx.expectP(500, 0)
// Edge decay started.
ctx.now = testTime.Add(60 * time.Minute)
ctx.expectP(1000, 0.4)
// Restart mission control to test persistence.
ctx.restartMc()
ctx.expectP(1000, 0.4)
// A node level failure should bring probability of every channel back
// to zero.
ctx.reportFailure(0, lnwire.NewExpiryTooSoon(lnwire.ChannelUpdate{}))
ctx.expectP(1000, 0)
// Check whether history snapshot looks sane.
history := ctx.mc.GetHistorySnapshot()
if len(history.Nodes) != 1 {
t.Fatalf("unexpected number of nodes: expected 1 got %v",
len(history.Nodes))
}
if len(history.Pairs) != 2 {
t.Fatalf("expected 2 pairs, but got %v", len(history.Pairs))
}
// Test reporting a success.
ctx.reportSuccess()
}
// TestMissionControlChannelUpdate tests that the first channel update is not
// penalizing the channel yet.
func TestMissionControlChannelUpdate(t *testing.T) {
ctx := createMcTestContext(t)
// Report a policy related failure. Because it is the first, we don't
// expect a penalty.
ctx.reportFailure(
0, lnwire.NewFeeInsufficient(0, lnwire.ChannelUpdate{}),
)
ctx.expectP(0, testAprioriHopProbability)
// Report another failure for the same channel. We expect it to be
// pruned.
ctx.reportFailure(
0, lnwire.NewFeeInsufficient(0, lnwire.ChannelUpdate{}),
)
ctx.expectP(0, 0)
}