Skip to content

Commit 5ec4b6b

Browse files
committed
WIP
1 parent 3d3437c commit 5ec4b6b

File tree

7 files changed

+141
-93
lines changed

7 files changed

+141
-93
lines changed

.mvn/maven.config

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# trusted checksum source setup
2+
-Daether.trustedChecksumsSource.summaryFile=true
3+
-Daether.trustedChecksumsSource.summaryFile.basedir=${session.rootDirectory}/.mvn/checksums/
4+
# post processor: trusted checksums
5+
-Daether.artifactResolver.postProcessor.trustedChecksums=true
6+
-Daether.artifactResolver.postProcessor.trustedChecksums.checksumAlgorithms=SHA-256
7+
-Daether.artifactResolver.postProcessor.trustedChecksums.failIfMissing=true
8+
-Daether.artifactResolver.postProcessor.trustedChecksums.snapshots=false

eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelExceptions.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ case class RemoteError(e: protocol.Error) extends ChannelError
3434
// @formatter:on
3535

3636
class ChannelException(val channelId: ByteVector32, message: String) extends RuntimeException(message)
37+
class ChannelJammingException(override val channelId: ByteVector32, message: String) extends ChannelException(channelId, message)
3738

3839
// @formatter:off
3940
case class InvalidChainHash (override val channelId: ByteVector32, local: BlockHash, remote: BlockHash) extends ChannelException(channelId, s"invalid chainHash (local=$local remote=$remote)")
@@ -130,8 +131,6 @@ case class ExpiryTooBig (override val channelId: Byte
130131
case class HtlcValueTooSmall (override val channelId: ByteVector32, minimum: MilliSatoshi, actual: MilliSatoshi) extends ChannelException(channelId, s"htlc value too small: minimum=$minimum actual=$actual")
131132
case class HtlcValueTooHighInFlight (override val channelId: ByteVector32, maximum: MilliSatoshi, actual: MilliSatoshi) extends ChannelException(channelId, s"in-flight htlcs hold too much value: maximum=$maximum actual=$actual")
132133
case class TooManyAcceptedHtlcs (override val channelId: ByteVector32, maximum: Long) extends ChannelException(channelId, s"too many accepted htlcs: maximum=$maximum")
133-
case class TooManySmallHtlcs (override val channelId: ByteVector32, number: Long, below: MilliSatoshi) extends ChannelException(channelId, s"too many small htlcs: $number HTLCs below $below")
134-
case class ConfidenceTooLow (override val channelId: ByteVector32, confidence: Double, occupancy: Double) extends ChannelException(channelId, s"confidence too low: confidence=$confidence occupancy=$occupancy")
135134
case class LocalDustHtlcExposureTooHigh (override val channelId: ByteVector32, maximum: Satoshi, actual: MilliSatoshi) extends ChannelException(channelId, s"dust htlcs hold too much value: maximum=$maximum actual=$actual")
136135
case class RemoteDustHtlcExposureTooHigh (override val channelId: ByteVector32, maximum: Satoshi, actual: MilliSatoshi) extends ChannelException(channelId, s"dust htlcs hold too much value: maximum=$maximum actual=$actual")
137136
case class InsufficientFunds (override val channelId: ByteVector32, amount: MilliSatoshi, missing: Satoshi, reserve: Satoshi, fees: Satoshi) extends ChannelException(channelId, s"insufficient funds: missing=$missing reserve=$reserve fees=$fees")
@@ -152,4 +151,6 @@ case class CommandUnavailableInThisState (override val channelId: Byte
152151
case class ForbiddenDuringSplice (override val channelId: ByteVector32, command: String) extends ChannelException(channelId, s"cannot process $command while splicing")
153152
case class ForbiddenDuringQuiescence (override val channelId: ByteVector32, command: String) extends ChannelException(channelId, s"cannot process $command while quiescent")
154153
case class ConcurrentRemoteSplice (override val channelId: ByteVector32) extends ChannelException(channelId, "splice attempt canceled, remote initiated splice before us")
154+
case class TooManySmallHtlcs (override val channelId: ByteVector32, number: Long, below: MilliSatoshi) extends ChannelJammingException(channelId, s"too many small htlcs: $number HTLCs below $below")
155+
case class ConfidenceTooLow (override val channelId: ByteVector32, confidence: Double, occupancy: Double) extends ChannelJammingException(channelId, s"confidence too low: confidence=$confidence occupancy=$occupancy")
155156
// @formatter:on

eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -887,24 +887,22 @@ case class Commitments(params: ChannelParams,
887887
val originChannels1 = originChannels + (add.id -> cmd.origin)
888888
// we verify that this htlc is allowed in every active commitment
889889
val canSendAdds = active.map(_.canSendAdd(add.amountMsat, params, changes1, feerates, feeConf, cmd.confidence))
890-
// Log only for jamming protection.
891-
canSendAdds.collectFirst {
892-
case Left(f: TooManySmallHtlcs) =>
893-
log.info("TooManySmallHtlcs: {} outgoing HTLCs are below {}}", f.number, f.below)
894-
Metrics.dropHtlc(f, Tags.Directions.Outgoing)
895-
case Left(f: ConfidenceTooLow) =>
896-
log.info("ConfidenceTooLow: confidence is {}% while channel is {}% full", (100 * f.confidence).toInt, (100 * f.occupancy).toInt)
897-
Metrics.dropHtlc(f, Tags.Directions.Outgoing)
898-
}
899-
canSendAdds.flatMap { // TODO: We ignore jamming protection, delete this flatMap to activate jamming protection.
900-
case Left(_: TooManySmallHtlcs) | Left(_: ConfidenceTooLow) => None
901-
case x => Some(x)
902-
}
903-
.collectFirst { case Left(f) =>
890+
val result = canSendAdds.collectFirst { case Left(f) if !f.isInstanceOf[ChannelJammingException] => // We ignore jamming protection. TODO: enable jamming protection
904891
Metrics.dropHtlc(f, Tags.Directions.Outgoing)
905892
Left(f)
893+
}.getOrElse(Right(copy(changes = changes1, originChannels = originChannels1), add))
894+
// Jamming protection is disabled but we still log which HTLCs would be dropped if it was enabled.
895+
if (result.isRight) {
896+
canSendAdds.collectFirst {
897+
case Left(f: TooManySmallHtlcs) =>
898+
log.info("TooManySmallHtlcs: {} outgoing HTLCs are below {}}", f.number, f.below)
899+
Metrics.dropHtlc(f, Tags.Directions.Outgoing)
900+
case Left(f: ConfidenceTooLow) =>
901+
log.info("ConfidenceTooLow: confidence is {}% while channel is {}% full", (100 * f.confidence).toInt, (100 * f.occupancy).toInt)
902+
Metrics.dropHtlc(f, Tags.Directions.Outgoing)
906903
}
907-
.getOrElse(Right(copy(changes = changes1, originChannels = originChannels1), add))
904+
}
905+
result
908906
}
909907

910908
def receiveAdd(add: UpdateAddHtlc, feerates: FeeratesPerKw, feeConf: OnChainFeeConf): Either[ChannelException, Commitments] = {

eclair-core/src/main/scala/fr/acinq/eclair/payment/relay/NodeRelay.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,7 @@ class NodeRelay private(nodeParams: NodeParams,
521521
context.system.eventStream ! EventStream.Publish(TrampolinePaymentRelayed(paymentHash, incoming, outgoing, paymentSent.recipientNodeId, paymentSent.recipientAmount))
522522
}
523523

524-
private def recordRelayDuration(receivedAt: TimestampMilli, isSuccess: Boolean): Unit = // TODO: always record reputation and duration before stopping, everything in one function
524+
private def recordRelayDuration(receivedAt: TimestampMilli, isSuccess: Boolean): Unit =
525525
Metrics.RelayedPaymentDuration
526526
.withTag(Tags.Relay, Tags.RelayType.Trampoline)
527527
.withTag(Tags.Success, isSuccess)

eclair-core/src/test/scala/fr/acinq/eclair/payment/relay/ChannelRelayerSpec.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -689,9 +689,7 @@ class ChannelRelayerSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("a
689689
val fwd = expectFwdAdd(register, channelIds(realScid1), outgoingAmount, outgoingExpiry, 7)
690690
fwd.message.replyTo ! RES_SUCCESS(fwd.message, channelId1)
691691
fwd.message.origin.replyTo ! RES_ADD_SETTLED(fwd.message.origin, downstream_htlc, testCase.result)
692-
val fwdFail = register.expectMessageType[Register.Forward[channel.Command]]
693-
assert(fwdFail.message == testCase.cmd)
694-
assert(fwdFail.channelId == r.add.channelId)
692+
expectFwdFail(register, r.add.channelId, testCase.cmd, reputationRecorder)
695693
}
696694
}
697695

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

Lines changed: 79 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,19 @@ package fr.acinq.eclair.reputation
1818

1919
import akka.actor.testkit.typed.scaladsl.{ScalaTestWithActorTestKit, TestProbe}
2020
import akka.actor.typed.ActorRef
21+
import akka.actor.typed.eventstream.EventStream
2122
import com.typesafe.config.ConfigFactory
2223
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
23-
import fr.acinq.eclair.channel.Upstream
24+
import fr.acinq.eclair.channel.{OutgoingHtlcAdded, OutgoingHtlcFailed, OutgoingHtlcFulfilled, Upstream}
2425
import fr.acinq.eclair.reputation.ReputationRecorder._
25-
import fr.acinq.eclair.wire.protocol.UpdateAddHtlc
26-
import fr.acinq.eclair.{MilliSatoshiLong, randomKey}
26+
import fr.acinq.eclair.wire.protocol.{TlvStream, UpdateAddHtlc, UpdateAddHtlcTlv, UpdateFailHtlc, UpdateFulfillHtlc}
27+
import fr.acinq.eclair.{CltvExpiry, MilliSatoshi, MilliSatoshiLong, TimestampMilli, randomBytes, randomBytes32, randomKey, randomLong}
2728
import org.scalatest.Outcome
2829
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
2930

30-
import java.util.UUID
3131
import scala.concurrent.duration.DurationInt
3232

3333
class ReputationRecorderSpec extends ScalaTestWithActorTestKit(ConfigFactory.load("application")) with FixtureAnyFunSuiteLike {
34-
val (uuid1, uuid2, uuid3, uuid4, uuid5, uuid6, uuid7, uuid8) = (UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID())
3534
val originNode: PublicKey = randomKey().publicKey
3635

3736
case class FixtureParam(config: Reputation.Config, reputationRecorder: ActorRef[Command], replyTo: TestProbe[Confidence])
@@ -43,56 +42,107 @@ class ReputationRecorderSpec extends ScalaTestWithActorTestKit(ConfigFactory.loa
4342
withFixture(test.toNoArgTest(FixtureParam(config, reputationRecorder.ref, replyTo)))
4443
}
4544

45+
def makeChannelUpstream(nodeId: PublicKey, endorsement: Int, amount: MilliSatoshi = 1000000 msat): Upstream.Hot.Channel =
46+
Upstream.Hot.Channel(UpdateAddHtlc(randomBytes32(), randomLong(), amount, randomBytes32(), CltvExpiry(1234), null, TlvStream(UpdateAddHtlcTlv.Endorsement(endorsement))), TimestampMilli.now(), nodeId)
47+
48+
def makeOutgoingHtlcAdded(upstream: Upstream.Hot, fee: MilliSatoshi): OutgoingHtlcAdded =
49+
OutgoingHtlcAdded(UpdateAddHtlc(randomBytes32(), randomLong(), 100000 msat, randomBytes32(), CltvExpiry(456), null, TlvStream.empty), upstream, fee)
50+
51+
def makeOutgoingHtlcFulfilled(add: UpdateAddHtlc): OutgoingHtlcFulfilled =
52+
OutgoingHtlcFulfilled(UpdateFulfillHtlc(add.channelId, add.id, randomBytes32(), TlvStream.empty))
53+
54+
def makeOutgoingHtlcFailed(add: UpdateAddHtlc): OutgoingHtlcFailed =
55+
OutgoingHtlcFailed(UpdateFailHtlc(add.channelId, add.id, randomBytes(100), TlvStream.empty))
56+
4657
test("channel relay") { f =>
4758
import f._
4859

49-
reputationRecorder ! GetConfidence(replyTo.ref, originNode, 7, uuid1, 2000 msat)
60+
val listener = TestProbe[Any]()
61+
testKit.system.eventStream ! EventStream.Subscribe(listener.ref)
62+
testKit.system.eventStream ! EventStream.Subscribe(listener.ref)
63+
testKit.system.eventStream ! EventStream.Subscribe(listener.ref)
64+
65+
val upstream1 = makeChannelUpstream(originNode, 7)
66+
reputationRecorder ! GetConfidence(replyTo.ref, upstream1, 2000 msat)
5067
assert(replyTo.expectMessageType[Confidence].value == 0)
51-
reputationRecorder ! RecordResult(originNode, 7, uuid1, isSuccess = true)
52-
reputationRecorder ! GetConfidence(replyTo.ref, originNode, 7, uuid2, 1000 msat)
68+
val added1 = makeOutgoingHtlcAdded(upstream1, 2000 msat)
69+
testKit.system.eventStream ! EventStream.Publish(added1)
70+
testKit.system.eventStream ! EventStream.Publish(makeOutgoingHtlcFulfilled(added1.add))
71+
listener.expectMessageType[OutgoingHtlcAdded]
72+
listener.expectMessageType[OutgoingHtlcFulfilled]
73+
val upstream2 = makeChannelUpstream(originNode, 7)
74+
reputationRecorder ! GetConfidence(replyTo.ref, upstream2, 1000 msat)
5375
assert(replyTo.expectMessageType[Confidence].value === (2.0 / 4) +- 0.001)
54-
reputationRecorder ! GetConfidence(replyTo.ref, originNode, 7, uuid3, 3000 msat)
76+
val added2 = makeOutgoingHtlcAdded(upstream2, 1000 msat)
77+
testKit.system.eventStream ! EventStream.Publish(added2)
78+
listener.expectMessageType[OutgoingHtlcAdded]
79+
reputationRecorder ! GetConfidence(replyTo.ref, makeChannelUpstream(originNode, 7), 3000 msat)
5580
assert(replyTo.expectMessageType[Confidence].value === (2.0 / 10) +- 0.001)
56-
reputationRecorder ! CancelRelay(originNode, 7, uuid3)
57-
reputationRecorder ! GetConfidence(replyTo.ref, originNode, 7, uuid4, 1000 msat)
81+
val upstream3 = makeChannelUpstream(originNode, 7)
82+
reputationRecorder ! GetConfidence(replyTo.ref, upstream3, 1000 msat)
5883
assert(replyTo.expectMessageType[Confidence].value === (2.0 / 6) +- 0.001)
59-
reputationRecorder ! RecordResult(originNode, 7, uuid4, isSuccess = true)
60-
reputationRecorder ! RecordResult(originNode, 7, uuid2, isSuccess = false)
84+
val added3 = makeOutgoingHtlcAdded(upstream3, 1000 msat)
85+
testKit.system.eventStream ! EventStream.Publish(added3)
86+
testKit.system.eventStream ! EventStream.Publish(makeOutgoingHtlcFulfilled(added3.add))
87+
testKit.system.eventStream ! EventStream.Publish(makeOutgoingHtlcFailed(added2.add))
88+
listener.expectMessageType[OutgoingHtlcAdded]
89+
listener.expectMessageType[OutgoingHtlcFulfilled]
90+
listener.expectMessageType[OutgoingHtlcFailed]
6191
// Not endorsed
62-
reputationRecorder ! GetConfidence(replyTo.ref, originNode, 0, uuid5, 1000 msat)
92+
reputationRecorder ! GetConfidence(replyTo.ref, makeChannelUpstream(originNode, 0), 1000 msat)
6393
assert(replyTo.expectMessageType[Confidence].value == 0)
6494
// Different origin node
65-
reputationRecorder ! GetConfidence(replyTo.ref, randomKey().publicKey, 7, uuid6, 1000 msat)
95+
reputationRecorder ! GetConfidence(replyTo.ref, makeChannelUpstream(randomKey().publicKey, 7), 1000 msat)
6696
assert(replyTo.expectMessageType[Confidence].value == 0)
6797
// Very large HTLC
68-
reputationRecorder ! GetConfidence(replyTo.ref, originNode, 7, uuid5, 100000000 msat)
98+
reputationRecorder ! GetConfidence(replyTo.ref, makeChannelUpstream(originNode, 7), 100000000 msat)
6999
assert(replyTo.expectMessageType[Confidence].value === 0.0 +- 0.001)
70100
}
71101

72102
test("trampoline relay") { f =>
73103
import f._
74104

105+
val listener = TestProbe[Any]()
106+
testKit.system.eventStream ! EventStream.Subscribe(listener.ref)
107+
testKit.system.eventStream ! EventStream.Subscribe(listener.ref)
108+
testKit.system.eventStream ! EventStream.Subscribe(listener.ref)
109+
75110
val (a, b, c) = (randomKey().publicKey, randomKey().publicKey, randomKey().publicKey)
76111

77-
reputationRecorder ! GetTrampolineConfidence(replyTo.ref, Map(PeerEndorsement(a, 7) -> 2000.msat, PeerEndorsement(b, 7) -> 4000.msat, PeerEndorsement(c, 0) -> 6000.msat), uuid1)
112+
val upstream1 = Upstream.Hot.Trampoline(makeChannelUpstream(a, 7, 20000 msat) :: makeChannelUpstream(b, 7, 40000 msat) :: makeChannelUpstream(c, 0, 60000 msat) :: Nil)
113+
reputationRecorder ! GetTrampolineConfidence(replyTo.ref, upstream1, 12000 msat)
78114
assert(replyTo.expectMessageType[Confidence].value == 0)
79-
reputationRecorder ! RecordTrampolineSuccess(Map(PeerEndorsement(a, 7) -> 1000.msat, PeerEndorsement(b, 7) -> 2000.msat, PeerEndorsement(c, 0) -> 3000.msat), uuid1)
80-
reputationRecorder ! GetTrampolineConfidence(replyTo.ref, Map(PeerEndorsement(a, 7) -> 1000.msat, PeerEndorsement(c, 0) -> 1000.msat), uuid2)
115+
val added1 = makeOutgoingHtlcAdded(upstream1, 6000 msat)
116+
testKit.system.eventStream ! EventStream.Publish(added1)
117+
testKit.system.eventStream ! EventStream.Publish(makeOutgoingHtlcFulfilled(added1.add))
118+
listener.expectMessageType[OutgoingHtlcAdded]
119+
listener.expectMessageType[OutgoingHtlcFulfilled]
120+
val upstream2 = Upstream.Hot.Trampoline(makeChannelUpstream(a, 7, 10000 msat) :: makeChannelUpstream(c, 0, 10000 msat) :: Nil)
121+
reputationRecorder ! GetTrampolineConfidence(replyTo.ref, upstream2, 2000 msat)
81122
assert(replyTo.expectMessageType[Confidence].value === (1.0 / 3) +- 0.001)
82-
reputationRecorder ! GetTrampolineConfidence(replyTo.ref, Map(PeerEndorsement(a, 0) -> 1000.msat, PeerEndorsement(b, 7) -> 2000.msat), uuid3)
123+
val added2 = makeOutgoingHtlcAdded(upstream2, 2000 msat)
124+
testKit.system.eventStream ! EventStream.Publish(added2)
125+
listener.expectMessageType[OutgoingHtlcAdded]
126+
val upstream3 = Upstream.Hot.Trampoline(makeChannelUpstream(a, 0, 10000 msat) :: makeChannelUpstream(b, 7, 20000 msat) :: Nil)
127+
reputationRecorder ! GetTrampolineConfidence(replyTo.ref, upstream3, 3000 msat)
83128
assert(replyTo.expectMessageType[Confidence].value == 0)
84-
reputationRecorder ! RecordTrampolineFailure(Set(PeerEndorsement(a, 7), PeerEndorsement(c, 0)), uuid2)
85-
reputationRecorder ! RecordTrampolineSuccess(Map(PeerEndorsement(a, 0) -> 1000.msat, PeerEndorsement(b, 7) -> 2000.msat), uuid3)
129+
val added3 = makeOutgoingHtlcAdded(upstream3, 3000 msat)
130+
testKit.system.eventStream ! EventStream.Publish(added3)
131+
testKit.system.eventStream ! EventStream.Publish(makeOutgoingHtlcFailed(added2.add))
132+
testKit.system.eventStream ! EventStream.Publish(makeOutgoingHtlcFulfilled(added3.add))
133+
listener.expectMessageType[OutgoingHtlcAdded]
134+
listener.expectMessageType[OutgoingHtlcFailed]
135+
listener.expectMessageType[OutgoingHtlcFulfilled]
86136

87-
reputationRecorder ! GetConfidence(replyTo.ref, a, 7, uuid4, 1000 msat)
88-
assert(replyTo.expectMessageType[Confidence].value === (1.0 / 4) +- 0.001)
89-
reputationRecorder ! GetConfidence(replyTo.ref, a, 0, uuid5, 1000 msat)
137+
reputationRecorder ! GetConfidence(replyTo.ref, makeChannelUpstream(a, 7), 1000 msat)
138+
assert(replyTo.expectMessageType[Confidence].value === (1.0 / 3) +- 0.001)
139+
reputationRecorder ! GetConfidence(replyTo.ref, makeChannelUpstream(a, 0), 1000 msat)
90140
assert(replyTo.expectMessageType[Confidence].value === (1.0 / 3) +- 0.001)
91-
reputationRecorder ! GetConfidence(replyTo.ref, b, 7, uuid6, 1000 msat)
141+
reputationRecorder ! GetConfidence(replyTo.ref, makeChannelUpstream(b, 7), 1000 msat)
92142
assert(replyTo.expectMessageType[Confidence].value === (4.0 / 6) +- 0.001)
93-
reputationRecorder ! GetConfidence(replyTo.ref, b, 0, uuid7, 1000 msat)
143+
reputationRecorder ! GetConfidence(replyTo.ref, makeChannelUpstream(b, 0), 1000 msat)
94144
assert(replyTo.expectMessageType[Confidence].value == 0.0)
95-
reputationRecorder ! GetConfidence(replyTo.ref, c, 0, uuid8, 1000 msat)
96-
assert(replyTo.expectMessageType[Confidence].value === (3.0 / 6) +- 0.001)
145+
reputationRecorder ! GetConfidence(replyTo.ref, makeChannelUpstream(c, 0), 1000 msat)
146+
assert(replyTo.expectMessageType[Confidence].value === (3.0 / 5) +- 0.001)
97147
}
98148
}

0 commit comments

Comments
 (0)