Skip to content

Commit

Permalink
Use 3 bits
Browse files Browse the repository at this point in the history
  • Loading branch information
thomash-acinq committed Jun 28, 2024
1 parent 96be77b commit 0136caf
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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._

Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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)
}
}

0 comments on commit 0136caf

Please sign in to comment.