mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-02-23 22:56:52 +01:00
Implement probing in lnd (#4202)
This commit is contained in:
parent
17944c4aad
commit
b8a984a986
3 changed files with 143 additions and 0 deletions
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(_))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue