1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-03-13 19:37:35 +01:00

Merge branch 'android' into android-phoenix

This commit is contained in:
dpad85 2021-05-07 20:21:43 +02:00
commit 3115084656
No known key found for this signature in database
GPG key ID: 574C8C6A1673E987
15 changed files with 143 additions and 59 deletions

View file

@ -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
}

View file

@ -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)

View file

@ -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
/**

View file

@ -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
/**

View file

@ -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

View file

@ -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

View file

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

View file

@ -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
}

View file

@ -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

View file

@ -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))

View file

@ -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)

View file

@ -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._

View file

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

View file

@ -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._

View file

@ -68,7 +68,7 @@
<akka.version>2.3.14</akka.version>
<akka.http.version>10.0.11</akka.http.version>
<sttp.version>1.3.9</sttp.version>
<bitcoinlib.version>0.17</bitcoinlib.version>
<bitcoinlib.version>0.18</bitcoinlib.version>
<guava.version>24.0-android</guava.version>
<kamon.version>2.1.0</kamon.version>
</properties>