diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala b/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala index 8a4e18490b..3e33ff2118 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/ShortChannelId.scala @@ -58,7 +58,25 @@ object ShortChannelId { def toShortId(blockHeight: Int, txIndex: Int, outputIndex: Int): Long = ((blockHeight & 0xFFFFFFL) << 40) | ((txIndex & 0xFFFFFFL) << 16) | (outputIndex & 0xFFFFL) - def generateLocalAlias(): Alias = Alias(System.nanoTime()) // TODO: fixme (duplicate, etc.) + /** + * At block height 350 000 LN didn't exist, so all real scids less than that will never be used. This results in + * more than `2^58` values. + * + * The expected number of values before we have a collision can be approximated by (*): + * `Q(H) ~= sqrt( Pi * H / 2) = sqrt(Pi * 2^57) = 673 000 000` + * + * We don't expect to have anywhere close to that many channels at any given time on our node, so we don't need to + * check for duplicate values. + * + * (*) https://en.wikipedia.org/wiki/Birthday_attack + */ + private val aliasUpperBound = 2^58 + + def generateLocalAlias(): Alias = { + // modulo won't skew the distribution because 2^64 is a multiple of 2^58 + val l = Math.abs(randomLong() % aliasUpperBound) + Alias(l) + } @inline def blockHeight(shortChannelId: ShortChannelId): BlockHeight = BlockHeight((shortChannelId.toLong >> 40) & 0xFFFFFF) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala index f7d3e6c2cc..df053325c1 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/Register.scala @@ -52,6 +52,12 @@ class Register extends Actor with ActorLogging { // We map all known scids (real or alias) to the channel_id. The relayer is in charge of deciding whether a real // scid can be used or not for routing (see option_scid_alias), but the register is neutral. val m = (scidAssigned.shortIds.real.toOption.toSeq :+ scidAssigned.shortIds.localAlias).map(_ -> scidAssigned.channelId).toMap + // duplicate check for aliases (we use a random value in a large enough space that there should never be collisions) + shortIds.get(scidAssigned.shortIds.localAlias) match { + case Some(channelId) if channelId != scidAssigned.channelId => + log.error("duplicate alias={} for channelIds={},{} this should never happen!", scidAssigned.shortIds.localAlias, channelId, scidAssigned.channelId) + case _ => () + } context become main(channels, shortIds ++ m, channelsTo) case Terminated(actor) if channels.values.toSet.contains(actor) => diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala index fc472f2054..094f4dfa4b 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/ShortChannelIdSpec.scala @@ -70,4 +70,11 @@ class ShortChannelIdSpec extends AnyFunSuite { Seq(-561L, 0xffffffffffffffffL, 0x2affffffffffffffL).foreach(id => assert(Alias(id) == UnspecifiedShortChannelId(id))) } + test("basic check on random alias generation") { + for(_ <- 0 until 100) { + val alias = ShortChannelId.generateLocalAlias() + assert(alias.toLong >= 0 && alias.toLong <= 384_829_069_721_665_536L) + } + } + }