1
0
Fork 0
mirror of https://github.com/ACINQ/eclair.git synced 2025-03-15 04:11:33 +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 * @param nodeParams
*/ */
def checkNetworkDBCompatibility(nodeParams: NodeParams): Unit = 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 Success(_) => {}
case Failure(_) => throw IncompatibleNetworkDBException 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 fr.acinq.eclair.crypto.ChaCha20Poly1305.{DecryptionError, EncryptionError, InvalidCounter}
import grizzled.slf4j.Logger import grizzled.slf4j.Logger
import grizzled.slf4j.Logging import grizzled.slf4j.Logging
import org.spongycastle.crypto.engines.ChaCha7539Engine import org.bouncycastle.crypto.engines.ChaCha7539Engine
import org.spongycastle.crypto.params.{KeyParameter, ParametersWithIV} import org.bouncycastle.crypto.params.{KeyParameter, ParametersWithIV}
import scodec.bits.ByteVector import scodec.bits.ByteVector
/** /**
@ -39,7 +39,7 @@ object Poly1305 {
*/ */
def mac(key: ByteVector, datas: ByteVector*): ByteVector = { def mac(key: ByteVector, datas: ByteVector*): ByteVector = {
val out = new Array[Byte](16) 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)) poly.init(new KeyParameter(key.toArray))
datas.foreach(data => poly.update(data.toArray, 0, data.length.toInt)) datas.foreach(data => poly.update(data.toArray, 0, data.length.toInt))
poly.doFinal(out, 0) poly.doFinal(out, 0)

View file

@ -17,9 +17,9 @@
package fr.acinq.eclair.crypto package fr.acinq.eclair.crypto
import fr.acinq.bitcoin.ByteVector32 import fr.acinq.bitcoin.ByteVector32
import org.spongycastle.crypto.digests.SHA256Digest import org.bouncycastle.crypto.digests.SHA256Digest
import org.spongycastle.crypto.macs.HMac import org.bouncycastle.crypto.macs.HMac
import org.spongycastle.crypto.params.KeyParameter import org.bouncycastle.crypto.params.KeyParameter
import scodec.bits.ByteVector 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.bitcoin.{Crypto, Protocol}
import fr.acinq.eclair.randomBytes import fr.acinq.eclair.randomBytes
import grizzled.slf4j.Logging import grizzled.slf4j.Logging
import org.spongycastle.crypto.digests.SHA256Digest import org.bouncycastle.crypto.digests.SHA256Digest
import org.spongycastle.crypto.macs.HMac import org.bouncycastle.crypto.macs.HMac
import org.spongycastle.crypto.params.KeyParameter import org.bouncycastle.crypto.params.KeyParameter
import scodec.bits.ByteVector import scodec.bits.ByteVector
/** /**

View file

@ -40,13 +40,13 @@ trait NetworkDb extends Closeable {
def addChannel(c: ChannelAnnouncement, txid: ByteVector32, capacity: Satoshi): Unit 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 removeChannel(shortChannelId: ShortChannelId) = removeChannels(Set(shortChannelId)): Unit
def removeChannels(shortChannelIds: Iterable[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 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])) :: ("bitcoinSignature2" | provide(null.asInstanceOf[ByteVector64])) ::
channelAnnouncementWitnessCodec).as[ChannelAnnouncement] 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) :: ("shortChannelId" | shortchannelid) ::
("timestamp" | uint32) :: ("timestamp" | uint32) ::
(("messageFlags" | byte) >>:~ { messageFlags => (("messageFlags" | byte) >>:~ { messageFlags =>
@ -73,9 +76,9 @@ class SqliteNetworkDb(sqlite: Connection) extends NetworkDb with Logging {
("unknownFields" | bytes) ("unknownFields" | bytes)
}) })
val channelUpdateCodec: Codec[ChannelUpdate] = ( def channelUpdateCodec(chainHash: ByteVector32): Codec[ChannelUpdate] = (
("signature" | provide(null.asInstanceOf[ByteVector64])) :: ("signature" | provide(null.asInstanceOf[ByteVector64])) ::
channelUpdateWitnessCodec).as[ChannelUpdate] channelUpdateWitnessCodec(chainHash)).as[ChannelUpdate]
using(sqlite.createStatement(), inTransaction = true) { statement => using(sqlite.createStatement(), inTransaction = true) { statement =>
getVersion(statement, DB_NAME, CURRENT_VERSION) match { 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" 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 => 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.setLong(2, u.shortChannelId.toLong)
statement.executeUpdate() 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 => using(sqlite.createStatement()) { statement =>
val rs = statement.executeQuery("SELECT channel_announcement, txid, capacity_sat, channel_update_1, channel_update_2 FROM channels") val rs = statement.executeQuery("SELECT channel_announcement, txid, capacity_sat, channel_update_1, channel_update_2 FROM channels")
var m = SortedMap.empty[ShortChannelId, PublicChannel] 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 ann = channelAnnouncementCodec.decode(rs.getBitVectorOpt("channel_announcement").get).require.value
val txId = ByteVector32.fromValidHex(rs.getString("txid")) val txId = ByteVector32.fromValidHex(rs.getString("txid"))
val capacity = rs.getLong("capacity_sat") val capacity = rs.getLong("capacity_sat")
val channel_update_1_opt = rs.getBitVectorOpt("channel_update_1").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.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 = m + (ann.shortChannelId -> PublicChannel(ann, txId, Satoshi(capacity), channel_update_1_opt, channel_update_2_opt, None))
} }
m m

View file

@ -16,15 +16,16 @@
package fr.acinq.eclair.payment package fr.acinq.eclair.payment
import java.util.UUID
import fr.acinq.bitcoin.ByteVector32 import fr.acinq.bitcoin.ByteVector32
import fr.acinq.bitcoin.Crypto.PublicKey import fr.acinq.bitcoin.Crypto.PublicKey
import fr.acinq.eclair.MilliSatoshi
import fr.acinq.eclair.crypto.Sphinx import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.payment.PaymentRequest.ExtraHop
import fr.acinq.eclair.router.Announcements import fr.acinq.eclair.router.Announcements
import fr.acinq.eclair.router.Router.{ChannelDesc, ChannelHop, Hop, Ignore} 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. * Created by PM on 01/02/2017.
@ -206,4 +207,23 @@ object PaymentFailure {
failures.foldLeft(ignore) { case (current, failure) => updateIgnored(failure, current) } 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)) gotoAbortedOrStop(PaymentAborted(d.sender, d.request, d.failures ++ pf.failures, d.pending.keySet - pf.id))
} else { } else {
val ignore1 = PaymentFailure.updateIgnored(pf.failures, d.ignore) 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. // 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)) gotoAbortedOrStop(PaymentAborted(d.sender, d.request, d.failures ++ pf.failures :+ failure, d.pending.keySet - pf.id))
} else { } else {
val ignore1 = PaymentFailure.updateIgnored(pf.failures, d.ignore) val ignore1 = PaymentFailure.updateIgnored(pf.failures, d.ignore)
val assistedRoutes1 = PaymentFailure.updateRoutingHints(pf.failures, d.request.assistedRoutes)
val stillPending = d.pending - pf.id val stillPending = d.pending - pf.id
val (toSend, maxFee) = remainingToSend(nodeParams, d.request, stillPending.values) val (toSend, maxFee) = remainingToSend(nodeParams, d.request, stillPending.values)
log.info("child payment failed, retry sending {} with maximum fee {}", toSend, maxFee) 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 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) router ! createRouteRequest(nodeParams, toSend, maxFee, routeParams, d1, cfg)
goto(WAIT_FOR_ROUTES) using d1 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...") log.info("loading network announcements from db...")
// On Android, we discard the node announcements // 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) log.info("loaded from db: channels={}", channels.size)
val initChannels = channels val initChannels = channels
// this will be used to calculate routes // this will be used to calculate routes

View file

@ -317,7 +317,7 @@ object Validation {
Metrics.channelUpdateRefreshed(u, pc.getChannelUpdateSameSideAs(u).get, publicChannel) Metrics.channelUpdateRefreshed(u, pc.getChannelUpdateSameSideAs(u).get, publicChannel)
sendDecision(origins, GossipDecision.Accepted(u)) sendDecision(origins, GossipDecision.Accepted(u))
ctx.system.eventStream.publish(ChannelUpdatesReceived(u :: Nil)) ctx.system.eventStream.publish(ChannelUpdatesReceived(u :: Nil))
db.updateChannel(u) db.updateChannel(u.chainHash, u)
// update the graph // update the graph
val pc1 = pc.applyChannelUpdate(update) val pc1 = pc.applyChannelUpdate(update)
val graph1 = if (Announcements.isEnabled(u.channelFlags)) { 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) log.debug("added channel_update for shortChannelId={} public={} flags={} {}", u.shortChannelId, publicChannel, u.channelFlags, u)
sendDecision(origins, GossipDecision.Accepted(u)) sendDecision(origins, GossipDecision.Accepted(u))
ctx.system.eventStream.publish(ChannelUpdatesReceived(u :: Nil)) ctx.system.eventStream.publish(ChannelUpdatesReceived(u :: Nil))
db.updateChannel(u) db.updateChannel(u.chainHash, u)
// we also need to update the graph // we also need to update the graph
val pc1 = pc.applyChannelUpdate(update) val pc1 = pc.applyChannelUpdate(update)
val graph1 = d.graph.addEdge(desc, u, pc1.capacity, pc1.getBalanceSameSideAs(u)) 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 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 mockNetworkDb.listNodes() returns Seq.empty
Mockito.doNothing().when(mockNetworkDb).removeNode(kit.nodeParams.nodeId) 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 fr.acinq.eclair.crypto.Noise._
import org.scalatest.funsuite.AnyFunSuite import org.scalatest.funsuite.AnyFunSuite
import org.spongycastle.crypto.ec.CustomNamedCurves import org.bouncycastle.crypto.ec.CustomNamedCurves
import scodec.bits._ import scodec.bits._

View file

@ -35,6 +35,7 @@ class SqliteNetworkDbSpec extends AnyFunSuite {
import TestConstants.forAllDbs import TestConstants.forAllDbs
val shortChannelIds = (42 to (5000 + 42)).map(i => ShortChannelId(i)) val shortChannelIds = (42 to (5000 + 42)).map(i => ShortChannelId(i))
val chainHash = Block.RegtestGenesisBlock.hash
test("init sqlite 2 times in a row") { test("init sqlite 2 times in a row") {
forAllDbs { dbs => forAllDbs { dbs =>
@ -112,17 +113,17 @@ class SqliteNetworkDbSpec extends AnyFunSuite {
forAllDbs { dbs => forAllDbs { dbs =>
val db = dbs.network() val db = dbs.network()
val sig = ByteVector64.Zeroes 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 c_shrunk = shrink(c)
val txid = ByteVector32.fromValidHex("0001" * 16) val txid = ByteVector32.fromValidHex("0001" * 16)
db.addChannel(c, txid, Satoshi(42)) 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: 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) = { def simpleTest(dbs: TestDatabases) = {
val db = dbs.network() val db = dbs.network()
@ -140,9 +141,9 @@ class SqliteNetworkDbSpec extends AnyFunSuite {
val b = generatePubkeyHigherThan(a) val b = generatePubkeyHigherThan(a)
val c = generatePubkeyHigherThan(b) 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_1 = Announcements.makeChannelAnnouncement(chainHash, 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_2 = Announcements.makeChannelAnnouncement(chainHash, 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_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_1_shrunk = shrink(channel_1)
val channel_2_shrunk = shrink(channel_2) val channel_2_shrunk = shrink(channel_2)
@ -153,39 +154,39 @@ class SqliteNetworkDbSpec extends AnyFunSuite {
val txid_3 = randomBytes32 val txid_3 = randomBytes32
val capacity = 10000 sat 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)
db.addChannel(channel_1, txid_1, capacity) // duplicate is ignored 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_2, txid_2, capacity)
db.addChannel(channel_3, txid_3, 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_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_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))) channel_3.shortChannelId -> PublicChannel(channel_3_shrunk, txid_3, capacity, None, None, None)))
db.removeChannel(channel_2.shortChannelId) 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_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))) 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_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(Block.RegtestGenesisBlock.hash, b, a.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(Block.RegtestGenesisBlock.hash, b, c.publicKey, ShortChannelId(44), 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_1_shrunk = shrink(channel_update_1)
val channel_update_2_shrunk = shrink(channel_update_2) val channel_update_2_shrunk = shrink(channel_update_2)
val channel_update_3_shrunk = shrink(channel_update_3) val channel_update_3_shrunk = shrink(channel_update_3)
db.updateChannel(channel_update_1) db.updateChannel(chainHash, channel_update_1)
db.updateChannel(channel_update_1) // duplicate is ignored db.updateChannel(chainHash, channel_update_1) // duplicate is ignored
db.updateChannel(channel_update_2) db.updateChannel(chainHash, channel_update_2)
db.updateChannel(channel_update_3) db.updateChannel(chainHash, channel_update_3)
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), 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))) channel_3.shortChannelId -> PublicChannel(channel_3_shrunk, txid_3, capacity, Some(channel_update_3_shrunk), None, None)))
db.removeChannel(channel_3.shortChannelId) 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))) 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 pub = priv.publicKey
val capacity = 10000 sat val capacity = 10000 sat
val channels = shortChannelIds.map(id => Announcements.makeChannelAnnouncement(Block.RegtestGenesisBlock.hash, id, pub, pub, pub, pub, sig, sig, sig, sig)) val channels = shortChannelIds.map(id => Announcements.makeChannelAnnouncement(chainHash, 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 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 updates = shortChannelIds.map(id => template.copy(shortChannelId = id))
val txid = randomBytes32 val txid = randomBytes32
channels.foreach(ca => db.addChannel(ca, txid, capacity)) channels.foreach(ca => db.addChannel(ca, txid, capacity))
updates.foreach(u => db.updateChannel(u)) updates.foreach(u => db.updateChannel(chainHash, u))
assert(db.listChannels().keySet === channels.map(_.shortChannelId).toSet) assert(db.listChannels(chainHash).keySet === channels.map(_.shortChannelId).toSet)
val toDelete = channels.map(_.shortChannelId).drop(500).take(2500) val toDelete = channels.map(_.shortChannelId).drop(500).take(2500)
db.removeChannels(toDelete) 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 package fr.acinq.eclair.payment
import java.util.UUID
import akka.actor.{ActorRef, Status} import akka.actor.{ActorRef, Status}
import akka.testkit.{TestFSMRef, TestProbe} import akka.testkit.{TestFSMRef, TestProbe}
import fr.acinq.bitcoin.{Block, Crypto} 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.channel.{AddHtlcFailed, ChannelFlags, ChannelUnavailable, Upstream}
import fr.acinq.eclair.crypto.Sphinx import fr.acinq.eclair.crypto.Sphinx
import fr.acinq.eclair.db.{FailureSummary, FailureType, OutgoingPaymentStatus} 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.MultiPartPaymentLifecycle._ import fr.acinq.eclair.payment.send.MultiPartPaymentLifecycle._
import fr.acinq.eclair.payment.send.PaymentError.RetryExhausted import fr.acinq.eclair.payment.send.PaymentError.RetryExhausted
@ -37,6 +36,7 @@ import org.scalatest.Outcome
import org.scalatest.funsuite.FixtureAnyFunSuiteLike import org.scalatest.funsuite.FixtureAnyFunSuiteLike
import scodec.bits.{ByteVector, HexStringSyntax} import scodec.bits.{ByteVector, HexStringSyntax}
import java.util.UUID
import scala.concurrent.duration._ import scala.concurrent.duration._
/** /**
@ -239,6 +239,64 @@ class MultiPartPaymentLifecycleSpec extends TestKitBaseClass with FixtureAnyFunS
assert(result.amountWithFees === 1000200.msat) 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 => test("abort after too many failed attempts") { f =>
import f._ import f._

View file

@ -68,7 +68,7 @@
<akka.version>2.3.14</akka.version> <akka.version>2.3.14</akka.version>
<akka.http.version>10.0.11</akka.http.version> <akka.http.version>10.0.11</akka.http.version>
<sttp.version>1.3.9</sttp.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> <guava.version>24.0-android</guava.version>
<kamon.version>2.1.0</kamon.version> <kamon.version>2.1.0</kamon.version>
</properties> </properties>