diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go new file mode 100644 index 000000000..f8dd35bad --- /dev/null +++ b/routing/missioncontrol.go @@ -0,0 +1,167 @@ +package routing + +import ( + "sync" + "time" +) + +const ( + // vertexDecay is the decay period of colored vertexes added to + // missionControl. Once vertexDecay passes after an entry has been + // added to the prune view, it is garbage collected. This value is + // larger than edgeDecay as an edge failure typical indicates an + // unbalanced channel, while a vertex failure indicates a node is not + // online and active. + vertexDecay = time.Duration(time.Minute * 5) + + // edgeDecay is the decay period of colored edges added to + // missionControl. Once edgeDecay passed after an entry has been added, + // it is garbage collected. This value is smaller than vertexDecay as + // an edge related failure during payment sending typically indicates + // that a channel was unbalanced, a condition which may quickly change. + // + // TODO(roasbeef): instead use random delay on each? + edgeDecay = time.Duration(time.Second * 5) +) + +// missionControl contains state which summarizes the past attempts of HTLC +// routing by external callers when sending payments throughout the network. +// missionControl remembers the outcome of these past routing attempts (success +// and failure), and is able to provide hints/guidance to future HTLC routing +// attempts. missionControl maintains a decaying network view of the +// edges/vertexes that should be marked as "pruned" during path finding. This +// graph view acts as a shared memory during HTLC payment routing attempts. +// With each execution, if an error is encountered, based on the type of error +// and the location of the error within the route, an edge or vertex is added +// to the view. Later sending attempts will then query the view for all the +// vertexes/edges that should be ignored. Items in the view decay after a set +// period of time, allowing the view to be dynamic w.r.t network changes. +type missionControl struct { + // failedEdges maps a short channel ID to be pruned, to the time that + // it was added to the prune view. Edges are added to this map if a + // caller reports to missionControl a failure localized to that edge + // when sending a payment. + failedEdges map[uint64]time.Time + + // failedVertexes maps a node's public key that should be pruned, to + // the time that it was added to the prune view. Vertexes are added to + // this map if a caller reports to missionControl a failure localized + // to that particular vertex. + failedVertexes map[vertex]time.Time + + sync.Mutex + + // TODO(roasbeef): further counters, if vertex continually unavailable, + // add to another generation + + // TODO(roasbeef): also add favorable metrics for nodes +} + +// newMissionControl returns a new instance of missionControl. +// +// TODO(roasbeef): persist memory +func newMissionControl() *missionControl { + return &missionControl{ + failedEdges: make(map[uint64]time.Time), + failedVertexes: make(map[vertex]time.Time), + } +} + +// ReportVertexFailure adds a vertex to the graph prune view after a client +// reports a routing failure localized to the vertex. The time the vertex was +// added is noted, as it'll be pruned from the view after a period of +// vertexDecay. +func (m *missionControl) ReportVertexFailure(v vertex) { + log.Debugf("Reporting vertex %v failure to Mission Control", v) + + m.Lock() + m.failedVertexes[v] = time.Now() + m.Unlock() +} + +// ReportChannelFailure adds a channel to the graph prune view. The time the +// channel was added is noted, as it'll be pruned from the view after a period +// of edgeDecay. +// +// TODO(roasbeef): also add value attempted to send and capacity of channel +func (m *missionControl) ReportChannelFailure(e uint64) { + log.Debugf("Reporting edge %v failure to Mission Control", e) + + m.Lock() + m.failedEdges[e] = time.Now() + m.Unlock() +} + +// GraphPruneView returns a new graphPruneView instance which is to be +// consulted during path finding. If a vertex/edge is found within the returned +// prune view, it is to be ignored as a goroutine has had issues routing +// through it successfully. Within this method the main view of the +// missionControl is garbage collected as entires are detected to be "stale". +func (m *missionControl) GraphPruneView() *graphPruneView { + // First, we'll grab the current time, this value will be used to + // determine if an entry is stale or not. + now := time.Now() + + m.Lock() + + // For each of the vertexes that have been added to the prune view, if + // it is now "stale", then we'll ignore it and avoid adding it to the + // view we'll return. + vertexes := make(map[vertex]struct{}) + for vertex, pruneTime := range m.failedVertexes { + if now.Sub(pruneTime) >= edgeDecay { + log.Tracef("Pruning decayed failure report for vertex %v "+ + "from Mission Control", vertex) + + delete(m.failedVertexes, vertex) + continue + } + + vertexes[vertex] = struct{}{} + } + + // We'll also do the same for edges, but use the edgeDecay this time + // rather than the decay for vertexes. + edges := make(map[uint64]struct{}) + for edge, pruneTime := range m.failedEdges { + if now.Sub(pruneTime) >= edgeDecay { + log.Tracef("Pruning decayed failure report for edge %v "+ + "from Mission Control", edge) + + delete(m.failedEdges, edge) + continue + } + + edges[edge] = struct{}{} + } + + m.Unlock() + + log.Debugf("Mission Control returning prune view of %v edges, %v "+ + "vertexes", len(edges), len(vertexes)) + + return &graphPruneView{ + edges: edges, + vertexes: vertexes, + } +} + +// graphPruneView is a filter of sorts that path finding routines should +// consult during the execution. Any edges or vertexes within the view should +// be ignored during path finding. The contents of the view reflect the current +// state of the wider network from the PoV of mission control compiled via HTLC +// routing attempts in the past. +type graphPruneView struct { + edges map[uint64]struct{} + + vertexes map[vertex]struct{} +} + +// ResetHistory resets the history of missionControl returning it to a state as +// if no payment attempts have been made. +func (m *missionControl) ResetHistory() { + m.Lock() + m.failedEdges = make(map[uint64]time.Time) + m.failedVertexes = make(map[vertex]time.Time) + m.Unlock() +}