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:
commit
3115084656
15 changed files with 143 additions and 59 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}))
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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._
|
||||
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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._
|
||||
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue