From 7d53a408fd8d00dc27043974ef95f90c9ab7a5e1 Mon Sep 17 00:00:00 2001 From: Thomas HUET Date: Mon, 24 Jul 2023 12:52:42 +0200 Subject: [PATCH] More metrics and test --- .../fr/acinq/eclair/payment/Monitoring.scala | 5 ++ .../eclair/payment/relay/ChannelRelay.scala | 6 +- .../eclair/reputation/ReputationSpec.scala | 67 +++++++++++++++++++ 3 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 eclair-core/src/test/scala/fr/acinq/eclair/reputation/ReputationSpec.scala diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Monitoring.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Monitoring.scala index d59491c903..ec6bc23604 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/Monitoring.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/Monitoring.scala @@ -20,6 +20,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.eclair.MilliSatoshi import fr.acinq.eclair.channel.CMD_FAIL_HTLC import kamon.Kamon +import kamon.metric.Histogram object Monitoring { @@ -67,6 +68,10 @@ object Monitoring { PaymentNodeOutAmount.withoutTags().record(bucket, amount.truncateToSatoshi.toLong) PaymentNodeOut.withoutTags().record(bucket) } + + private val RelayConfidence = Kamon.histogram("payment.relay.confidence", "Confidence (in percent) that the relayed HTLC will be fulfilled") + def relaySettleFulfill(confidence: Double) = RelayConfidence.withTag("status", "fulfill").record((confidence * 100).toLong) + def relaySettleFail(confidence: Double) = RelayConfidence.withTag("status", "fail").record((confidence * 100).toLong) } object Tags { diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala index 32c785c0e9..3fcaedd2d8 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/ChannelRelay.scala @@ -163,13 +163,14 @@ class ChannelRelay private(nodeParams: NodeParams, case WrappedAddResponse(_: RES_SUCCESS[_]) => context.log.debug("sent htlc to the downstream channel") - waitForAddSettled() + waitForAddSettled(confidence) } - def waitForAddSettled(): Behavior[Command] = + def waitForAddSettled(confidence: Double): Behavior[Command] = Behaviors.receiveMessagePartial { case WrappedAddResponse(RES_ADD_SETTLED(o: Origin.ChannelRelayedHot, htlc, fulfill: HtlcResult.Fulfill)) => context.log.debug("relaying fulfill to upstream") + Metrics.relaySettleFulfill(confidence) val cmd = CMD_FULFILL_HTLC(o.originHtlcId, fulfill.paymentPreimage, commit = true) context.system.eventStream ! EventStream.Publish(ChannelPaymentRelayed(o.amountIn, o.amountOut, htlc.paymentHash, o.originChannelId, htlc.channelId, startedAt, TimestampMilli.now())) recordRelayDuration(isSuccess = true) @@ -177,6 +178,7 @@ class ChannelRelay private(nodeParams: NodeParams, case WrappedAddResponse(RES_ADD_SETTLED(o: Origin.ChannelRelayedHot, _, fail: HtlcResult.Fail)) => context.log.debug("relaying fail to upstream") + Metrics.relaySettleFail(confidence) Metrics.recordPaymentRelayFailed(Tags.FailureType.Remote, Tags.RelayType.Channel) val cmd = translateRelayFailure(o.originHtlcId, fail) recordRelayDuration(isSuccess = false) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/reputation/ReputationSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/reputation/ReputationSpec.scala new file mode 100644 index 0000000000..bd181be42a --- /dev/null +++ b/eclair-core/src/test/scala/fr/acinq/eclair/reputation/ReputationSpec.scala @@ -0,0 +1,67 @@ +/* + * Copyright 2023 ACINQ SAS + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package fr.acinq.eclair.reputation + +import fr.acinq.eclair.{MilliSatoshiLong, TimestampMilli} +import fr.acinq.eclair.reputation.Reputation.ReputationConfig +import org.scalatest.funsuite.AnyFunSuite +import org.scalactic.Tolerance.convertNumericToPlusOrMinusWrapper + +import java.util.UUID +import scala.concurrent.duration.DurationInt + +class ReputationSpec extends AnyFunSuite { + val (uuid1, uuid2, uuid3, uuid4, uuid5, uuid6, uuid7) = (UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID()) + + test("basic") { + var r = Reputation.init(ReputationConfig(1000000000 msat, 30 seconds)) + r = r.attempt(uuid1, 10000 msat) + assert(r.confidence() == 0) + r = r.record(uuid1, isSuccess = true) + r = r.attempt(uuid2, 10000 msat) + assert(r.confidence() == 0.5) + r = r.attempt(uuid3, 10000 msat) + assert(r.confidence() === 0.333 +- 0.001) + r = r.record(uuid2, isSuccess = true) + r = r.record(uuid3, isSuccess = true) + r = r.attempt(uuid4, 1 msat) + assert(r.confidence() === 1.0 +- 0.001) + r = r.attempt(uuid5, 90000 msat) + assert(r.confidence() === 0.25 +- 0.001) + r = r.attempt(uuid6, 10000 msat) + assert(r.confidence() === (3.0 / 13) +- 0.001) + r = r.cancel(uuid5) + assert(r.confidence() === 0.75 +- 0.001) + r = r.record(uuid6, isSuccess = false) + assert(r.confidence() === 0.75 +- 0.001) + r = r.attempt(uuid7, 10000 msat) + assert(r.confidence() === 0.6 +- 0.001) + } + + test("long HTLC") { + var r = Reputation.init(ReputationConfig(1000000000 msat, 1 second)) + r = r.attempt(uuid1, 100000 msat) + assert(r.confidence() == 0) + r = r.record(uuid1, isSuccess = true) + assert(r.confidence() == 1) + r = r.attempt(uuid2, 1000 msat, TimestampMilli(0)) + assert(r.confidence(TimestampMilli(0)) === 0.99 +- 0.001) + assert(r.confidence(TimestampMilli(0) + 100.seconds) == 0.5) + r = r.record(uuid2, isSuccess = false, now = TimestampMilli(0) + 100.seconds) + assert(r.confidence() == 0.5) + } +} \ No newline at end of file