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

org.bitcoins.lnd.rpc.config.LndConfig.scala Maven / Gradle / Ivy

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

import org.bitcoins.commons.util.BitcoinSLogger
import org.bitcoins.core.api.commons.ConfigFactory
import org.bitcoins.core.config._
import scodec.bits.ByteVector

import java.io.File
import java.net.URI
import java.nio.file.{Files, Path, Paths}
import scala.jdk.CollectionConverters.IteratorHasAsScala
import scala.util.Properties

/** This class represents a parsed `lnd.conf` file. It respects the different
  * ways of writing options in `lnd.conf`: Raw options, network-prefixed options
  * and options within network sections. It also tries to conform to the way lnd
  * gives precedence to the different properties.
  *
  * Not all options are exposed from this class. We only expose those that are
  * of relevance when making RPC requests.
  */
case class LndConfig(private[bitcoins] val lines: Seq[String], datadir: File)
    extends BitcoinSLogger {

  // create datadir and config if it DNE on disk
  if (!datadir.exists()) {
    logger.debug(
      s"datadir=${datadir.getAbsolutePath} does not exist, creating now"
    )
    datadir.mkdirs()
    LndConfig.writeConfigToFile(this, datadir)
  }

  private val confFile = datadir.toPath.resolve("lnd.conf")

  // create lnd.conf file in datadir if it does not exist
  if (!Files.exists(confFile)) {
    logger.debug(
      s"lnd.conf in datadir=${datadir.getAbsolutePath} does not exist, creating now"
    )
    LndConfig.writeConfigToFile(this, datadir)
  }

  /** Converts the config back to a string that can be written to file, and
    * passed to `lnd`
    */
  lazy val toWriteableString: String = lines.mkString("\n")

  /** Splits the provided lines into pairs of keys/values based on `=`, and then
    * applies the provided `collect` function on those pairs
    */
  private def collectFrom(
      lines: Seq[String]
  )(collect: PartialFunction[(String, String), String]): Seq[String] = {

    val splittedPairs = {
      val splitLines = lines.map(
        _.split("=")
          .map(_.trim)
          .toList
      )

      splitLines.collect { case h :: t :: _ =>
        h -> t
      }
    }

    splittedPairs.collect(collect)
  }

  /** The blockchain network associated with this `lnd` config */
  lazy val network: BitcoinNetwork = {
    val isMainnet = getValue("bitcoin.mainnet").exists(v =>
      v.toLowerCase == "true" || v.toInt == 1)
    val isTestnet = getValue("bitcoin.testnet").exists(v =>
      v.toLowerCase == "true" || v.toInt == 1)
    val isSignet = getValue("bitcoin.signet").exists(v =>
      v.toLowerCase == "true" || v.toInt == 1)
    val isRegtest = getValue("bitcoin.regtest").exists(v =>
      v.toLowerCase == "true" || v.toInt == 1)

    if (isMainnet && !(isRegtest || isSignet || isTestnet)) MainNet
    else if (isTestnet && !(isRegtest || isSignet || isMainnet)) TestNet3
    else if (isSignet && !(isRegtest || isTestnet || isMainnet)) SigNet
    else if (isRegtest && !(isSignet || isTestnet || isMainnet)) RegTest
    else
      throw new IllegalArgumentException("No network config set in lnd.conf")
  }

  private[config] def getValue(key: String): Option[String] = {
    val linesToSearchIn =
      lines.takeWhile(l => !l.trim.startsWith("[") || !l.trim.startsWith(";"))
    val collect = collectFrom(linesToSearchIn)(_)
    collect { case (`key`, value) =>
      value
    }.headOption
  }

  lazy val listenBinding: URI = new URI({
    val baseUrl = getValue("listen").getOrElse("127.0.0.1:9735")
    if (baseUrl.startsWith("http")) baseUrl
    else "http://" + baseUrl
  })

  lazy val rpcBinding: URI = {
    val uri = new URI({
      val baseUrl = getValue("rpclisten").getOrElse("127.0.0.1:10009")
      if (baseUrl.startsWith("http")) baseUrl
      else "http://" + baseUrl
    })

    // if our local lnd is bound to 0.0.0.0
    // then we can just make requests to localhost
    if (uri.getHost == "0.0.0.0") {
      logger.warn("lnd rpc is bound to 0.0.0.0, using localhost instead")
      new URI(s"http://127.0.0.1:${uri.getPort}")
    } else uri
  }

  lazy val restBinding: URI = new URI({
    val baseUrl = getValue("restlisten").getOrElse("127.0.0.1:8080")
    if (baseUrl.startsWith("http")) baseUrl
    else "http://" + baseUrl
  })

  lazy val debuglevel: LogLevel = getValue("debuglevel")
    .flatMap(LogLevel.fromStringOpt)
    .getOrElse(LogLevel.Debug)

  /** Creates a new config with the given keys and values appended */
  def withOption(key: String, value: String): LndConfig = {
    val ourLines = this.lines
    val newLine = s"$key=$value"
    val lines = newLine +: ourLines
    val newConfig = LndConfig(lines, datadir)
    logger.debug(
      s"Appending new config with $key=$value to datadir=${datadir.getAbsolutePath}"
    )
    LndConfig.writeConfigToFile(newConfig, datadir)

    newConfig
  }

  def withDatadir(newDatadir: File): LndConfig = {
    LndConfig(lines, newDatadir)
  }

  lazy val lndInstance: LndInstanceLocal = LndInstanceLocal(
    datadir.toPath,
    network,
    listenBinding,
    restBinding,
    rpcBinding,
    debuglevel
  )

  lazy val lndInstanceRemote: LndInstanceRemote = {
    val macaroon = {
      val path = datadir.toPath
        .resolve("data")
        .resolve("chain")
        .resolve("bitcoin")
        .resolve(LndInstanceLocal.getNetworkDirName(network))
        .resolve("admin.macaroon")
      val bytes = Files.readAllBytes(path)
      ByteVector(bytes).toHex
    }

    val cert = datadir.toPath.resolve("tls.cert").toFile

    LndInstanceRemote(rpcBinding, macaroon, cert)
  }
}

object LndConfig extends ConfigFactory[LndConfig] with BitcoinSLogger {

  /** The empty `lnd` config */
  override lazy val empty: LndConfig = LndConfig("", DEFAULT_DATADIR)

  /** Constructs a `lnd` config from the given string, by splitting it on
    * newlines
    */
  override def apply(config: String, datadir: File): LndConfig =
    apply(config.split("\n").toList, datadir)

  /** Reads the given path and construct a `lnd` config from it */
  override def apply(config: Path): LndConfig =
    apply(config.toFile, config.getParent.toFile)

  /** Reads the given file and construct a `lnd` config from it */
  override def apply(
      config: File,
      datadir: File = DEFAULT_DATADIR
  ): LndConfig = {
    val lines = Files
      .readAllLines(config.toPath)
      .iterator()
      .asScala
      .toList

    apply(lines, datadir)
  }

  override def fromConfigFile(file: File): LndConfig = {
    apply(file.toPath)
  }

  override def fromDataDir(dir: File): LndConfig = {
    apply(dir.toPath.resolve("lnd.conf"))
  }

  /** If there is a `lnd.conf` in the default data directory, this is read.
    * Otherwise, the default configuration is returned.
    */
  override def fromDefaultDatadir: LndConfig = {
    if (DEFAULT_CONF_FILE.isFile) {
      apply(DEFAULT_CONF_FILE)
    } else {
      LndConfig.empty
    }
  }

  override val DEFAULT_DATADIR: File = {
    val path = if (Properties.isMac) {
      Paths.get(Properties.userHome, "Library", "Application Support", "Lnd")
    } else if (Properties.isWin) {
      Paths.get("C:", "Users", Properties.userName, "Appdata", "Local", "Lnd")
    } else {
      Paths.get(Properties.userHome, ".lnd")
    }
    path.toFile
  }

  /** Default location of lnd conf file */
  override val DEFAULT_CONF_FILE: File = DEFAULT_DATADIR.toPath
    .resolve("lnd.conf")
    .toFile

  /** Writes the config to the data directory within it, if it doesn't exist.
    * Returns the written file.
    */
  override def writeConfigToFile(config: LndConfig, datadir: File): Path = {

    val confStr = config.lines.mkString("\n")

    Files.createDirectories(datadir.toPath)
    val confFile = datadir.toPath.resolve("lnd.conf")

    if (datadir == DEFAULT_DATADIR && confFile == DEFAULT_CONF_FILE.toPath) {
      logger.warn(
        s"We will not overwrite the existing lnd.conf in default datadir"
      )
    } else {
      Files.write(confFile, confStr.getBytes)
    }

    confFile
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy