Implement probing in lnd (#4202)

This commit is contained in:
benthecarman 2022-04-25 10:50:33 -05:00 committed by GitHub
parent 17944c4aad
commit b8a984a986
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 143 additions and 0 deletions

View file

@ -197,4 +197,30 @@ class LndRpcClientPairTest extends DualLndFixture {
assert(tlv == customMessage) assert(tlv == customMessage)
} }
} }
it must "probe an amount" in { params =>
val (_, lndA, lndB) = params
for {
nodeIdA <- lndA.nodeId
routes <- lndB.probe(Satoshis(1000), nodeIdA)
} yield {
assert(routes.nonEmpty)
}
}
it must "probe and pay" in { params =>
val (_, lndA, lndB) = params
for {
inv <- lndA.addInvoice("test", Satoshis(1000), 3600)
paymentOpt <- lndB.probeAndPay(inv.invoice)
} yield {
paymentOpt match {
case Some(payment) =>
assert(payment.status.isSucceeded)
case None => fail()
}
}
}
} }

View file

@ -36,6 +36,7 @@ import org.bitcoins.crypto._
import org.bitcoins.lnd.rpc.LndRpcClient._ import org.bitcoins.lnd.rpc.LndRpcClient._
import org.bitcoins.lnd.rpc.LndUtils._ import org.bitcoins.lnd.rpc.LndUtils._
import org.bitcoins.lnd.rpc.config._ import org.bitcoins.lnd.rpc.config._
import org.bitcoins.lnd.rpc.internal._
import routerrpc._ import routerrpc._
import scodec.bits._ import scodec.bits._
import signrpc._ import signrpc._
@ -62,6 +63,7 @@ class LndRpcClient(val instance: LndInstance, binaryOpt: Option[File] = None)(
implicit val system: ActorSystem) implicit val system: ActorSystem)
extends NativeProcessFactory extends NativeProcessFactory
with LndUtils with LndUtils
with LndRouterClient
with StartStopAsync[LndRpcClient] with StartStopAsync[LndRpcClient]
with Logging { with Logging {
instance match { instance match {

View file

@ -0,0 +1,115 @@
package org.bitcoins.lnd.rpc.internal
import lnrpc.Failure.FailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS
import lnrpc.{
HTLCAttempt,
MPPRecord,
QueryRoutesRequest,
QueryRoutesResponse,
Route
}
import org.bitcoins.core.currency.Satoshis
import org.bitcoins.core.protocol.ln.LnInvoice
import org.bitcoins.core.protocol.ln.currency.MilliSatoshis
import org.bitcoins.core.protocol.ln.node.NodeId
import org.bitcoins.core.util.FutureUtil
import org.bitcoins.crypto._
import org.bitcoins.lnd.rpc.LndRpcClient
import routerrpc._
import scala.concurrent.Future
trait LndRouterClient { self: LndRpcClient =>
def queryRoutes(
amount: Satoshis,
node: NodeId): Future[QueryRoutesResponse] = {
val request =
QueryRoutesRequest(pubKey = node.pubKey.hex,
amt = amount.toLong,
finalCltvDelta = 40,
useMissionControl = true)
queryRoutes(request)
}
def queryRoutes(request: QueryRoutesRequest): Future[QueryRoutesResponse] = {
logger.trace("lnd calling queryroutes")
lnd.queryRoutes(request)
}
def probe(invoice: LnInvoice): Future[Vector[Route]] = {
val amount = invoice.amount.map(_.toSatoshis).getOrElse(Satoshis.zero)
probe(amount, invoice.nodeId)
}
def sendToRoute(hash: Sha256Digest, route: Route): Future[HTLCAttempt] = {
val request = SendToRouteRequest(paymentHash = hash.bytes, Some(route))
sendToRoute(request)
}
def sendToRoute(request: SendToRouteRequest): Future[HTLCAttempt] = {
logger.trace("lnd calling sendtoroute")
router.sendToRouteV2(request)
}
def probe(amount: Satoshis, node: NodeId): Future[Vector[Route]] = {
queryRoutes(amount, node).map(_.routes).flatMap { routes =>
val fs = routes.toVector.map { route =>
val fakeHash = CryptoUtil.sha256(ECPrivateKey.freshPrivateKey.bytes)
sendToRoute(fakeHash, route).map(t => (route, t))
}
Future.sequence(fs).map { results =>
results
.filter(
_._2.failure.exists(_.code == INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS))
.map(_._1)
}
}
}
def sendToRoute(invoice: LnInvoice, route: Route): Future[HTLCAttempt] = {
val milliSatoshis =
invoice.amount.map(MilliSatoshis(_)).getOrElse(MilliSatoshis.zero)
val last = route.hops.last
val secret = invoice.lnTags.secret.get.secret.bytes
val mpp =
MPPRecord(paymentAddr = secret, totalAmtMsat = milliSatoshis.toLong)
val update = last.copy(mppRecord = Some(mpp), tlvPayload = true)
val updatedHops = route.hops.init :+ update
val updatedRoute = route.copy(hops = updatedHops)
val request =
SendToRouteRequest(paymentHash = invoice.lnTags.paymentHash.bytes,
route = Some(updatedRoute))
sendToRoute(request)
}
def probeAndPay(invoice: LnInvoice): Future[Option[HTLCAttempt]] = {
probe(invoice).flatMap { routes =>
val sorted = routes.sortBy(_.totalFeesMsat)
attemptToPayRoutes(invoice, sorted)
}
}
def attemptToPayRoutes(
invoice: LnInvoice,
routes: Vector[Route]): Future[Option[HTLCAttempt]] = {
val init: Option[HTLCAttempt] = None
FutureUtil.foldLeftAsync(init, routes) { case (ret, route) =>
ret match {
case Some(value) =>
value.failure match {
case Some(_) => sendToRoute(invoice, route).map(Some(_))
case None => Future.successful(Some(value))
}
case None => sendToRoute(invoice, route).map(Some(_))
}
}
}
}