routing: add option to force import MC pair history

This commit adds the `force` flag to the `XImportMissionControl` RPC
which allows skipping rules around the pair import except for what is
mandatory to make values meaningful. This can be useful for when clients
would like to forcibly override MC state in order to manipulate routing
results.
This commit is contained in:
Andras Banki-Horvath
2022-01-19 17:01:09 +01:00
parent 3829385073
commit 23c157a07b
8 changed files with 479 additions and 400 deletions

View File

@@ -363,7 +363,9 @@ func (m *MissionControl) GetHistorySnapshot() *MissionControlSnapshot {
// ImportHistory imports the set of mission control results provided to our
// in-memory state. These results are not persisted, so will not survive
// restarts.
func (m *MissionControl) ImportHistory(history *MissionControlSnapshot) error {
func (m *MissionControl) ImportHistory(history *MissionControlSnapshot,
force bool) error {
if history == nil {
return errors.New("cannot import nil history")
}
@@ -374,7 +376,7 @@ func (m *MissionControl) ImportHistory(history *MissionControlSnapshot) error {
log.Infof("Importing history snapshot with %v pairs to mission control",
len(history.Pairs))
imported := m.state.importSnapshot(history)
imported := m.state.importSnapshot(history, force)
log.Infof("Imported %v results to mission control", imported)
@@ -520,7 +522,7 @@ func (m *MissionControl) applyPaymentResult(
}
m.state.setLastPairResult(
pair.From, pair.To, result.timeReply, &pairResult,
pair.From, pair.To, result.timeReply, &pairResult, false,
)
}

View File

@@ -54,7 +54,7 @@ func (m *missionControlState) resetHistory() {
// setLastPairResult stores a result for a node pair.
func (m *missionControlState) setLastPairResult(fromNode, toNode route.Vertex,
timestamp time.Time, result *pairResult) {
timestamp time.Time, result *pairResult, force bool) {
nodePairs, ok := m.lastPairResult[fromNode]
if !ok {
@@ -74,7 +74,7 @@ func (m *missionControlState) setLastPairResult(fromNode, toNode route.Vertex,
// prevents the success range from shrinking when there is no
// reason to do so. For example: small amount probes shouldn't
// affect a previous success for a much larger amount.
if successAmt > current.SuccessAmt {
if force || successAmt > current.SuccessAmt {
current.SuccessAmt = successAmt
}
@@ -83,7 +83,9 @@ func (m *missionControlState) setLastPairResult(fromNode, toNode route.Vertex,
// are likely to succeed. We don't want to clear the failure
// completely, because we haven't learnt much for amounts above
// the current success amount.
if !current.FailTime.IsZero() && successAmt >= current.FailAmt {
if force || (!current.FailTime.IsZero() &&
successAmt >= current.FailAmt) {
current.FailAmt = successAmt + 1
}
} else {
@@ -100,7 +102,7 @@ func (m *missionControlState) setLastPairResult(fromNode, toNode route.Vertex,
// come in out of order. This check makes it easier for payment
// processes to converge to a final state.
failInterval := timestamp.Sub(current.FailTime)
if failAmt > current.FailAmt &&
if !force && failAmt > current.FailAmt &&
failInterval < m.minFailureRelaxInterval {
log.Debugf("Ignoring higher amount failure within min "+
@@ -213,7 +215,9 @@ func (m *missionControlState) getSnapshot() *MissionControlSnapshot {
// importSnapshot takes an existing snapshot and merges it with our current
// state if the result provided are fresher than our current results. It returns
// the number of pairs that were used.
func (m *missionControlState) importSnapshot(snapshot *MissionControlSnapshot) int {
func (m *missionControlState) importSnapshot(snapshot *MissionControlSnapshot,
force bool) int {
var imported int
for _, pair := range snapshot.Pairs {
@@ -230,13 +234,13 @@ func (m *missionControlState) importSnapshot(snapshot *MissionControlSnapshot) i
failResult := failPairResult(pair.FailAmt)
imported += m.importResult(
lastResult.FailTime, pair.FailTime, failResult,
fromNode, toNode,
fromNode, toNode, force,
)
successResult := successPairResult(pair.SuccessAmt)
imported += m.importResult(
lastResult.SuccessTime, pair.SuccessTime, successResult,
fromNode, toNode,
fromNode, toNode, force,
)
}
@@ -244,9 +248,10 @@ func (m *missionControlState) importSnapshot(snapshot *MissionControlSnapshot) i
}
func (m *missionControlState) importResult(currentTs, importedTs time.Time,
importedResult pairResult, fromNode, toNode route.Vertex) int {
importedResult pairResult, fromNode, toNode route.Vertex,
force bool) int {
if currentTs.After(importedTs) {
if !force && currentTs.After(importedTs) {
log.Debugf("Not setting pair result for %v->%v (%v) "+
"success=%v, timestamp %v older than last result %v",
fromNode, toNode, importedResult.amt,
@@ -255,7 +260,9 @@ func (m *missionControlState) importResult(currentTs, importedTs time.Time,
return 0
}
m.setLastPairResult(fromNode, toNode, importedTs, &importedResult)
m.setLastPairResult(
fromNode, toNode, importedTs, &importedResult, force,
)
return 1
}

View File

@@ -5,11 +5,12 @@ import (
"time"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/stretchr/testify/require"
)
// TestMissionControlStateFailureResult tests setting failure results on the
// mission control state.
func TestMissionControlStateFailureResult(t *testing.T) {
// TestMissionControlStateSetLastPairResult tests setting mission control state
// pair results.
func TestMissionControlStateSetLastPairResult(t *testing.T) {
const minFailureRelaxInterval = time.Minute
state := newMissionControlState(minFailureRelaxInterval)
@@ -20,7 +21,9 @@ func TestMissionControlStateFailureResult(t *testing.T) {
)
// Report a 1000 sat failure.
state.setLastPairResult(from, to, timestamp, &pairResult{amt: 1000})
state.setLastPairResult(
from, to, timestamp, &pairResult{amt: 1000}, false,
)
result, _ := state.getLastPairResult(from)
if result[to].FailAmt != 1000 {
t.Fatalf("unexpected fail amount %v", result[to].FailAmt)
@@ -29,7 +32,9 @@ func TestMissionControlStateFailureResult(t *testing.T) {
// Report an 1100 sat failure one hour later. It is expected to
// overwrite the previous failure.
timestamp = timestamp.Add(time.Hour)
state.setLastPairResult(from, to, timestamp, &pairResult{amt: 1100})
state.setLastPairResult(
from, to, timestamp, &pairResult{amt: 1100}, false,
)
result, _ = state.getLastPairResult(from)
if result[to].FailAmt != 1100 {
t.Fatalf("unexpected fail amount %v", result[to].FailAmt)
@@ -39,9 +44,51 @@ func TestMissionControlStateFailureResult(t *testing.T) {
// the failure amount is too soon after the previous failure, the result
// is not applied.
timestamp = timestamp.Add(time.Second)
state.setLastPairResult(from, to, timestamp, &pairResult{amt: 1200})
state.setLastPairResult(
from, to, timestamp, &pairResult{amt: 1200}, false,
)
result, _ = state.getLastPairResult(from)
if result[to].FailAmt != 1100 {
t.Fatalf("unexpected fail amount %v", result[to].FailAmt)
}
// Roll back time 1 second to test forced import.
timestamp = testTime
state.setLastPairResult(
from, to, timestamp, &pairResult{amt: 999}, true,
)
result, _ = state.getLastPairResult(from)
require.Equal(t, 999, int(result[to].FailAmt))
// Report an 1500 sat success.
timestamp = timestamp.Add(time.Second)
state.setLastPairResult(
from, to, timestamp, &pairResult{amt: 1500, success: true}, false,
)
result, _ = state.getLastPairResult(from)
// We don't expect the failtime to change, only the fail amount, we
// expect however change of both the success time and the success amount.
expected := TimedPairResult{
FailTime: timestamp.Add(-time.Second),
FailAmt: 1501,
SuccessTime: timestamp,
SuccessAmt: 1500,
}
require.Equal(t, expected, result[to])
// Again roll back time to test forced import.
state.setLastPairResult(
from, to, testTime, &pairResult{amt: 50, success: true}, true,
)
result, _ = state.getLastPairResult(from)
// We don't expect the failtime to change, only the fail amount, we
// expect however change of both the success time and the success amount.
expected = TimedPairResult{
FailTime: timestamp.Add(-time.Second),
FailAmt: 51,
SuccessTime: testTime,
SuccessAmt: 50,
}
require.Equal(t, expected, result[to])
}