diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/DBCompatChecker.scala b/eclair-core/src/main/scala/fr/acinq/eclair/DBCompatChecker.scala
index 9a3b7ef8e..9ba71086f 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/DBCompatChecker.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/DBCompatChecker.scala
@@ -39,7 +39,7 @@ object DBCompatChecker extends Logging {
* @param nodeParams
*/
def checkNetworkDBCompatibility(nodeParams: NodeParams): Unit =
- Try(nodeParams.db.network.listChannels(), nodeParams.db.network.listNodes()) match {
+ Try(nodeParams.db.network.listChannels(nodeParams.chainHash), nodeParams.db.network.listNodes()) match {
case Success(_) => {}
case Failure(_) => throw IncompatibleNetworkDBException
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala
index 28e496d7f..33edcb623 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/ChaCha20Poly1305.scala
@@ -22,8 +22,8 @@ import fr.acinq.bitcoin.{ByteVector32, Protocol}
import fr.acinq.eclair.crypto.ChaCha20Poly1305.{DecryptionError, EncryptionError, InvalidCounter}
import grizzled.slf4j.Logger
import grizzled.slf4j.Logging
-import org.spongycastle.crypto.engines.ChaCha7539Engine
-import org.spongycastle.crypto.params.{KeyParameter, ParametersWithIV}
+import org.bouncycastle.crypto.engines.ChaCha7539Engine
+import org.bouncycastle.crypto.params.{KeyParameter, ParametersWithIV}
import scodec.bits.ByteVector
/**
@@ -39,7 +39,7 @@ object Poly1305 {
*/
def mac(key: ByteVector, datas: ByteVector*): ByteVector = {
val out = new Array[Byte](16)
- val poly = new org.spongycastle.crypto.macs.Poly1305()
+ val poly = new org.bouncycastle.crypto.macs.Poly1305()
poly.init(new KeyParameter(key.toArray))
datas.foreach(data => poly.update(data.toArray, 0, data.length.toInt))
poly.doFinal(out, 0)
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Mac.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Mac.scala
index 494cd1020..62e50100f 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Mac.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Mac.scala
@@ -17,9 +17,9 @@
package fr.acinq.eclair.crypto
import fr.acinq.bitcoin.ByteVector32
-import org.spongycastle.crypto.digests.SHA256Digest
-import org.spongycastle.crypto.macs.HMac
-import org.spongycastle.crypto.params.KeyParameter
+import org.bouncycastle.crypto.digests.SHA256Digest
+import org.bouncycastle.crypto.macs.HMac
+import org.bouncycastle.crypto.params.KeyParameter
import scodec.bits.ByteVector
/**
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Noise.scala b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Noise.scala
index ceed60483..3502a1a37 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Noise.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/crypto/Noise.scala
@@ -23,9 +23,9 @@ import fr.acinq.bitcoin.Crypto.PrivateKey
import fr.acinq.bitcoin.{Crypto, Protocol}
import fr.acinq.eclair.randomBytes
import grizzled.slf4j.Logging
-import org.spongycastle.crypto.digests.SHA256Digest
-import org.spongycastle.crypto.macs.HMac
-import org.spongycastle.crypto.params.KeyParameter
+import org.bouncycastle.crypto.digests.SHA256Digest
+import org.bouncycastle.crypto.macs.HMac
+import org.bouncycastle.crypto.params.KeyParameter
import scodec.bits.ByteVector
/**
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/NetworkDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/NetworkDb.scala
index eca888c0e..d1d34871b 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/NetworkDb.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/NetworkDb.scala
@@ -40,13 +40,13 @@ trait NetworkDb extends Closeable {
def addChannel(c: ChannelAnnouncement, txid: ByteVector32, capacity: Satoshi): Unit
- def updateChannel(u: ChannelUpdate): Unit
+ def updateChannel(chainHash: ByteVector32, u: ChannelUpdate): Unit
def removeChannel(shortChannelId: ShortChannelId) = removeChannels(Set(shortChannelId)): Unit
def removeChannels(shortChannelIds: Iterable[ShortChannelId]): Unit
- def listChannels(): SortedMap[ShortChannelId, PublicChannel]
+ def listChannels(chainHash: ByteVector32): SortedMap[ShortChannelId, PublicChannel]
def addToPruned(shortChannelIds: Iterable[ShortChannelId]): Unit
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteNetworkDb.scala b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteNetworkDb.scala
index fd5a64dc2..03b558534 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteNetworkDb.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/db/sqlite/SqliteNetworkDb.scala
@@ -59,8 +59,11 @@ class SqliteNetworkDb(sqlite: Connection) extends NetworkDb with Logging {
("bitcoinSignature2" | provide(null.asInstanceOf[ByteVector64])) ::
channelAnnouncementWitnessCodec).as[ChannelAnnouncement]
- val channelUpdateWitnessCodec =
- ("chainHash" | provide(null.asInstanceOf[ByteVector32])) ::
+ /**
+ * we override the chain hash because we don't write it but we need it at runtime to compute channel queries checksums
+ */
+ def channelUpdateWitnessCodec(chainHash: ByteVector32) =
+ ("chainHash" | provide(chainHash)) ::
("shortChannelId" | shortchannelid) ::
("timestamp" | uint32) ::
(("messageFlags" | byte) >>:~ { messageFlags =>
@@ -73,9 +76,9 @@ class SqliteNetworkDb(sqlite: Connection) extends NetworkDb with Logging {
("unknownFields" | bytes)
})
- val channelUpdateCodec: Codec[ChannelUpdate] = (
+ def channelUpdateCodec(chainHash: ByteVector32): Codec[ChannelUpdate] = (
("signature" | provide(null.asInstanceOf[ByteVector64])) ::
- channelUpdateWitnessCodec).as[ChannelUpdate]
+ channelUpdateWitnessCodec(chainHash)).as[ChannelUpdate]
using(sqlite.createStatement(), inTransaction = true) { statement =>
getVersion(statement, DB_NAME, CURRENT_VERSION) match {
@@ -145,16 +148,16 @@ class SqliteNetworkDb(sqlite: Connection) extends NetworkDb with Logging {
}
}
- override def updateChannel(u: ChannelUpdate): Unit = withMetrics("network/update-channel") {
+ override def updateChannel(chainHash: ByteVector32, u: ChannelUpdate): Unit = withMetrics("network/update-channel") {
val column = if (u.isNode1) "channel_update_1" else "channel_update_2"
using(sqlite.prepareStatement(s"UPDATE channels SET $column=? WHERE short_channel_id=?")) { statement =>
- statement.setBytes(1, channelUpdateCodec.encode(u).require.toByteArray)
+ statement.setBytes(1, channelUpdateCodec(chainHash).encode(u).require.toByteArray)
statement.setLong(2, u.shortChannelId.toLong)
statement.executeUpdate()
}
}
- override def listChannels(): SortedMap[ShortChannelId, PublicChannel] = withMetrics("network/list-channels") {
+ override def listChannels(chainHash: ByteVector32): SortedMap[ShortChannelId, PublicChannel] = withMetrics("network/list-channels") {
using(sqlite.createStatement()) { statement =>
val rs = statement.executeQuery("SELECT channel_announcement, txid, capacity_sat, channel_update_1, channel_update_2 FROM channels")
var m = SortedMap.empty[ShortChannelId, PublicChannel]
@@ -162,8 +165,8 @@ class SqliteNetworkDb(sqlite: Connection) extends NetworkDb with Logging {
val ann = channelAnnouncementCodec.decode(rs.getBitVectorOpt("channel_announcement").get).require.value
val txId = ByteVector32.fromValidHex(rs.getString("txid"))
val capacity = rs.getLong("capacity_sat")
- val channel_update_1_opt = rs.getBitVectorOpt("channel_update_1").map(channelUpdateCodec.decode(_).require.value)
- val channel_update_2_opt = rs.getBitVectorOpt("channel_update_2").map(channelUpdateCodec.decode(_).require.value)
+ val channel_update_1_opt = rs.getBitVectorOpt("channel_update_1").map(channelUpdateCodec(chainHash).decode(_).require.value)
+ val channel_update_2_opt = rs.getBitVectorOpt("channel_update_2").map(channelUpdateCodec(chainHash).decode(_).require.value)
m = m + (ann.shortChannelId -> PublicChannel(ann, txId, Satoshi(capacity), channel_update_1_opt, channel_update_2_opt, None))
}
m
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala
index be7ee0b25..df0cef09b 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/PaymentEvents.scala
@@ -16,15 +16,16 @@
package fr.acinq.eclair.payment
-import java.util.UUID
-
import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.Crypto.PublicKey
-import fr.acinq.eclair.MilliSatoshi
import fr.acinq.eclair.crypto.Sphinx
+import fr.acinq.eclair.payment.PaymentRequest.ExtraHop
import fr.acinq.eclair.router.Announcements
import fr.acinq.eclair.router.Router.{ChannelDesc, ChannelHop, Hop, Ignore}
-import fr.acinq.eclair.wire.Node
+import fr.acinq.eclair.wire.{ChannelUpdate, Node}
+import fr.acinq.eclair.{MilliSatoshi, ShortChannelId}
+
+import java.util.UUID
/**
* Created by PM on 01/02/2017.
@@ -206,4 +207,23 @@ object PaymentFailure {
failures.foldLeft(ignore) { case (current, failure) => updateIgnored(failure, current) }
}
+ /** Update the invoice routing hints based on more recent channel updates received. */
+ def updateRoutingHints(failures: Seq[PaymentFailure], routingHints: Seq[Seq[ExtraHop]]): Seq[Seq[ExtraHop]] = {
+ // We're only interested in the last channel update received per channel.
+ val updates = failures.foldLeft(Map.empty[ShortChannelId, ChannelUpdate]) {
+ case (current, failure) => failure match {
+ case RemoteFailure(_, Sphinx.DecryptedFailurePacket(_, f: Update)) => current.updated(f.update.shortChannelId, f.update)
+ case _ => current
+ }
+ }
+ routingHints.map(_.map(extraHop => updates.get(extraHop.shortChannelId) match {
+ case Some(u) => extraHop.copy(
+ cltvExpiryDelta = u.cltvExpiryDelta,
+ feeBase = u.feeBaseMsat,
+ feeProportionalMillionths = u.feeProportionalMillionths
+ )
+ case None => extraHop
+ }))
+ }
+
}
\ No newline at end of file
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/MultiPartPaymentLifecycle.scala b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/MultiPartPaymentLifecycle.scala
index 664871d9e..012f4cf31 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/MultiPartPaymentLifecycle.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/payment/send/MultiPartPaymentLifecycle.scala
@@ -129,7 +129,8 @@ class MultiPartPaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig,
gotoAbortedOrStop(PaymentAborted(d.sender, d.request, d.failures ++ pf.failures, d.pending.keySet - pf.id))
} else {
val ignore1 = PaymentFailure.updateIgnored(pf.failures, d.ignore)
- stay using d.copy(pending = d.pending - pf.id, ignore = ignore1, failures = d.failures ++ pf.failures)
+ val assistedRoutes1 = PaymentFailure.updateRoutingHints(pf.failures, d.request.assistedRoutes)
+ stay using d.copy(pending = d.pending - pf.id, ignore = ignore1, failures = d.failures ++ pf.failures, request = d.request.copy(assistedRoutes = assistedRoutes1))
}
// The recipient released the preimage without receiving the full payment amount.
@@ -150,11 +151,12 @@ class MultiPartPaymentLifecycle(nodeParams: NodeParams, cfg: SendPaymentConfig,
gotoAbortedOrStop(PaymentAborted(d.sender, d.request, d.failures ++ pf.failures :+ failure, d.pending.keySet - pf.id))
} else {
val ignore1 = PaymentFailure.updateIgnored(pf.failures, d.ignore)
+ val assistedRoutes1 = PaymentFailure.updateRoutingHints(pf.failures, d.request.assistedRoutes)
val stillPending = d.pending - pf.id
val (toSend, maxFee) = remainingToSend(nodeParams, d.request, stillPending.values)
log.info("child payment failed, retry sending {} with maximum fee {}", toSend, maxFee)
val routeParams = d.request.getRouteParams(nodeParams, randomize = true) // we randomize route selection when we retry
- val d1 = d.copy(pending = stillPending, ignore = ignore1, failures = d.failures ++ pf.failures)
+ val d1 = d.copy(pending = stillPending, ignore = ignore1, failures = d.failures ++ pf.failures, request = d.request.copy(assistedRoutes = assistedRoutes1))
router ! createRouteRequest(nodeParams, toSend, maxFee, routeParams, d1, cfg)
goto(WAIT_FOR_ROUTES) using d1
}
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala
index a001dd46b..4aeddc72c 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Router.scala
@@ -71,7 +71,7 @@ class Router(val nodeParams: NodeParams, watcher: ActorRef, initialized: Option[
{
log.info("loading network announcements from db...")
// On Android, we discard the node announcements
- val channels = db.listChannels()
+ val channels = db.listChannels(nodeParams.chainHash)
log.info("loaded from db: channels={}", channels.size)
val initChannels = channels
// this will be used to calculate routes
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala
index 86c5f3ed6..8f9a34a1e 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/router/Validation.scala
@@ -317,7 +317,7 @@ object Validation {
Metrics.channelUpdateRefreshed(u, pc.getChannelUpdateSameSideAs(u).get, publicChannel)
sendDecision(origins, GossipDecision.Accepted(u))
ctx.system.eventStream.publish(ChannelUpdatesReceived(u :: Nil))
- db.updateChannel(u)
+ db.updateChannel(u.chainHash, u)
// update the graph
val pc1 = pc.applyChannelUpdate(update)
val graph1 = if (Announcements.isEnabled(u.channelFlags)) {
@@ -330,7 +330,7 @@ object Validation {
log.debug("added channel_update for shortChannelId={} public={} flags={} {}", u.shortChannelId, publicChannel, u.channelFlags, u)
sendDecision(origins, GossipDecision.Accepted(u))
ctx.system.eventStream.publish(ChannelUpdatesReceived(u :: Nil))
- db.updateChannel(u)
+ db.updateChannel(u.chainHash, u)
// we also need to update the graph
val pc1 = pc.applyChannelUpdate(update)
val graph1 = d.graph.addEdge(desc, u, pc1.capacity, pc1.getBalanceSameSideAs(u))
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala
index 8d55cb505..150e8a693 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/EclairImplSpec.scala
@@ -231,7 +231,7 @@ class EclairImplSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with I
}
val mockNetworkDb = mock[NetworkDb](withSettings.lenient()) // on Android listNodes() and removeNode() are not used
- mockNetworkDb.listChannels() returns channels
+ mockNetworkDb.listChannels(Block.RegtestGenesisBlock.hash) returns channels
mockNetworkDb.listNodes() returns Seq.empty
Mockito.doNothing().when(mockNetworkDb).removeNode(kit.nodeParams.nodeId)
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/NoiseSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/NoiseSpec.scala
index 359ba623b..56abc6d35 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/crypto/NoiseSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/crypto/NoiseSpec.scala
@@ -18,7 +18,7 @@ package fr.acinq.eclair.crypto
import fr.acinq.eclair.crypto.Noise._
import org.scalatest.funsuite.AnyFunSuite
-import org.spongycastle.crypto.ec.CustomNamedCurves
+import org.bouncycastle.crypto.ec.CustomNamedCurves
import scodec.bits._
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteNetworkDbSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteNetworkDbSpec.scala
index 005b49634..f2c45e208 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteNetworkDbSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/db/SqliteNetworkDbSpec.scala
@@ -35,6 +35,7 @@ class SqliteNetworkDbSpec extends AnyFunSuite {
import TestConstants.forAllDbs
val shortChannelIds = (42 to (5000 + 42)).map(i => ShortChannelId(i))
+ val chainHash = Block.RegtestGenesisBlock.hash
test("init sqlite 2 times in a row") {
forAllDbs { dbs =>
@@ -112,17 +113,17 @@ class SqliteNetworkDbSpec extends AnyFunSuite {
forAllDbs { dbs =>
val db = dbs.network()
val sig = ByteVector64.Zeroes
- val c = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(42), randomKey.publicKey, randomKey.publicKey, randomKey.publicKey, randomKey.publicKey, sig, sig, sig, sig)
+ val c = Announcements.makeChannelAnnouncement(chainHash, ShortChannelId(42), randomKey.publicKey, randomKey.publicKey, randomKey.publicKey, randomKey.publicKey, sig, sig, sig, sig)
val c_shrunk = shrink(c)
val txid = ByteVector32.fromValidHex("0001" * 16)
db.addChannel(c, txid, Satoshi(42))
- assert(db.listChannels() === SortedMap(c.shortChannelId -> PublicChannel(c_shrunk, txid, Satoshi(42), None, None, None)))
+ assert(db.listChannels(chainHash) === SortedMap(c.shortChannelId -> PublicChannel(c_shrunk, txid, Satoshi(42), None, None, None)))
}
}
def shrink(c: ChannelAnnouncement) = c.copy(bitcoinKey1 = null, bitcoinKey2 = null, bitcoinSignature1 = null, bitcoinSignature2 = null, nodeSignature1 = null, nodeSignature2 = null, chainHash = null, features = null)
- def shrink(c: ChannelUpdate) = c.copy(signature = null, chainHash = null)
+ def shrink(c: ChannelUpdate) = c.copy(signature = null)
def simpleTest(dbs: TestDatabases) = {
val db = dbs.network()
@@ -140,9 +141,9 @@ class SqliteNetworkDbSpec extends AnyFunSuite {
val b = generatePubkeyHigherThan(a)
val c = generatePubkeyHigherThan(b)
- val channel_1 = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(42), a.publicKey, b.publicKey, randomKey.publicKey, randomKey.publicKey, sig, sig, sig, sig)
- val channel_2 = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(43), a.publicKey, c.publicKey, randomKey.publicKey, randomKey.publicKey, sig, sig, sig, sig)
- val channel_3 = Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, ShortChannelId(44), b.publicKey, c.publicKey, randomKey.publicKey, randomKey.publicKey, sig, sig, sig, sig)
+ val channel_1 = Announcements.makeChannelAnnouncement(chainHash, ShortChannelId(42), a.publicKey, b.publicKey, randomKey.publicKey, randomKey.publicKey, sig, sig, sig, sig)
+ val channel_2 = Announcements.makeChannelAnnouncement(chainHash, ShortChannelId(43), a.publicKey, c.publicKey, randomKey.publicKey, randomKey.publicKey, sig, sig, sig, sig)
+ val channel_3 = Announcements.makeChannelAnnouncement(chainHash, ShortChannelId(44), b.publicKey, c.publicKey, randomKey.publicKey, randomKey.publicKey, sig, sig, sig, sig)
val channel_1_shrunk = shrink(channel_1)
val channel_2_shrunk = shrink(channel_2)
@@ -153,39 +154,39 @@ class SqliteNetworkDbSpec extends AnyFunSuite {
val txid_3 = randomBytes32
val capacity = 10000 sat
- assert(db.listChannels().toSet === Set.empty)
+ assert(db.listChannels(chainHash).toSet === Set.empty)
db.addChannel(channel_1, txid_1, capacity)
db.addChannel(channel_1, txid_1, capacity) // duplicate is ignored
- assert(db.listChannels().size === 1)
+ assert(db.listChannels(chainHash).size === 1)
db.addChannel(channel_2, txid_2, capacity)
db.addChannel(channel_3, txid_3, capacity)
- assert(db.listChannels() === SortedMap(
+ assert(db.listChannels(chainHash) === SortedMap(
channel_1.shortChannelId -> PublicChannel(channel_1_shrunk, txid_1, capacity, None, None, None),
channel_2.shortChannelId -> PublicChannel(channel_2_shrunk, txid_2, capacity, None, None, None),
channel_3.shortChannelId -> PublicChannel(channel_3_shrunk, txid_3, capacity, None, None, None)))
db.removeChannel(channel_2.shortChannelId)
- assert(db.listChannels() === SortedMap(
+ assert(db.listChannels(chainHash) === SortedMap(
channel_1.shortChannelId -> PublicChannel(channel_1_shrunk, txid_1, capacity, None, None, None),
channel_3.shortChannelId -> PublicChannel(channel_3_shrunk, txid_3, capacity, None, None, None)))
- val channel_update_1 = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, a, b.publicKey, ShortChannelId(42), CltvExpiryDelta(5), 7000000 msat, 50000 msat, 100, 500000000L msat, true)
- val channel_update_2 = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, b, a.publicKey, ShortChannelId(42), CltvExpiryDelta(5), 7000000 msat, 50000 msat, 100, 500000000L msat, true)
- val channel_update_3 = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, b, c.publicKey, ShortChannelId(44), CltvExpiryDelta(5), 7000000 msat, 50000 msat, 100, 500000000L msat, true)
+ val channel_update_1 = Announcements.makeChannelUpdate(chainHash, a, b.publicKey, ShortChannelId(42), CltvExpiryDelta(5), 7000000 msat, 50000 msat, 100, 500000000L msat, true)
+ val channel_update_2 = Announcements.makeChannelUpdate(chainHash, b, a.publicKey, ShortChannelId(42), CltvExpiryDelta(5), 7000000 msat, 50000 msat, 100, 500000000L msat, true)
+ val channel_update_3 = Announcements.makeChannelUpdate(chainHash, b, c.publicKey, ShortChannelId(44), CltvExpiryDelta(5), 7000000 msat, 50000 msat, 100, 500000000L msat, true)
val channel_update_1_shrunk = shrink(channel_update_1)
val channel_update_2_shrunk = shrink(channel_update_2)
val channel_update_3_shrunk = shrink(channel_update_3)
- db.updateChannel(channel_update_1)
- db.updateChannel(channel_update_1) // duplicate is ignored
- db.updateChannel(channel_update_2)
- db.updateChannel(channel_update_3)
- assert(db.listChannels() === SortedMap(
+ db.updateChannel(chainHash, channel_update_1)
+ db.updateChannel(chainHash, channel_update_1) // duplicate is ignored
+ db.updateChannel(chainHash, channel_update_2)
+ db.updateChannel(chainHash, channel_update_3)
+ assert(db.listChannels(chainHash) === SortedMap(
channel_1.shortChannelId -> PublicChannel(channel_1_shrunk, txid_1, capacity, Some(channel_update_1_shrunk), Some(channel_update_2_shrunk), None),
channel_3.shortChannelId -> PublicChannel(channel_3_shrunk, txid_3, capacity, Some(channel_update_3_shrunk), None, None)))
db.removeChannel(channel_3.shortChannelId)
- assert(db.listChannels() === SortedMap(
+ assert(db.listChannels(chainHash) === SortedMap(
channel_1.shortChannelId -> PublicChannel(channel_1_shrunk, txid_1, capacity, Some(channel_update_1_shrunk), Some(channel_update_2_shrunk), None)))
}
@@ -247,17 +248,17 @@ class SqliteNetworkDbSpec extends AnyFunSuite {
val pub = priv.publicKey
val capacity = 10000 sat
- val channels = shortChannelIds.map(id => Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, id, pub, pub, pub, pub, sig, sig, sig, sig))
- val template = Announcements.makeChannelUpdate(Block.RegtestGenesisBlock.hash, priv, pub, ShortChannelId(42), CltvExpiryDelta(5), 7000000 msat, 50000 msat, 100, 500000000L msat, true)
+ val channels = shortChannelIds.map(id => Announcements.makeChannelAnnouncement(chainHash, id, pub, pub, pub, pub, sig, sig, sig, sig))
+ val template = Announcements.makeChannelUpdate(chainHash, priv, pub, ShortChannelId(42), CltvExpiryDelta(5), 7000000 msat, 50000 msat, 100, 500000000L msat, true)
val updates = shortChannelIds.map(id => template.copy(shortChannelId = id))
val txid = randomBytes32
channels.foreach(ca => db.addChannel(ca, txid, capacity))
- updates.foreach(u => db.updateChannel(u))
- assert(db.listChannels().keySet === channels.map(_.shortChannelId).toSet)
+ updates.foreach(u => db.updateChannel(chainHash, u))
+ assert(db.listChannels(chainHash).keySet === channels.map(_.shortChannelId).toSet)
val toDelete = channels.map(_.shortChannelId).drop(500).take(2500)
db.removeChannels(toDelete)
- assert(db.listChannels().keySet === (channels.map(_.shortChannelId).toSet -- toDelete))
+ assert(db.listChannels(chainHash).keySet === (channels.map(_.shortChannelId).toSet -- toDelete))
}
}
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala
index bc8e93507..7a6f924df 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/payment/MultiPartPaymentLifecycleSpec.scala
@@ -16,8 +16,6 @@
package fr.acinq.eclair.payment
-import java.util.UUID
-
import akka.actor.{ActorRef, Status}
import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.{Block, Crypto}
@@ -25,6 +23,7 @@ import fr.acinq.eclair._
import fr.acinq.eclair.channel.{AddHtlcFailed, ChannelFlags, ChannelUnavailable, Upstream}
import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.db.{FailureSummary, FailureType, OutgoingPaymentStatus}
+import fr.acinq.eclair.payment.PaymentRequest.ExtraHop
import fr.acinq.eclair.payment.send.MultiPartPaymentLifecycle
import fr.acinq.eclair.payment.send.MultiPartPaymentLifecycle._
import fr.acinq.eclair.payment.send.PaymentError.RetryExhausted
@@ -37,6 +36,7 @@ import org.scalatest.Outcome
import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import scodec.bits.{ByteVector, HexStringSyntax}
+import java.util.UUID
import scala.concurrent.duration._
/**
@@ -239,6 +239,64 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
assert(result.amountWithFees === 1000200.msat)
}
+ test("retry with updated routing hints") { f =>
+ import f._
+
+ // The B -> E channel is private and provided in the invoice routing hints.
+ val routingHint = ExtraHop(b, hop_be.lastUpdate.shortChannelId, hop_be.lastUpdate.feeBaseMsat, hop_be.lastUpdate.feeProportionalMillionths, hop_be.lastUpdate.cltvExpiryDelta)
+ val payment = SendMultiPartPayment(randomBytes32, e, finalAmount, expiry, 3, routeParams = Some(routeParams), assistedRoutes = List(List(routingHint)))
+ sender.send(payFsm, payment)
+ assert(router.expectMsgType[RouteRequest].assistedRoutes.head.head === routingHint)
+ val route = Route(finalAmount, hop_ab_1 :: hop_be :: Nil)
+ router.send(payFsm, RouteResponse(Seq(route)))
+ childPayFsm.expectMsgType[SendPaymentToRoute]
+ childPayFsm.expectNoMsg(100 millis)
+
+ // B changed his fees and expiry after the invoice was issued.
+ val channelUpdate = hop_be.lastUpdate.copy(feeBaseMsat = 250 msat, feeProportionalMillionths = 150, cltvExpiryDelta = CltvExpiryDelta(24))
+ val childId = payFsm.stateData.asInstanceOf[PaymentProgress].pending.keys.head
+ childPayFsm.send(payFsm, PaymentFailed(childId, paymentHash, Seq(RemoteFailure(route.hops, Sphinx.DecryptedFailurePacket(b, FeeInsufficient(finalAmount, channelUpdate))))))
+ // We update the routing hints accordingly before requesting a new route.
+ assert(router.expectMsgType[RouteRequest].assistedRoutes.head.head === ExtraHop(b, channelUpdate.shortChannelId, 250 msat, 150, CltvExpiryDelta(24)))
+ }
+
+ test("update routing hints") { _ =>
+ val routingHints = Seq(
+ Seq(ExtraHop(a, ShortChannelId(1), 10 msat, 0, CltvExpiryDelta(12)), ExtraHop(b, ShortChannelId(2), 0 msat, 100, CltvExpiryDelta(24))),
+ Seq(ExtraHop(a, ShortChannelId(3), 1 msat, 10, CltvExpiryDelta(144)))
+ )
+
+ def makeChannelUpdate(shortChannelId: ShortChannelId, feeBase: MilliSatoshi, feeProportional: Long, cltvExpiryDelta: CltvExpiryDelta): ChannelUpdate = {
+ defaultChannelUpdate.copy(shortChannelId = shortChannelId, feeBaseMsat = feeBase, feeProportionalMillionths = feeProportional, cltvExpiryDelta = cltvExpiryDelta)
+ }
+
+ {
+ val failures = Seq(
+ LocalFailure(Nil, ChannelUnavailable(randomBytes32)),
+ RemoteFailure(Nil, Sphinx.DecryptedFailurePacket(b, FeeInsufficient(100 msat, makeChannelUpdate(ShortChannelId(2), 15 msat, 150, CltvExpiryDelta(48))))),
+ UnreadableRemoteFailure(Nil)
+ )
+ val routingHints1 = Seq(
+ Seq(ExtraHop(a, ShortChannelId(1), 10 msat, 0, CltvExpiryDelta(12)), ExtraHop(b, ShortChannelId(2), 15 msat, 150, CltvExpiryDelta(48))),
+ Seq(ExtraHop(a, ShortChannelId(3), 1 msat, 10, CltvExpiryDelta(144)))
+ )
+ assert(routingHints1 === PaymentFailure.updateRoutingHints(failures, routingHints))
+ }
+ {
+ val failures = Seq(
+ RemoteFailure(Nil, Sphinx.DecryptedFailurePacket(a, FeeInsufficient(100 msat, makeChannelUpdate(ShortChannelId(1), 20 msat, 20, CltvExpiryDelta(20))))),
+ RemoteFailure(Nil, Sphinx.DecryptedFailurePacket(b, FeeInsufficient(100 msat, makeChannelUpdate(ShortChannelId(2), 21 msat, 21, CltvExpiryDelta(21))))),
+ RemoteFailure(Nil, Sphinx.DecryptedFailurePacket(a, FeeInsufficient(100 msat, makeChannelUpdate(ShortChannelId(3), 22 msat, 22, CltvExpiryDelta(22))))),
+ RemoteFailure(Nil, Sphinx.DecryptedFailurePacket(a, FeeInsufficient(100 msat, makeChannelUpdate(ShortChannelId(1), 23 msat, 23, CltvExpiryDelta(23)))))
+ )
+ val routingHints1 = Seq(
+ Seq(ExtraHop(a, ShortChannelId(1), 23 msat, 23, CltvExpiryDelta(23)), ExtraHop(b, ShortChannelId(2), 21 msat, 21, CltvExpiryDelta(21))),
+ Seq(ExtraHop(a, ShortChannelId(3), 22 msat, 22, CltvExpiryDelta(22)))
+ )
+ assert(routingHints1 === PaymentFailure.updateRoutingHints(failures, routingHints))
+ }
+ }
+
test("abort after too many failed attempts") { f =>
import f._
diff --git a/pom.xml b/pom.xml
index b207a5998..30a0f1431 100644
--- a/pom.xml
+++ b/pom.xml
@@ -68,7 +68,7 @@
2.3.14
10.0.11
1.3.9
- 0.17
+ 0.18
24.0-android
2.1.0