Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
routing: track amt ranges in mc
  • Loading branch information
joostjager committed Sep 27, 2019
1 parent 3abaf00 commit fca612e
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 187 deletions.
18 changes: 9 additions & 9 deletions cmd/lncli/cmd_query_mission_control.go
Expand Up @@ -32,10 +32,9 @@ func queryMissionControl(ctx *cli.Context) error {
}

type displayPairHistory struct {
NodeFrom, NodeTo string
LastAttemptSuccessful bool
Timestamp int64
AmtSat int64
NodeFrom, NodeTo string
SuccessTime, FailTime int64
FailAmtSat, SuccessAmtSat int64
}

displayResp := struct {
Expand All @@ -46,11 +45,12 @@ func queryMissionControl(ctx *cli.Context) error {
displayResp.Pairs = append(
displayResp.Pairs,
displayPairHistory{
NodeFrom: hex.EncodeToString(n.NodeFrom),
NodeTo: hex.EncodeToString(n.NodeTo),
LastAttemptSuccessful: n.LastAttemptSuccessful,
Timestamp: n.Timestamp,
AmtSat: n.AmtSat,
NodeFrom: hex.EncodeToString(n.NodeFrom),
NodeTo: hex.EncodeToString(n.NodeTo),
FailTime: n.FailTime,
SuccessTime: n.SuccessTime,
FailAmtSat: n.FailAmtSat,
SuccessAmtSat: n.SuccessAmtSat,
},
)
}
Expand Down
274 changes: 143 additions & 131 deletions lnrpc/routerrpc/router.pb.go

Large diffs are not rendered by default.

22 changes: 16 additions & 6 deletions lnrpc/routerrpc/router.proto
Expand Up @@ -349,14 +349,24 @@ message PairHistory {
/// The destination node pubkey of the pair.
bytes node_to = 2 [json_name="node_to"];

/// Time stamp of last result.
int64 timestamp = 3 [json_name = "timestamp"];
/// Time of last failure.
int64 fail_time = 3 [json_name = "fail_time"];

/// Last amount that was (attempted to be) forwarded.
int64 amt_sat = 4 [json_name = "amt_sat"];
/**
Lowest amount that failed to forward. This may be set to zero if the failure
is independent of amount.
*/
int64 fail_amt_sat = 4 [json_name = "fail_amt_sat"];

reserved 5;

reserved 6;

/// Time of last success.
int64 success_time = 7 [json_name = "success_time"];

/// Whether the last payment attempt through this pair was successful.
bool last_attempt_successful = 6 [json_name = "last_attempt_successful"];
/// Highest amount that we could successfully forward.
int64 success_amt_sat = 8 [json_name = "success_amt_sat"];
}

message QueryMcProbabilityRequest{
Expand Down
15 changes: 10 additions & 5 deletions lnrpc/routerrpc/router_server.go
Expand Up @@ -466,11 +466,16 @@ func (s *Server) QueryMissionControl(ctx context.Context,
pair := p

rpcPair := PairHistory{
NodeFrom: pair.Pair.From[:],
NodeTo: pair.Pair.To[:],
Timestamp: pair.Timestamp.Unix(),
AmtSat: int64(pair.Amt.ToSatoshis()),
LastAttemptSuccessful: pair.Success,
NodeFrom: pair.Pair.From[:],
NodeTo: pair.Pair.To[:],
FailAmtSat: int64(pair.FailAmt.ToSatoshis()),
SuccessAmtSat: int64(pair.SuccessAmt.ToSatoshis()),
}
if !pair.FailTime.IsZero() {
rpcPair.FailTime = pair.FailTime.Unix()
}
if !pair.SuccessTime.IsZero() {
rpcPair.SuccessTime = pair.SuccessTime.Unix()
}

rpcPairs = append(rpcPairs, &rpcPair)
Expand Down
74 changes: 61 additions & 13 deletions routing/missioncontrol.go
Expand Up @@ -119,16 +119,17 @@ type MissionControlConfig struct {

// TimedPairResult describes a timestamped pair result.
type TimedPairResult struct {
// timestamp is the time when this result was obtained.
Timestamp time.Time
// FailTime is the time of the last failure.
FailTime time.Time

// Amt is the amount that was (attempted to be) forwarded in the last
// payment attempt.
Amt lnwire.MilliSatoshi
// FailAmt is the lowest amount that failed to forward.
FailAmt lnwire.MilliSatoshi

// success indicates whether the payment attempt was successful through
// this pair.
Success bool
// SuccessTime is the time of the last success.
SuccessTime time.Time

// SuccessAmt is the highest amount that successfully forwarded.
SuccessAmt lnwire.MilliSatoshi
}

// MissionControlSnapshot contains a snapshot of the current state of mission
Expand Down Expand Up @@ -260,11 +261,58 @@ func (m *MissionControl) setLastPairResult(fromNode,
m.lastPairResult[fromNode] = nodePairs
}

nodePairs[toNode] = TimedPairResult{
Timestamp: timestamp,
Amt: result.amt,
Success: result.success,
current := nodePairs[toNode]

// Apply the new result to the existing data for this pair. If there is
// no existing data, apply it to the default values for TimedPairResult.
if result.success {
current.SuccessTime = timestamp
successAmt := result.amt

// Only update the success amount if this amount is higher. This
// 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 {
current.SuccessAmt = successAmt
}

// If the success amount goes into the failure range, move the
// failure range up. Future attempts up to the success amount
// 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 {
current.FailAmt = successAmt + 1
}
} else {
// For failures we always want to update both the amount and the
// time. Those need to relate to the same result, because the
// time is used to gradually diminish the penality for that
// specific result. Updating the timestamp but not the amount
// could cause a failure for a lower amount (a more severe
// condition) to be revived as if it just happened.
current.FailAmt = result.amt
current.FailTime = timestamp

// If the failure range goes into the success range, move the
// success range down.
if result.amt <= current.SuccessAmt {
// Special case a failure for amount zero to prevent a
// negative success amount. This can happen when the
// failure is independent of the actual amount.
if result.amt == 0 {
current.SuccessAmt = 0
} else {
current.SuccessAmt = result.amt - 1
}
}
}

log.Debugf("Setting %v->%v range to [%v-%v]",
fromNode, toNode, current.SuccessAmt, current.FailAmt)

nodePairs[toNode] = current
}

// setAllPairFailure stores a result for all known connection of the given node.
Expand All @@ -278,7 +326,7 @@ func (m *MissionControl) setAllPairFailure(fromNode route.Vertex,

for connection := range nodePairs {
nodePairs[connection] = TimedPairResult{
Timestamp: timestamp,
FailTime: timestamp,
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions routing/missioncontrol_test.go
Expand Up @@ -196,12 +196,12 @@ func TestMissionControlChannelUpdate(t *testing.T) {
ctx.reportFailure(
0, lnwire.NewFeeInsufficient(0, lnwire.ChannelUpdate{}),
)
ctx.expectP(0, testAprioriHopProbability)
ctx.expectP(100, 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)
ctx.expectP(100, 0)
}
27 changes: 13 additions & 14 deletions routing/probability_estimator.go
Expand Up @@ -81,19 +81,18 @@ func (p *probabilityEstimator) getNodeProbability(now time.Time,
totalWeight := aprioriFactor

for _, result := range results {
age := now.Sub(result.Timestamp)

switch {
// Weigh success with a constant high weight of 1. There is no
// decay.
case result.Success:
// Weigh success with a constant high weight of 1. There
// is no decay.
if result.SuccessAmt != 0 && amt <= result.SuccessAmt {
totalWeight++
probabilitiesTotal += p.prevSuccessProbability
probabilitiesTotal += prevSuccessProbability
}

// Weigh failures in accordance with their age. The base
// probability of a failure is considered zero, so nothing needs
// to be added to probabilitiesTotal.
case amt >= result.Amt:
// probability of a failure is considered zero, so
// nothing needs to be added to probabilitiesTotal.
if !result.FailTime.IsZero() && amt >= result.FailAmt {
age := now.Sub(result.FailTime)
totalWeight += p.getWeight(age)
}
}
Expand Down Expand Up @@ -133,8 +132,8 @@ func (p *probabilityEstimator) getPairProbability(

// For successes, we have a fixed (high) probability. Those pairs
// will be assumed good until proven otherwise.
if lastPairResult.Success {
return p.prevSuccessProbability
if amt <= lastPairResult.SuccessAmt {
return prevSuccessProbability
}

nodeProbability := getNodeProbability()
Expand All @@ -144,11 +143,11 @@ func (p *probabilityEstimator) getPairProbability(
// penalization. If the current amount is smaller than the amount that
// previously triggered a failure, we act as if this is an untried
// channel.
if amt < lastPairResult.Amt {
if lastPairResult.FailTime.IsZero() || amt < lastPairResult.FailAmt {
return nodeProbability
}

timeSinceLastFailure := now.Sub(lastPairResult.Timestamp)
timeSinceLastFailure := now.Sub(lastPairResult.FailTime)

// Calculate success probability based on the weight of the last
// failure. When the failure is fresh, its weight is 1 and we'll return
Expand Down
15 changes: 8 additions & 7 deletions routing/probability_estimator_test.go
Expand Up @@ -85,8 +85,7 @@ func TestProbabilityEstimatorOneSuccess(t *testing.T) {

ctx.results = map[int]TimedPairResult{
node1: {
Timestamp: testTime.Add(-time.Hour),
Success: true,
SuccessAmt: lnwire.MilliSatoshi(1000),
},
}

Expand All @@ -109,7 +108,8 @@ func TestProbabilityEstimatorOneFailure(t *testing.T) {

ctx.results = map[int]TimedPairResult{
node1: {
Timestamp: testTime.Add(-time.Hour),
FailTime: testTime.Add(-time.Hour),
FailAmt: lnwire.MilliSatoshi(50),
},
}

Expand All @@ -131,14 +131,15 @@ func TestProbabilityEstimatorMix(t *testing.T) {

ctx.results = map[int]TimedPairResult{
node1: {
Timestamp: testTime.Add(-time.Hour),
Success: true,
SuccessAmt: lnwire.MilliSatoshi(1000),
},
node2: {
Timestamp: testTime.Add(-2 * time.Hour),
FailTime: testTime.Add(-2 * time.Hour),
FailAmt: lnwire.MilliSatoshi(50),
},
node3: {
Timestamp: testTime.Add(-3 * time.Hour),
FailTime: testTime.Add(-3 * time.Hour),
FailAmt: lnwire.MilliSatoshi(50),
},
}

Expand Down

0 comments on commit fca612e

Please sign in to comment.