Skip to content

Commit b8b5cb1

Browse files
committed
pending multiplier
1 parent aeae8cc commit b8b5cb1

7 files changed

Lines changed: 59 additions & 55 deletions

File tree

eclair-core/src/main/resources/reference.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,7 @@ eclair {
537537
local-reputation {
538538
max-weight-msat = 100000000000 # 1 BTC
539539
min-duration = 12 seconds
540+
pending-multiplier = 1000
540541
}
541542
}
542543

eclair-core/src/main/scala/fr/acinq/eclair/NodeParams.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,9 @@ object NodeParams extends Logging {
609609
purgeInvoicesInterval = purgeInvoicesInterval,
610610
localReputationConfig = ReputationConfig(MilliSatoshi(
611611
config.getLong("local-reputation.max-weight-msat")),
612-
FiniteDuration(config.getDuration("local-reputation.min-duration").getSeconds, TimeUnit.SECONDS)),
612+
FiniteDuration(config.getDuration("local-reputation.min-duration").getSeconds, TimeUnit.SECONDS),
613+
config.getDouble("local-reputation.pending-multiplier")
614+
),
613615
)
614616
}
615617
}

eclair-core/src/main/scala/fr/acinq/eclair/reputation/Reputation.scala

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ import fr.acinq.eclair.{MilliSatoshi, TimestampMilli}
2222
import java.util.UUID
2323
import scala.concurrent.duration.FiniteDuration
2424

25-
case class Reputation(pastWeight: Double, pending: Map[UUID, Pending], pastScore: Double, maxWeight: Double, minDuration: FiniteDuration) {
26-
private def pendingWeight(now: TimestampMilli): Double = pending.values.map(_.weight(now, minDuration)).sum
25+
case class Reputation(pastWeight: Double, pending: Map[UUID, Pending], pastScore: Double, maxWeight: Double, minDuration: FiniteDuration, pendingMultiplier: Double) {
26+
private def pendingWeight(now: TimestampMilli): Double = pending.values.map(_.weight(now, minDuration, pendingMultiplier)).sum
2727

2828
def confidence(now: TimestampMilli = TimestampMilli.now()): Double = pastScore / (pastWeight + pendingWeight(now))
2929

@@ -35,25 +35,25 @@ case class Reputation(pastWeight: Double, pending: Map[UUID, Pending], pastScore
3535
def record(relayId: UUID, isSuccess: Boolean, feeOverride: Option[MilliSatoshi] = None, now: TimestampMilli = TimestampMilli.now()): Reputation = {
3636
var p = pending.getOrElse(relayId, Pending(MilliSatoshi(0), now))
3737
feeOverride.foreach(fee => p = p.copy(fee = fee))
38-
val newWeight = pastWeight + p.weight(now, minDuration)
38+
val newWeight = pastWeight + p.weight(now, minDuration, 1.0)
3939
val newScore = if (isSuccess) pastScore + p.fee.toLong.toDouble else pastScore
4040
if (newWeight > maxWeight) {
41-
Reputation(maxWeight, pending - relayId, newScore * maxWeight / newWeight, maxWeight, minDuration)
41+
Reputation(maxWeight, pending - relayId, newScore * maxWeight / newWeight, maxWeight, minDuration, pendingMultiplier)
4242
} else {
43-
Reputation(newWeight, pending - relayId, newScore, maxWeight, minDuration)
43+
Reputation(newWeight, pending - relayId, newScore, maxWeight, minDuration, pendingMultiplier)
4444
}
4545
}
4646
}
4747

4848
object Reputation {
4949
case class Pending(fee: MilliSatoshi, startedAt: TimestampMilli) {
50-
def weight(now: TimestampMilli, minDuration: FiniteDuration): Double = {
50+
def weight(now: TimestampMilli, minDuration: FiniteDuration, pendingMultiplier: Double): Double = {
5151
val duration = now - startedAt
52-
fee.toLong.toDouble * (duration / minDuration).max(1.0)
52+
fee.toLong.toDouble * (duration / minDuration).max(pendingMultiplier)
5353
}
5454
}
5555

56-
case class ReputationConfig(maxWeight: MilliSatoshi, minDuration: FiniteDuration)
56+
case class ReputationConfig(maxWeight: MilliSatoshi, minDuration: FiniteDuration, pendingMultiplier: Double)
5757

58-
def init(config: ReputationConfig): Reputation = Reputation(0.0, Map.empty, 0.0, config.maxWeight.toLong.toDouble, config.minDuration)
58+
def init(config: ReputationConfig): Reputation = Reputation(0.0, Map.empty, 0.0, config.maxWeight.toLong.toDouble, config.minDuration, config.pendingMultiplier)
5959
}

eclair-core/src/test/scala/fr/acinq/eclair/TestConstants.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ object TestConstants {
226226
maxAttempts = 2,
227227
),
228228
purgeInvoicesInterval = None,
229-
localReputationConfig = ReputationConfig(1000000 msat, 10 seconds),
229+
localReputationConfig = ReputationConfig(1000000 msat, 10 seconds, 100),
230230
)
231231

232232
def channelParams: LocalParams = OpenChannelInterceptor.makeChannelParams(
@@ -392,7 +392,7 @@ object TestConstants {
392392
maxAttempts = 2,
393393
),
394394
purgeInvoicesInterval = None,
395-
localReputationConfig = ReputationConfig(2000000 msat, 20 seconds),
395+
localReputationConfig = ReputationConfig(2000000 msat, 20 seconds, 200),
396396
)
397397

398398
def channelParams: LocalParams = OpenChannelInterceptor.makeChannelParams(

eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalQuiescentStateSpec.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
147147
// initiator should reject commands that change the commitment once it became quiescent
148148
val sender1, sender2, sender3 = TestProbe()
149149
val cmds = Seq(
150-
CMD_ADD_HTLC(sender1.ref, 1_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, localOrigin(sender1.ref)),
150+
CMD_ADD_HTLC(sender1.ref, 1_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, 1.0, localOrigin(sender1.ref)),
151151
CMD_UPDATE_FEE(FeeratePerKw(100 sat), replyTo_opt = Some(sender2.ref)),
152152
CMD_CLOSE(sender3.ref, None, None)
153153
)
@@ -163,7 +163,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
163163
// both should reject commands that change the commitment while quiescent
164164
val sender1, sender2, sender3 = TestProbe()
165165
val cmds = Seq(
166-
CMD_ADD_HTLC(sender1.ref, 1_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, localOrigin(sender1.ref)),
166+
CMD_ADD_HTLC(sender1.ref, 1_000_000 msat, randomBytes32(), CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), TestConstants.emptyOnionPacket, None, 1.0, localOrigin(sender1.ref)),
167167
CMD_UPDATE_FEE(FeeratePerKw(100 sat), replyTo_opt = Some(sender2.ref)),
168168
CMD_CLOSE(sender3.ref, None, None)
169169
)
@@ -302,7 +302,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
302302
import f._
303303
val (preimage, add) = addHtlc(10_000 msat, bob, alice, bob2alice, alice2bob)
304304
crossSign(bob, alice, bob2alice, alice2bob)
305-
alice2relayer.expectMsg(RelayForward(add))
305+
alice2relayer.expectMsg(RelayForward(add, TestConstants.Bob.nodeParams.nodeId))
306306
initiateQuiescence(f, sendInitialStfu = true)
307307
val forbiddenMsg = UpdateFulfillHtlc(channelId(bob), add.id, preimage)
308308
// both parties will respond to a forbidden msg while quiescent with a warning (and disconnect)
@@ -343,7 +343,7 @@ class NormalQuiescentStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteL
343343
import f._
344344
initiateQuiescence(f, sendInitialStfu = true)
345345
// have to build a htlc manually because eclair would refuse to accept this command as it's forbidden
346-
val forbiddenMsg = UpdateAddHtlc(channelId = randomBytes32(), id = 5656, amountMsat = 50000000 msat, cltvExpiry = CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), paymentHash = randomBytes32(), onionRoutingPacket = TestConstants.emptyOnionPacket, blinding_opt = None)
346+
val forbiddenMsg = UpdateAddHtlc(channelId = randomBytes32(), id = 5656, amountMsat = 50000000 msat, cltvExpiry = CltvExpiryDelta(144).toCltvExpiry(currentBlockHeight), paymentHash = randomBytes32(), onionRoutingPacket = TestConstants.emptyOnionPacket, blinding_opt = None, confidence = 1.0)
347347
// both parties will respond to a forbidden msg while quiescent with a warning (and disconnect)
348348
bob2alice.forward(alice, forbiddenMsg)
349349
alice2bob.expectMsg(Warning(channelId(alice), ForbiddenDuringSplice(channelId(alice), "UpdateAddHtlc").getMessage))

eclair-core/src/test/scala/fr/acinq/eclair/reputation/ReputationRecorderSpec.scala

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class ReputationRecorderSpec extends ScalaTestWithActorTestKit(ConfigFactory.loa
3636
case class FixtureParam(config: ReputationConfig, reputationRecorder: ActorRef[Command], replyTo: TestProbe[Confidence])
3737

3838
override def withFixture(test: OneArgTest): Outcome = {
39-
val config = ReputationConfig(1000000000 msat, 10 seconds)
39+
val config = ReputationConfig(1000000000 msat, 10 seconds, 2)
4040
val replyTo = TestProbe[Confidence]("confidence")
4141
val reputationRecorder = testKit.spawn(ReputationRecorder(config, Map.empty))
4242
withFixture(test.toNoArgTest(FixtureParam(config, reputationRecorder.ref, replyTo)))
@@ -45,26 +45,26 @@ class ReputationRecorderSpec extends ScalaTestWithActorTestKit(ConfigFactory.loa
4545
test("standard") { f =>
4646
import f._
4747

48-
reputationRecorder ! GetConfidence(replyTo.ref, originNode, isEndorsed = true, uuid1, 1100 msat)
48+
reputationRecorder ! GetConfidence(replyTo.ref, originNode, isEndorsed = true, uuid1, 2000 msat)
4949
assert(replyTo.expectMessageType[Confidence].value == 0)
5050
reputationRecorder ! RecordResult(originNode, isEndorsed = true, uuid1, isSuccess = true)
51-
reputationRecorder ! GetConfidence(replyTo.ref, originNode, isEndorsed = true, uuid2, 1100 msat)
52-
assert(replyTo.expectMessageType[Confidence].value == 0.5)
53-
reputationRecorder ! GetConfidence(replyTo.ref, originNode, isEndorsed = true, uuid3, 1100 msat)
54-
assert(replyTo.expectMessageType[Confidence].value === 0.333 +- 0.001)
51+
reputationRecorder ! GetConfidence(replyTo.ref, originNode, isEndorsed = true, uuid2, 1000 msat)
52+
assert(replyTo.expectMessageType[Confidence].value === (2.0 / 4) +- 0.001)
53+
reputationRecorder ! GetConfidence(replyTo.ref, originNode, isEndorsed = true, uuid3, 3000 msat)
54+
assert(replyTo.expectMessageType[Confidence].value === (2.0 / 10) +- 0.001)
5555
reputationRecorder ! CancelRelay(originNode, isEndorsed = true, uuid3)
56-
reputationRecorder ! GetConfidence(replyTo.ref, originNode, isEndorsed = true, uuid4, 1100 msat)
57-
assert(replyTo.expectMessageType[Confidence].value === 0.333 +- 0.001)
56+
reputationRecorder ! GetConfidence(replyTo.ref, originNode, isEndorsed = true, uuid4, 1000 msat)
57+
assert(replyTo.expectMessageType[Confidence].value === (2.0 / 6) +- 0.001)
5858
reputationRecorder ! RecordResult(originNode, isEndorsed = true, uuid4, isSuccess = true)
5959
reputationRecorder ! RecordResult(originNode, isEndorsed = true, uuid2, isSuccess = false)
6060
// Not endorsed
61-
reputationRecorder ! GetConfidence(replyTo.ref, originNode, isEndorsed = false, uuid5, 1100 msat)
61+
reputationRecorder ! GetConfidence(replyTo.ref, originNode, isEndorsed = false, uuid5, 1000 msat)
6262
assert(replyTo.expectMessageType[Confidence].value == 0)
6363
// Different origin node
64-
reputationRecorder ! GetConfidence(replyTo.ref, randomKey().publicKey, isEndorsed = true, uuid6, 1100 msat)
64+
reputationRecorder ! GetConfidence(replyTo.ref, randomKey().publicKey, isEndorsed = true, uuid6, 1000 msat)
6565
assert(replyTo.expectMessageType[Confidence].value == 0)
6666
// Very large HTLC
67-
reputationRecorder ! GetConfidence(replyTo.ref, originNode, isEndorsed = true, uuid5, 10000000 msat)
67+
reputationRecorder ! GetConfidence(replyTo.ref, originNode, isEndorsed = true, uuid5, 100000000 msat)
6868
assert(replyTo.expectMessageType[Confidence].value === 0.0 +- 0.001)
6969
}
7070

@@ -73,25 +73,25 @@ class ReputationRecorderSpec extends ScalaTestWithActorTestKit(ConfigFactory.loa
7373

7474
val (a, b, c) = (randomKey().publicKey, randomKey().publicKey, randomKey().publicKey)
7575

76-
reputationRecorder ! GetTrampolineConfidence(replyTo.ref, Map((a, true) -> 1000.msat, (b, true) -> 2000.msat, (c, false) -> 3000.msat), uuid1)
76+
reputationRecorder ! GetTrampolineConfidence(replyTo.ref, Map((a, true) -> 2000.msat, (b, true) -> 4000.msat, (c, false) -> 6000.msat), uuid1)
7777
assert(replyTo.expectMessageType[Confidence].value == 0)
78-
reputationRecorder ! RecordTrampolineSuccess(Map((a, true) -> 500.msat, (b, true) -> 1000.msat, (c, false) -> 1500.msat), uuid1)
78+
reputationRecorder ! RecordTrampolineSuccess(Map((a, true) -> 1000.msat, (b, true) -> 2000.msat, (c, false) -> 3000.msat), uuid1)
7979
reputationRecorder ! GetTrampolineConfidence(replyTo.ref, Map((a, true) -> 1000.msat, (c, false) -> 1000.msat), uuid2)
80-
assert(replyTo.expectMessageType[Confidence].value === 0.333 +- 0.001)
80+
assert(replyTo.expectMessageType[Confidence].value === (1.0 / 3) +- 0.001)
8181
reputationRecorder ! GetTrampolineConfidence(replyTo.ref, Map((a, false) -> 1000.msat, (b, true) -> 2000.msat), uuid3)
8282
assert(replyTo.expectMessageType[Confidence].value == 0)
8383
reputationRecorder ! RecordTrampolineFailure(Set((a, true), (c, false)), uuid2)
8484
reputationRecorder ! RecordTrampolineSuccess(Map((a, false) -> 1000.msat, (b, true) -> 2000.msat), uuid3)
8585

8686
reputationRecorder ! GetConfidence(replyTo.ref, a, isEndorsed = true, uuid4, 1000 msat)
87-
assert(replyTo.expectMessageType[Confidence].value === 0.2 +- 0.001)
87+
assert(replyTo.expectMessageType[Confidence].value === (1.0 / 4) +- 0.001)
8888
reputationRecorder ! GetConfidence(replyTo.ref, a, isEndorsed = false, uuid5, 1000 msat)
89-
assert(replyTo.expectMessageType[Confidence].value === 0.5 +- 0.001)
89+
assert(replyTo.expectMessageType[Confidence].value === (1.0 / 3) +- 0.001)
9090
reputationRecorder ! GetConfidence(replyTo.ref, b, isEndorsed = true, uuid6, 1000 msat)
91-
assert(replyTo.expectMessageType[Confidence].value === 0.75 +- 0.001)
91+
assert(replyTo.expectMessageType[Confidence].value === (4.0 / 6) +- 0.001)
9292
reputationRecorder ! GetConfidence(replyTo.ref, b, isEndorsed = false, uuid7, 1000 msat)
9393
assert(replyTo.expectMessageType[Confidence].value == 0.0)
9494
reputationRecorder ! GetConfidence(replyTo.ref, c, isEndorsed = false, uuid8, 1000 msat)
95-
assert(replyTo.expectMessageType[Confidence].value === (3.0 / 7) +- 0.001)
95+
assert(replyTo.expectMessageType[Confidence].value === (3.0 / 6) +- 0.001)
9696
}
9797
}

eclair-core/src/test/scala/fr/acinq/eclair/reputation/ReputationSpec.scala

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,63 +28,64 @@ class ReputationSpec extends AnyFunSuite {
2828
val (uuid1, uuid2, uuid3, uuid4, uuid5, uuid6, uuid7) = (UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID())
2929

3030
test("basic") {
31-
var r = Reputation.init(ReputationConfig(1000000000 msat, 1 second))
31+
var r = Reputation.init(ReputationConfig(1000000000 msat, 1 second, 2))
3232
r = r.attempt(uuid1, 10000 msat)
3333
assert(r.confidence() == 0)
3434
r = r.record(uuid1, isSuccess = true)
3535
r = r.attempt(uuid2, 10000 msat)
36-
assert(r.confidence() == 0.5)
36+
assert(r.confidence() === (1.0 / 3) +- 0.001)
3737
r = r.attempt(uuid3, 10000 msat)
38-
assert(r.confidence() === 0.333 +- 0.001)
38+
assert(r.confidence() === (1.0 / 5) +- 0.001)
3939
r = r.record(uuid2, isSuccess = true)
4040
r = r.record(uuid3, isSuccess = true)
41+
assert(r.confidence() == 1)
4142
r = r.attempt(uuid4, 1 msat)
4243
assert(r.confidence() === 1.0 +- 0.001)
43-
r = r.attempt(uuid5, 90000 msat)
44-
assert(r.confidence() === 0.25 +- 0.001)
44+
r = r.attempt(uuid5, 40000 msat)
45+
assert(r.confidence() === (3.0 / 11) +- 0.001)
4546
r = r.attempt(uuid6, 10000 msat)
4647
assert(r.confidence() === (3.0 / 13) +- 0.001)
4748
r = r.cancel(uuid5)
48-
assert(r.confidence() === 0.75 +- 0.001)
49+
assert(r.confidence() === (3.0 / 5) +- 0.001)
4950
r = r.record(uuid6, isSuccess = false)
50-
assert(r.confidence() === 0.75 +- 0.001)
51+
assert(r.confidence() === (3.0 / 4) +- 0.001)
5152
r = r.attempt(uuid7, 10000 msat)
52-
assert(r.confidence() === 0.6 +- 0.001)
53+
assert(r.confidence() === (3.0 / 6) +- 0.001)
5354
}
5455

5556
test("long HTLC") {
56-
var r = Reputation.init(ReputationConfig(1000000000 msat, 1 second))
57+
var r = Reputation.init(ReputationConfig(1000000000 msat, 1 second, 10))
5758
r = r.attempt(uuid1, 100000 msat)
5859
assert(r.confidence() == 0)
5960
r = r.record(uuid1, isSuccess = true)
6061
assert(r.confidence() == 1)
6162
r = r.attempt(uuid2, 1000 msat, TimestampMilli(0))
62-
assert(r.confidence(TimestampMilli(0)) === 0.99 +- 0.001)
63+
assert(r.confidence(TimestampMilli(0)) === (10.0 / 11) +- 0.001)
6364
assert(r.confidence(TimestampMilli(0) + 100.seconds) == 0.5)
6465
r = r.record(uuid2, isSuccess = false, now = TimestampMilli(0) + 100.seconds)
6566
assert(r.confidence() == 0.5)
6667
}
6768

6869
test("max weight") {
69-
var r = Reputation.init(ReputationConfig(1000000 msat, 1 second))
70+
var r = Reputation.init(ReputationConfig(100 msat, 1 second, 10))
7071
// build perfect reputation
7172
for(i <- 1 to 100){
7273
val uuid = UUID.randomUUID()
73-
r = r.attempt(uuid, 100000 msat)
74+
r = r.attempt(uuid, 10 msat)
7475
r = r.record(uuid, isSuccess = true)
7576
}
7677
assert(r.confidence() == 1)
77-
r = r.attempt(uuid1, 100000 msat)
78-
assert(r.confidence() === 0.91 +- 0.01)
78+
r = r.attempt(uuid1, 1 msat)
79+
assert(r.confidence() === (100.0 / 110) +- 0.001)
7980
r = r.record(uuid1, isSuccess = false)
80-
assert(r.confidence() === 0.91 +- 0.01)
81-
r = r.attempt(uuid2, 100000 msat)
82-
assert(r.confidence() === 0.83 +- 0.01)
81+
assert(r.confidence() === (100.0 / 101) +- 0.001)
82+
r = r.attempt(uuid2, 1 msat)
83+
assert(r.confidence() === (100.0 / 101) * (100.0 / 110) +- 0.001)
8384
r = r.record(uuid2, isSuccess = false)
84-
assert(r.confidence() === 0.83 +- 0.01)
85-
r = r.attempt(uuid3, 100000 msat)
86-
assert(r.confidence() === 0.75 +- 0.01)
85+
assert(r.confidence() === (100.0 / 101) * (100.0 / 101) +- 0.001)
86+
r = r.attempt(uuid3, 1 msat)
87+
assert(r.confidence() === (100.0 / 101) * (100.0 / 101) * (100.0 / 110) +- 0.001)
8788
r = r.record(uuid3, isSuccess = false)
88-
assert(r.confidence() === 0.75 +- 0.01)
89+
assert(r.confidence() === (100.0 / 101) * (100.0 / 101) * (100.0 / 101) +- 0.001)
8990
}
9091
}

0 commit comments

Comments
 (0)