github.com/decred/dcrlnd@v0.7.6/routing/missioncontrol_test.go (about) 1 package routing 2 3 import ( 4 "io/ioutil" 5 "os" 6 "testing" 7 "time" 8 9 "github.com/decred/dcrlnd/kvdb" 10 "github.com/decred/dcrlnd/lnwire" 11 "github.com/decred/dcrlnd/routing/route" 12 "github.com/stretchr/testify/require" 13 ) 14 15 var ( 16 mcTestRoute = &route.Route{ 17 SourcePubKey: mcTestSelf, 18 Hops: []*route.Hop{ 19 { 20 ChannelID: 1, 21 PubKeyBytes: route.Vertex{11}, 22 AmtToForward: 1000, 23 LegacyPayload: true, 24 }, 25 { 26 ChannelID: 2, 27 PubKeyBytes: route.Vertex{12}, 28 LegacyPayload: true, 29 }, 30 }, 31 } 32 33 mcTestTime = time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC) 34 mcTestSelf = route.Vertex{10} 35 mcTestNode1 = mcTestRoute.Hops[0].PubKeyBytes 36 mcTestNode2 = mcTestRoute.Hops[1].PubKeyBytes 37 38 testPenaltyHalfLife = 30 * time.Minute 39 testAprioriHopProbability = 0.9 40 testAprioriWeight = 0.5 41 ) 42 43 type mcTestContext struct { 44 t *testing.T 45 mc *MissionControl 46 now time.Time 47 48 db kvdb.Backend 49 dbPath string 50 51 pid uint64 52 } 53 54 func createMcTestContext(t *testing.T) *mcTestContext { 55 ctx := &mcTestContext{ 56 t: t, 57 now: mcTestTime, 58 } 59 60 file, err := ioutil.TempFile("", "*.db") 61 if err != nil { 62 t.Fatal(err) 63 } 64 65 ctx.dbPath = file.Name() 66 67 ctx.db, err = kvdb.Open( 68 kvdb.BoltBackendName, ctx.dbPath, true, kvdb.DefaultDBTimeout, 69 ) 70 if err != nil { 71 t.Fatal(err) 72 } 73 74 ctx.restartMc() 75 76 return ctx 77 } 78 79 // restartMc creates a new instances of mission control on the same database. 80 func (ctx *mcTestContext) restartMc() { 81 // Since we don't run a timer to store results in unit tests, we store 82 // them here before fetching back everything in NewMissionControl. 83 if ctx.mc != nil { 84 require.NoError(ctx.t, ctx.mc.store.storeResults()) 85 } 86 87 mc, err := NewMissionControl( 88 ctx.db, mcTestSelf, 89 &MissionControlConfig{ 90 ProbabilityEstimatorCfg: ProbabilityEstimatorCfg{ 91 PenaltyHalfLife: testPenaltyHalfLife, 92 AprioriHopProbability: testAprioriHopProbability, 93 AprioriWeight: testAprioriWeight, 94 }, 95 }, 96 ) 97 if err != nil { 98 ctx.t.Fatal(err) 99 } 100 101 mc.now = func() time.Time { return ctx.now } 102 ctx.mc = mc 103 } 104 105 // cleanup closes the database and removes the temp file. 106 func (ctx *mcTestContext) cleanup() { 107 ctx.db.Close() 108 os.Remove(ctx.dbPath) 109 } 110 111 // Assert that mission control returns a probability for an edge. 112 func (ctx *mcTestContext) expectP(amt lnwire.MilliAtom, expected float64) { 113 ctx.t.Helper() 114 115 p := ctx.mc.GetProbability(mcTestNode1, mcTestNode2, amt) 116 if p != expected { 117 ctx.t.Fatalf("expected probability %v but got %v", expected, p) 118 } 119 } 120 121 // reportFailure reports a failure by using a test route. 122 func (ctx *mcTestContext) reportFailure(amt lnwire.MilliAtom, 123 failure lnwire.FailureMessage) { 124 125 mcTestRoute.Hops[0].AmtToForward = amt 126 127 errorSourceIdx := 1 128 ctx.mc.ReportPaymentFail( 129 ctx.pid, mcTestRoute, &errorSourceIdx, failure, 130 ) 131 } 132 133 // reportSuccess reports a success by using a test route. 134 func (ctx *mcTestContext) reportSuccess() { 135 err := ctx.mc.ReportPaymentSuccess(ctx.pid, mcTestRoute) 136 if err != nil { 137 ctx.t.Fatal(err) 138 } 139 140 ctx.pid++ 141 } 142 143 // TestMissionControl tests mission control probability estimation. 144 func TestMissionControl(t *testing.T) { 145 ctx := createMcTestContext(t) 146 defer ctx.cleanup() 147 148 ctx.now = testTime 149 150 testTime := time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC) 151 152 // For local channels, we expect a higher probability than our a prior 153 // test probability. 154 selfP := ctx.mc.GetProbability(mcTestSelf, mcTestNode1, 100) 155 if selfP != prevSuccessProbability { 156 t.Fatalf("expected prev success prob for untried local chans") 157 } 158 159 // Initial probability is expected to be the a priori. 160 ctx.expectP(1000, testAprioriHopProbability) 161 162 // Expect probability to be zero after reporting the edge as failed. 163 ctx.reportFailure(1000, lnwire.NewTemporaryChannelFailure(nil)) 164 ctx.expectP(1000, 0) 165 166 // As we reported with a min penalization amt, a lower amt than reported 167 // should return the node probability, which is the a priori 168 // probability. 169 ctx.expectP(500, testAprioriHopProbability) 170 171 // Edge decay started. The node probability weighted average should now 172 // have shifted from 1:1 to 1:0.5 -> 60%. The connection probability is 173 // half way through the recovery, so we expect 30% here. 174 ctx.now = testTime.Add(30 * time.Minute) 175 ctx.expectP(1000, 0.3) 176 177 // Edge fails again, this time without a min penalization amt. The edge 178 // should be penalized regardless of amount. 179 ctx.reportFailure(0, lnwire.NewTemporaryChannelFailure(nil)) 180 ctx.expectP(1000, 0) 181 ctx.expectP(500, 0) 182 183 // Edge decay started. 184 ctx.now = testTime.Add(60 * time.Minute) 185 ctx.expectP(1000, 0.3) 186 187 // Restart mission control to test persistence. 188 ctx.restartMc() 189 ctx.expectP(1000, 0.3) 190 191 // A node level failure should bring probability of all known channels 192 // back to zero. 193 ctx.reportFailure(0, lnwire.NewExpiryTooSoon(lnwire.ChannelUpdate{})) 194 ctx.expectP(1000, 0) 195 196 // Check whether history snapshot looks sane. 197 history := ctx.mc.GetHistorySnapshot() 198 199 if len(history.Pairs) != 4 { 200 t.Fatalf("expected 4 pairs, but got %v", len(history.Pairs)) 201 } 202 203 // Test reporting a success. 204 ctx.reportSuccess() 205 } 206 207 // TestMissionControlChannelUpdate tests that the first channel update is not 208 // penalizing the channel yet. 209 func TestMissionControlChannelUpdate(t *testing.T) { 210 ctx := createMcTestContext(t) 211 212 // Report a policy related failure. Because it is the first, we don't 213 // expect a penalty. 214 ctx.reportFailure( 215 0, lnwire.NewFeeInsufficient(0, lnwire.ChannelUpdate{}), 216 ) 217 ctx.expectP(100, testAprioriHopProbability) 218 219 // Report another failure for the same channel. We expect it to be 220 // pruned. 221 ctx.reportFailure( 222 0, lnwire.NewFeeInsufficient(0, lnwire.ChannelUpdate{}), 223 ) 224 ctx.expectP(100, 0) 225 }