1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-02-23 14:40:34 +01:00

Cancel previous LiftChannelExclusion (#2510)

Keep at most one scheduled `LiftChannelExclusion` by cancelling the previous one if the new ban lasts longer
This commit is contained in:
Thomas HUET 2022-11-28 12:25:43 +01:00 committed by GitHub
parent e32697e798
commit 6569eaf07d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 59 additions and 6 deletions

View file

@ -99,7 +99,7 @@ object RouteCalculation {
implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors implicit val sender: ActorRef = ctx.self // necessary to preserve origin when sending messages to other actors
val extraEdges = r.extraEdges.map(GraphEdge(_)).filterNot(_.desc.a == r.source).toSet // we ignore routing hints for our own channels, we have more accurate information val extraEdges = r.extraEdges.map(GraphEdge(_)).filterNot(_.desc.a == r.source).toSet // we ignore routing hints for our own channels, we have more accurate information
val ignoredEdges = r.ignore.channels ++ d.excludedChannels val ignoredEdges = r.ignore.channels ++ d.excludedChannels.keySet
val params = r.routeParams val params = r.routeParams
val routesToFind = if (params.randomize) DEFAULT_ROUTES_COUNT else 1 val routesToFind = if (params.randomize) DEFAULT_ROUTES_COUNT else 1

View file

@ -18,7 +18,7 @@ package fr.acinq.eclair.router
import akka.Done import akka.Done
import akka.actor.typed.scaladsl.adapter.actorRefAdapter import akka.actor.typed.scaladsl.adapter.actorRefAdapter
import akka.actor.{Actor, ActorLogging, ActorRef, Props, Terminated, typed} import akka.actor.{Actor, ActorLogging, ActorRef, Cancellable, Props, Terminated, typed}
import akka.event.DiagnosticLoggingAdapter import akka.event.DiagnosticLoggingAdapter
import akka.event.Logging.MDC import akka.event.Logging.MDC
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
@ -109,7 +109,7 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
awaiting = Map.empty, awaiting = Map.empty,
privateChannels = Map.empty, privateChannels = Map.empty,
scid2PrivateChannels = Map.empty, scid2PrivateChannels = Map.empty,
excludedChannels = Set.empty, excludedChannels = Map.empty,
graphWithBalances = GraphWithBalanceEstimates(graph, nodeParams.routerConf.balanceEstimateHalfLife), graphWithBalances = GraphWithBalanceEstimates(graph, nodeParams.routerConf.balanceEstimateHalfLife),
sync = Map.empty) sync = Map.empty)
startWith(NORMAL, data) startWith(NORMAL, data)
@ -172,13 +172,34 @@ class Router(val nodeParams: NodeParams, watcher: typed.ActorRef[ZmqWatcher.Comm
case Event(ExcludeChannel(desc, duration_opt), d) => case Event(ExcludeChannel(desc, duration_opt), d) =>
log.info("excluding shortChannelId={} from nodeId={} for duration={}", desc.shortChannelId, desc.a, duration_opt.getOrElse("n/a")) log.info("excluding shortChannelId={} from nodeId={} for duration={}", desc.shortChannelId, desc.a, duration_opt.getOrElse("n/a"))
duration_opt.foreach(banDuration => context.system.scheduler.scheduleOnce(banDuration, self, LiftChannelExclusion(desc))) val (excludedUntil, oldTimer) = d.excludedChannels.get(desc) match {
stay() using d.copy(excludedChannels = d.excludedChannels + desc) case Some(ExcludedForever) => (TimestampSecond.max, None)
case Some(ExcludedUntil(liftExclusionAt, timer)) => (liftExclusionAt, Some(timer))
case None => (TimestampSecond.min, None)
}
val newState = duration_opt match {
case Some(banDuration) =>
if (TimestampSecond.now() + banDuration > excludedUntil) {
oldTimer.foreach(_.cancel())
val newTimer = context.system.scheduler.scheduleOnce(banDuration, self, LiftChannelExclusion(desc))
ExcludedUntil(TimestampSecond.now() + banDuration, newTimer)
} else {
d.excludedChannels(desc)
}
case None =>
oldTimer.foreach(_.cancel())
ExcludedForever
}
stay() using d.copy(excludedChannels = d.excludedChannels + (desc -> newState))
case Event(LiftChannelExclusion(desc@ChannelDesc(shortChannelId, nodeId, _)), d) => case Event(LiftChannelExclusion(desc@ChannelDesc(shortChannelId, nodeId, _)), d) =>
log.info("reinstating shortChannelId={} from nodeId={}", shortChannelId, nodeId) log.info("reinstating shortChannelId={} from nodeId={}", shortChannelId, nodeId)
stay() using d.copy(excludedChannels = d.excludedChannels - desc) stay() using d.copy(excludedChannels = d.excludedChannels - desc)
case Event(GetExcludedChannels, d) =>
sender() ! d.excludedChannels
stay()
case Event(GetNodes, d) => case Event(GetNodes, d) =>
sender() ! d.nodes.values sender() ! d.nodes.values
stay() stay()
@ -579,6 +600,12 @@ object Router {
/** This is used when we get a TemporaryChannelFailure, to give time for the channel to recover (note that exclusions are directed) */ /** This is used when we get a TemporaryChannelFailure, to give time for the channel to recover (note that exclusions are directed) */
case class ExcludeChannel(desc: ChannelDesc, duration_opt: Option[FiniteDuration]) case class ExcludeChannel(desc: ChannelDesc, duration_opt: Option[FiniteDuration])
case class LiftChannelExclusion(desc: ChannelDesc) case class LiftChannelExclusion(desc: ChannelDesc)
sealed trait ExcludedChannelStatus
case object ExcludedForever extends ExcludedChannelStatus
case class ExcludedUntil(liftExclusionAt: TimestampSecond, timer: Cancellable) extends ExcludedChannelStatus
case object GetExcludedChannels
// @formatter:on // @formatter:on
// @formatter:off // @formatter:off
@ -643,7 +670,7 @@ object Router {
awaiting: Map[ChannelAnnouncement, Seq[GossipOrigin]], // note: this is a seq because we want to preserve order: first actor is the one who we need to send a tcp-ack when validation is done awaiting: Map[ChannelAnnouncement, Seq[GossipOrigin]], // note: this is a seq because we want to preserve order: first actor is the one who we need to send a tcp-ack when validation is done
privateChannels: Map[ByteVector32, PrivateChannel], // indexed by channel id privateChannels: Map[ByteVector32, PrivateChannel], // indexed by channel id
scid2PrivateChannels: Map[Long, ByteVector32], // real scid or alias to channel_id, only to be used for private channels scid2PrivateChannels: Map[Long, ByteVector32], // real scid or alias to channel_id, only to be used for private channels
excludedChannels: Set[ChannelDesc], // those channels are temporarily excluded from route calculation, because their node returned a TemporaryChannelFailure excludedChannels: Map[ChannelDesc, ExcludedChannelStatus], // those channels are temporarily excluded from route calculation, because their node returned a TemporaryChannelFailure
graphWithBalances: GraphWithBalanceEstimates, graphWithBalances: GraphWithBalanceEstimates,
sync: Map[PublicKey, Syncing] // keep tracks of channel range queries sent to each peer. If there is an entry in the map, it means that there is an ongoing query for which we have not yet received an 'end' message sync: Map[PublicKey, Syncing] // keep tracks of channel range queries sent to each peer. If there is an entry in the map, it means that there is an ongoing query for which we have not yet received an 'end' message
) { ) {

View file

@ -504,6 +504,32 @@ class RouterSpec extends BaseRouterSpec {
sender.expectMsgType[RouteResponse] sender.expectMsgType[RouteResponse]
} }
test("concurrent channel exclusions") { fixture =>
import fixture._
val sender = TestProbe()
sender.send(router, RouteRequest(a, d, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, routeParams = DEFAULT_ROUTE_PARAMS))
sender.expectMsgType[RouteResponse]
val bc = ChannelDesc(scid_bc, b, c)
sender.send(router, ExcludeChannel(bc, Some(1 second)))
sender.send(router, ExcludeChannel(bc, Some(10 minute)))
sender.send(router, ExcludeChannel(bc, Some(1 second)))
sender.send(router, RouteRequest(a, d, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, routeParams = DEFAULT_ROUTE_PARAMS))
sender.expectMsg(Failure(RouteNotFound))
sender.send(router, GetExcludedChannels)
val excludedChannels1 = sender.expectMsgType[Map[ChannelDesc, ExcludedChannelStatus]]
assert(excludedChannels1.size == 1)
assert(excludedChannels1(bc).isInstanceOf[ExcludedUntil])
assert(excludedChannels1(bc).asInstanceOf[ExcludedUntil].liftExclusionAt > TimestampSecond.now() + 9.minute)
sender.send(router, LiftChannelExclusion(bc))
sender.send(router, RouteRequest(a, d, DEFAULT_AMOUNT_MSAT, DEFAULT_MAX_FEE, routeParams = DEFAULT_ROUTE_PARAMS))
sender.expectMsgType[RouteResponse]
sender.send(router, ExcludeChannel(bc, None))
sender.send(router, GetExcludedChannels)
val excludedChannels2 = sender.expectMsgType[Map[ChannelDesc, ExcludedChannelStatus]]
assert(excludedChannels2.size == 1)
assert(excludedChannels2(bc) == ExcludedForever)
}
test("send routing state") { fixture => test("send routing state") { fixture =>
import fixture._ import fixture._
val sender = TestProbe() val sender = TestProbe()