All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bitcoins.lnd.rpc.internal.LndRouterClient.scala Maven / Gradle / Ivy

The newest version!
package org.bitcoins.lnd.rpc.internal

import lnrpc.Failure.FailureCode.INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS
import lnrpc.{
  HTLCAttempt,
  HopHint,
  MPPRecord,
  QueryRoutesRequest,
  QueryRoutesResponse,
  Route,
  RouteHint
}
import org.bitcoins.core.currency._
import org.bitcoins.core.number._
import org.bitcoins.core.protocol.ln.{LnInvoice, PaymentSecret}
import org.bitcoins.core.protocol.ln.node.NodeId
import org.bitcoins.core.protocol.ln.routing.LnRoute
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: CurrencyUnit,
      node: NodeId,
      routeHints: Vector[LnRoute]
  ): Future[QueryRoutesResponse] = {
    val hopHints = routeHints.map { hint =>
      HopHint(
        hint.pubkey.hex,
        hint.shortChannelID.u64,
        UInt32(hint.feeBaseMsat.msat.toLong),
        hint.feePropMilli.u32,
        UInt32(hint.cltvExpiryDelta)
      )
    }
    val request =
      QueryRoutesRequest(
        pubKey = node.pubKey.hex,
        amt = amount.satoshis.toLong,
        finalCltvDelta = 80,
        useMissionControl = true,
        routeHints = Vector(RouteHint(hopHints))
      )

    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)
    val hints = invoice.lnTags.routingInfo.map(_.routes).getOrElse(Vector.empty)
    probe(amount, invoice.nodeId, hints)
  }

  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,
      routeHints: Vector[LnRoute]
  ): Future[Vector[Route]] = {
    queryRoutes(amount, node, routeHints).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] = {
    sendToRoute(
      invoice.lnTags.paymentHash.hash,
      route,
      invoice.lnTags.secret.map(_.secret)
    )
  }

  def sendToRoute(
      paymentHash: Sha256Digest,
      route: Route,
      secretOpt: Option[PaymentSecret]
  ): Future[HTLCAttempt] = {
    val updatedRoute = secretOpt match {
      case Some(secret) =>
        val last = route.hops.last
        val mpp = MPPRecord(
          paymentAddr = secret.bytes,
          totalAmtMsat = route.totalAmtMsat
        )
        val update = last.copy(mppRecord = Some(mpp), tlvPayload = true)
        val updatedHops = route.hops.init :+ update

        route.copy(hops = updatedHops)
      case None => route
    }

    val request =
      SendToRouteRequest(
        paymentHash = 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(_))
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy