From 0136cafec574aec9bc09f0a395d98cbf21e0fd58 Mon Sep 17 00:00:00 2001 From: Thomas HUET Date: Mon, 24 Jun 2024 11:09:49 +0200 Subject: [PATCH] Use 3 bits --- .../eclair/payment/relay/NodeRelay.scala | 4 +- .../reputation/ReputationRecorder.scala | 50 +++++++++---------- .../acinq/eclair/wire/protocol/HtlcTlv.scala | 8 +-- .../wire/protocol/LightningMessageTypes.scala | 4 +- .../reputation/ReputationRecorderSpec.scala | 44 ++++++++-------- 5 files changed, 55 insertions(+), 55 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala index 28addef635..0dfcd85771 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala @@ -267,7 +267,7 @@ class NodeRelay private(nodeParams: NodeParams, private def doSend(upstream: Upstream.Hot.Trampoline, nextPayload: IntermediatePayload.NodeRelay, nextPacket_opt: Option[OnionRoutingPacket]): Behavior[Command] = { context.log.debug(s"relaying trampoline payment (amountIn=${upstream.amountIn} expiryIn=${upstream.expiryIn} amountOut=${nextPayload.amountToForward} expiryOut=${nextPayload.outgoingCltv})") val totalFee = upstream.amountIn - nextPayload.amountToForward - val fees = upstream.received.foldLeft(Map.empty[(PublicKey, Boolean), MilliSatoshi])((fees, r) => + val fees = upstream.received.foldLeft(Map.empty[(PublicKey, Int), MilliSatoshi])((fees, r) => fees.updatedWith((r.receivedFrom, r.add.endorsement))(fee => Some(fee.getOrElse(MilliSatoshi(0)) + r.add.amountMsat * totalFee.toLong / upstream.amountIn.toLong))) reputationRecorder ! GetTrampolineConfidence(context.messageAdapter[ReputationRecorder.Confidence](confidence => WrappedConfidence(confidence.value)), fees, relayId) @@ -307,7 +307,7 @@ class NodeRelay private(nodeParams: NodeParams, context.log.debug("trampoline payment fully resolved downstream") success(upstream, fulfilledUpstream, paymentSent) val totalFee = upstream.amountIn - paymentSent.amountWithFees - val fees = upstream.received.foldLeft(Map.empty[(PublicKey, Boolean), MilliSatoshi])((fees, r) => + val fees = upstream.received.foldLeft(Map.empty[(PublicKey, Int), MilliSatoshi])((fees, r) => fees.updatedWith((r.receivedFrom, r.add.endorsement))(fee => Some(fee.getOrElse(MilliSatoshi(0)) + r.add.amountMsat * totalFee.toLong / upstream.amountIn.toLong))) reputationRecorder ! RecordTrampolineSuccess(fees, relayId) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/reputation/ReputationRecorder.scala b/eclair-core/src/main/scala/fr/acinq/eclair/reputation/ReputationRecorder.scala index a91b480f67..47ce40100c 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/reputation/ReputationRecorder.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/reputation/ReputationRecorder.scala @@ -28,47 +28,47 @@ object ReputationRecorder { sealed trait Command sealed trait StandardCommand extends Command - case class GetConfidence(replyTo: ActorRef[Confidence], originNode: PublicKey, isEndorsed: Boolean, relayId: UUID, fee: MilliSatoshi) extends StandardCommand - case class CancelRelay(originNode: PublicKey, isEndorsed: Boolean, relayId: UUID) extends StandardCommand - case class RecordResult(originNode: PublicKey, isEndorsed: Boolean, relayId: UUID, isSuccess: Boolean) extends StandardCommand + case class GetConfidence(replyTo: ActorRef[Confidence], originNode: PublicKey, endorsement: Int, relayId: UUID, fee: MilliSatoshi) extends StandardCommand + case class CancelRelay(originNode: PublicKey, endorsement: Int, relayId: UUID) extends StandardCommand + case class RecordResult(originNode: PublicKey, endorsement: Int, relayId: UUID, isSuccess: Boolean) extends StandardCommand sealed trait TrampolineCommand extends Command - case class GetTrampolineConfidence(replyTo: ActorRef[Confidence], fees: Map[(PublicKey, Boolean), MilliSatoshi], relayId: UUID) extends TrampolineCommand - case class RecordTrampolineFailure(keys: Set[(PublicKey, Boolean)], relayId: UUID) extends TrampolineCommand - case class RecordTrampolineSuccess(fees: Map[(PublicKey, Boolean), MilliSatoshi], relayId: UUID) extends TrampolineCommand + case class GetTrampolineConfidence(replyTo: ActorRef[Confidence], fees: Map[(PublicKey, Int), MilliSatoshi], relayId: UUID) extends TrampolineCommand + case class RecordTrampolineFailure(keys: Set[(PublicKey, Int)], relayId: UUID) extends TrampolineCommand + case class RecordTrampolineSuccess(fees: Map[(PublicKey, Int), MilliSatoshi], relayId: UUID) extends TrampolineCommand case class Confidence(value: Double) - def apply(reputationConfig: ReputationConfig, reputations: Map[(PublicKey, Boolean), Reputation]): Behavior[Command] = { + def apply(reputationConfig: ReputationConfig, reputations: Map[(PublicKey, Int), Reputation]): Behavior[Command] = { Behaviors.receiveMessage { - case GetConfidence(replyTo, originNode, isEndorsed, relayId, fee) => - val updatedReputation = reputations.getOrElse((originNode, isEndorsed), Reputation.init(reputationConfig)).attempt(relayId, fee) + case GetConfidence(replyTo, originNode, endorsement, relayId, fee) => + val updatedReputation = reputations.getOrElse((originNode, endorsement), Reputation.init(reputationConfig)).attempt(relayId, fee) replyTo ! Confidence(updatedReputation.confidence()) - ReputationRecorder(reputationConfig, reputations.updated((originNode, isEndorsed), updatedReputation)) - case CancelRelay(originNode, isEndorsed, relayId) => - val updatedReputation = reputations.getOrElse((originNode, isEndorsed), Reputation.init(reputationConfig)).cancel(relayId) - ReputationRecorder(reputationConfig, reputations.updated((originNode, isEndorsed), updatedReputation)) - case RecordResult(originNode, isEndorsed, relayId, isSuccess) => - val updatedReputation = reputations.getOrElse((originNode, isEndorsed), Reputation.init(reputationConfig)).record(relayId, isSuccess) - ReputationRecorder(reputationConfig, reputations.updated((originNode, isEndorsed), updatedReputation)) + ReputationRecorder(reputationConfig, reputations.updated((originNode, endorsement), updatedReputation)) + case CancelRelay(originNode, endorsement, relayId) => + val updatedReputation = reputations.getOrElse((originNode, endorsement), Reputation.init(reputationConfig)).cancel(relayId) + ReputationRecorder(reputationConfig, reputations.updated((originNode, endorsement), updatedReputation)) + case RecordResult(originNode, endorsement, relayId, isSuccess) => + val updatedReputation = reputations.getOrElse((originNode, endorsement), Reputation.init(reputationConfig)).record(relayId, isSuccess) + ReputationRecorder(reputationConfig, reputations.updated((originNode, endorsement), updatedReputation)) case GetTrampolineConfidence(replyTo, fees, relayId) => - val (confidence, updatedReputations) = fees.foldLeft((1.0, reputations)){case ((c, r), ((originNode, isEndorsed), fee)) => - val updatedReputation = reputations.getOrElse((originNode, isEndorsed), Reputation.init(reputationConfig)).attempt(relayId, fee) + val (confidence, updatedReputations) = fees.foldLeft((1.0, reputations)){case ((c, r), ((originNode, endorsement), fee)) => + val updatedReputation = reputations.getOrElse((originNode, endorsement), Reputation.init(reputationConfig)).attempt(relayId, fee) val updatedConfidence = c.min(updatedReputation.confidence()) - (updatedConfidence, r.updated((originNode, isEndorsed), updatedReputation)) + (updatedConfidence, r.updated((originNode, endorsement), updatedReputation)) } replyTo ! Confidence(confidence) ReputationRecorder(reputationConfig, updatedReputations) case RecordTrampolineFailure(keys, relayId) => - val updatedReputations = keys.foldLeft(reputations) { case (r, (originNode, isEndorsed)) => - val updatedReputation = reputations.getOrElse((originNode, isEndorsed), Reputation.init(reputationConfig)).record(relayId, isSuccess = false) - r.updated((originNode, isEndorsed), updatedReputation) + val updatedReputations = keys.foldLeft(reputations) { case (r, (originNode, endorsement)) => + val updatedReputation = reputations.getOrElse((originNode, endorsement), Reputation.init(reputationConfig)).record(relayId, isSuccess = false) + r.updated((originNode, endorsement), updatedReputation) } ReputationRecorder(reputationConfig, updatedReputations) case RecordTrampolineSuccess(fees, relayId) => - val updatedReputations = fees.foldLeft(reputations) { case (r, ((originNode, isEndorsed), fee)) => - val updatedReputation = reputations.getOrElse((originNode, isEndorsed), Reputation.init(reputationConfig)).record(relayId, isSuccess = true, Some(fee)) - r.updated((originNode, isEndorsed), updatedReputation) + val updatedReputations = fees.foldLeft(reputations) { case (r, ((originNode, endorsement), fee)) => + val updatedReputation = reputations.getOrElse((originNode, endorsement), Reputation.init(reputationConfig)).record(relayId, isSuccess = true, Some(fee)) + r.updated((originNode, endorsement), updatedReputation) } ReputationRecorder(reputationConfig, updatedReputations) } diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/HtlcTlv.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/HtlcTlv.scala index 208cca024b..30e4895534 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/HtlcTlv.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/HtlcTlv.scala @@ -20,7 +20,7 @@ import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey import fr.acinq.eclair.UInt64 import fr.acinq.eclair.wire.protocol.CommonCodecs._ import fr.acinq.eclair.wire.protocol.TlvCodecs.{tlvField, tlvStream, tu16} -import scodec.Codec +import scodec.{Attempt, Codec, Err} import scodec.bits.HexStringSyntax import scodec.codecs._ @@ -34,15 +34,15 @@ object UpdateAddHtlcTlv { /** Blinding ephemeral public key that should be used to derive shared secrets when using route blinding. */ case class BlindingPoint(publicKey: PublicKey) extends UpdateAddHtlcTlv - case class Endorsement(endorsed: Boolean) extends UpdateAddHtlcTlv + case class Endorsement(level: Int) extends UpdateAddHtlcTlv private val blindingPoint: Codec[BlindingPoint] = (("length" | constant(hex"21")) :: ("blinding" | publicKey)).as[BlindingPoint] - private val endorsement: Codec[Endorsement] = tlvField(bool8) + private val endorsement: Codec[Endorsement] = tlvField(uint8.narrow[Endorsement](n => if (n >= 8) Attempt.failure(Err(s"invalid endorsement level: $n")) else Attempt.successful(Endorsement(n)), _.level)) val addHtlcTlvCodec: Codec[TlvStream[UpdateAddHtlcTlv]] = tlvStream(discriminated[UpdateAddHtlcTlv].by(varint) .typecase(UInt64(0), blindingPoint) - .typecase(UInt64(1), endorsement)) + .typecase(UInt64(106823), endorsement)) } sealed trait UpdateFulfillHtlcTlv extends Tlv diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala index af72ff3b26..8cde9545fd 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/wire/protocol/LightningMessageTypes.scala @@ -354,7 +354,7 @@ case class UpdateAddHtlc(channelId: ByteVector32, tlvStream: TlvStream[UpdateAddHtlcTlv]) extends HtlcMessage with UpdateMessage with HasChannelId { val blinding_opt: Option[PublicKey] = tlvStream.get[UpdateAddHtlcTlv.BlindingPoint].map(_.publicKey) - val endorsement: Boolean = tlvStream.get[UpdateAddHtlcTlv.Endorsement].forall(_.endorsed) + val endorsement: Int = tlvStream.get[UpdateAddHtlcTlv.Endorsement].map(_.level).getOrElse(0) /** When storing in our DB, we avoid wasting storage with unknown data. */ def removeUnknownTlvs(): UpdateAddHtlc = this.copy(tlvStream = tlvStream.copy(unknown = Set.empty)) @@ -369,7 +369,7 @@ object UpdateAddHtlc { onionRoutingPacket: OnionRoutingPacket, blinding_opt: Option[PublicKey], confidence: Double): UpdateAddHtlc = { - val tlvs: Set[UpdateAddHtlcTlv] = Set(blinding_opt.map(UpdateAddHtlcTlv.BlindingPoint), Some(UpdateAddHtlcTlv.Endorsement(confidence > 0.5))).flatten + val tlvs: Set[UpdateAddHtlcTlv] = Set(blinding_opt.map(UpdateAddHtlcTlv.BlindingPoint), Some(UpdateAddHtlcTlv.Endorsement((confidence * 7.999).toInt))).flatten UpdateAddHtlc(channelId, id, amountMsat, paymentHash, cltvExpiry, onionRoutingPacket, TlvStream(tlvs)) } } diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/reputation/ReputationRecorderSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/reputation/ReputationRecorderSpec.scala index a5b8bbb19b..d53cd5b27e 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/reputation/ReputationRecorderSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/reputation/ReputationRecorderSpec.scala @@ -45,26 +45,26 @@ class ReputationRecorderSpec extends ScalaTestWithActorTestKit(ConfigFactory.loa test("standard") { f => import f._ - reputationRecorder ! GetConfidence(replyTo.ref, originNode, isEndorsed = true, uuid1, 2000 msat) + reputationRecorder ! GetConfidence(replyTo.ref, originNode, 7, uuid1, 2000 msat) assert(replyTo.expectMessageType[Confidence].value == 0) - reputationRecorder ! RecordResult(originNode, isEndorsed = true, uuid1, isSuccess = true) - reputationRecorder ! GetConfidence(replyTo.ref, originNode, isEndorsed = true, uuid2, 1000 msat) + reputationRecorder ! RecordResult(originNode, 7, uuid1, isSuccess = true) + reputationRecorder ! GetConfidence(replyTo.ref, originNode, 7, uuid2, 1000 msat) assert(replyTo.expectMessageType[Confidence].value === (2.0 / 4) +- 0.001) - reputationRecorder ! GetConfidence(replyTo.ref, originNode, isEndorsed = true, uuid3, 3000 msat) + reputationRecorder ! GetConfidence(replyTo.ref, originNode, 7, uuid3, 3000 msat) assert(replyTo.expectMessageType[Confidence].value === (2.0 / 10) +- 0.001) - reputationRecorder ! CancelRelay(originNode, isEndorsed = true, uuid3) - reputationRecorder ! GetConfidence(replyTo.ref, originNode, isEndorsed = true, uuid4, 1000 msat) + reputationRecorder ! CancelRelay(originNode, 7, uuid3) + reputationRecorder ! GetConfidence(replyTo.ref, originNode, 7, uuid4, 1000 msat) assert(replyTo.expectMessageType[Confidence].value === (2.0 / 6) +- 0.001) - reputationRecorder ! RecordResult(originNode, isEndorsed = true, uuid4, isSuccess = true) - reputationRecorder ! RecordResult(originNode, isEndorsed = true, uuid2, isSuccess = false) + reputationRecorder ! RecordResult(originNode, 7, uuid4, isSuccess = true) + reputationRecorder ! RecordResult(originNode, 7, uuid2, isSuccess = false) // Not endorsed - reputationRecorder ! GetConfidence(replyTo.ref, originNode, isEndorsed = false, uuid5, 1000 msat) + reputationRecorder ! GetConfidence(replyTo.ref, originNode, 0, uuid5, 1000 msat) assert(replyTo.expectMessageType[Confidence].value == 0) // Different origin node - reputationRecorder ! GetConfidence(replyTo.ref, randomKey().publicKey, isEndorsed = true, uuid6, 1000 msat) + reputationRecorder ! GetConfidence(replyTo.ref, randomKey().publicKey, 7, uuid6, 1000 msat) assert(replyTo.expectMessageType[Confidence].value == 0) // Very large HTLC - reputationRecorder ! GetConfidence(replyTo.ref, originNode, isEndorsed = true, uuid5, 100000000 msat) + reputationRecorder ! GetConfidence(replyTo.ref, originNode, 7, uuid5, 100000000 msat) assert(replyTo.expectMessageType[Confidence].value === 0.0 +- 0.001) } @@ -73,25 +73,25 @@ class ReputationRecorderSpec extends ScalaTestWithActorTestKit(ConfigFactory.loa val (a, b, c) = (randomKey().publicKey, randomKey().publicKey, randomKey().publicKey) - reputationRecorder ! GetTrampolineConfidence(replyTo.ref, Map((a, true) -> 2000.msat, (b, true) -> 4000.msat, (c, false) -> 6000.msat), uuid1) + reputationRecorder ! GetTrampolineConfidence(replyTo.ref, Map((a, 7) -> 2000.msat, (b, 7) -> 4000.msat, (c, 0) -> 6000.msat), uuid1) assert(replyTo.expectMessageType[Confidence].value == 0) - reputationRecorder ! RecordTrampolineSuccess(Map((a, true) -> 1000.msat, (b, true) -> 2000.msat, (c, false) -> 3000.msat), uuid1) - reputationRecorder ! GetTrampolineConfidence(replyTo.ref, Map((a, true) -> 1000.msat, (c, false) -> 1000.msat), uuid2) + reputationRecorder ! RecordTrampolineSuccess(Map((a, 7) -> 1000.msat, (b, 7) -> 2000.msat, (c, 0) -> 3000.msat), uuid1) + reputationRecorder ! GetTrampolineConfidence(replyTo.ref, Map((a, 7) -> 1000.msat, (c, 0) -> 1000.msat), uuid2) assert(replyTo.expectMessageType[Confidence].value === (1.0 / 3) +- 0.001) - reputationRecorder ! GetTrampolineConfidence(replyTo.ref, Map((a, false) -> 1000.msat, (b, true) -> 2000.msat), uuid3) + reputationRecorder ! GetTrampolineConfidence(replyTo.ref, Map((a, 0) -> 1000.msat, (b, 7) -> 2000.msat), uuid3) assert(replyTo.expectMessageType[Confidence].value == 0) - reputationRecorder ! RecordTrampolineFailure(Set((a, true), (c, false)), uuid2) - reputationRecorder ! RecordTrampolineSuccess(Map((a, false) -> 1000.msat, (b, true) -> 2000.msat), uuid3) + reputationRecorder ! RecordTrampolineFailure(Set((a, 7), (c, 0)), uuid2) + reputationRecorder ! RecordTrampolineSuccess(Map((a, 0) -> 1000.msat, (b, 7) -> 2000.msat), uuid3) - reputationRecorder ! GetConfidence(replyTo.ref, a, isEndorsed = true, uuid4, 1000 msat) + reputationRecorder ! GetConfidence(replyTo.ref, a, 7, uuid4, 1000 msat) assert(replyTo.expectMessageType[Confidence].value === (1.0 / 4) +- 0.001) - reputationRecorder ! GetConfidence(replyTo.ref, a, isEndorsed = false, uuid5, 1000 msat) + reputationRecorder ! GetConfidence(replyTo.ref, a, 0, uuid5, 1000 msat) assert(replyTo.expectMessageType[Confidence].value === (1.0 / 3) +- 0.001) - reputationRecorder ! GetConfidence(replyTo.ref, b, isEndorsed = true, uuid6, 1000 msat) + reputationRecorder ! GetConfidence(replyTo.ref, b, 7, uuid6, 1000 msat) assert(replyTo.expectMessageType[Confidence].value === (4.0 / 6) +- 0.001) - reputationRecorder ! GetConfidence(replyTo.ref, b, isEndorsed = false, uuid7, 1000 msat) + reputationRecorder ! GetConfidence(replyTo.ref, b, 0, uuid7, 1000 msat) assert(replyTo.expectMessageType[Confidence].value == 0.0) - reputationRecorder ! GetConfidence(replyTo.ref, c, isEndorsed = false, uuid8, 1000 msat) + reputationRecorder ! GetConfidence(replyTo.ref, c, 0, uuid8, 1000 msat) assert(replyTo.expectMessageType[Confidence].value === (3.0 / 6) +- 0.001) } } \ No newline at end of file