<commit_before>package org.bitcoins.rpc.client.common

import java.util.UUID

import akka.actor.ActorSystem
import akka.http.javadsl.model.headers.HttpCredentials
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.stream.{ActorMaterializer, StreamTcpException}
import akka.util.ByteString
import org.bitcoins.core.config.{MainNet, NetworkParameters, RegTest, TestNet3}
import org.bitcoins.core.crypto.ECPrivateKey
import org.bitcoins.core.util.{BitcoinSLogger, FutureUtil, StartStop}
import org.bitcoins.rpc.config.BitcoindInstance
import org.bitcoins.commons.serializers.JsonSerializers._
import org.bitcoins.rpc.util.AsyncUtil

import scala.concurrent._
import scala.concurrent.duration.DurationInt
import scala.sys.process._
import scala.util.{Failure, Success, Try}
import java.nio.file.Files

import org.bitcoins.rpc.config.BitcoindAuthCredentials.CookieBased
import org.bitcoins.rpc.config.BitcoindAuthCredentials.PasswordBased
import java.nio.file.Path

import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts
import org.bitcoins.rpc.config.BitcoindAuthCredentials
import org.bitcoins.rpc.BitcoindException
import play.api.libs.json._

/**
  * This is the base trait for Bitcoin Core
  * RPC clients. It defines no RPC calls
  * except for the a ping. It contains functionality
  * and utilities useful when working with an RPC
  * client, like data directories, log files
  * and whether or not the client is started.
  */
trait Client extends BitcoinSLogger with StartStop[BitcoindRpcClient] {
  def version: BitcoindVersion
  protected val instance: BitcoindInstance

  /**
    * The log file of the Bitcoin Core daemon
    */
  lazy val logFile: Path = {

    val prefix = instance.network match {
      case MainNet  => ""
      case TestNet3 => "testnet"
      case RegTest  => "regtest"
    }
    instance.datadir.toPath.resolve(prefix).resolve("debug.log")
  }

  /** The configuration file of the Bitcoin Core daemon */
  lazy val confFile: Path =
    instance.datadir.toPath.resolve("bitcoin.conf")

  implicit protected val system: ActorSystem
  implicit protected val materializer: ActorMaterializer =
    ActorMaterializer.create(system)
  implicit protected val executor: ExecutionContext = system.getDispatcher
  implicit protected val network: NetworkParameters = instance.network

  /**
    * This is here (and not in JsonWrriters)
    * so that the implicit network val is accessible
    */
  implicit object ECPrivateKeyWrites extends Writes[ECPrivateKey] {
    override def writes(o: ECPrivateKey): JsValue = JsString(o.toWIF(network))
  }

  implicit val eCPrivateKeyWrites: Writes[ECPrivateKey] = ECPrivateKeyWrites
  implicit val importMultiAddressWrites: Writes[RpcOpts.ImportMultiAddress] =
    Json.writes[RpcOpts.ImportMultiAddress]
  implicit val importMultiRequestWrites: Writes[RpcOpts.ImportMultiRequest] =
    Json.writes[RpcOpts.ImportMultiRequest]
  private val resultKey: String = "result"
  private val errorKey: String = "error"

  def getDaemon: BitcoindInstance = instance

  /** Starts bitcoind on the local system.
    * @return a future that completes when bitcoind is fully started.
    *         This future times out after 60 seconds if the client
    *         cannot be started
    */
  override def start(): Future[BitcoindRpcClient] = {
    if (version != BitcoindVersion.Unknown) {
      val foundVersion = instance.getVersion
      if (foundVersion != version) {
        throw new RuntimeException(
          s"Wrong version for bitcoind RPC client! Expected $version, got $foundVersion")
      }
    }

    val binaryPath = instance.binary.getAbsolutePath
    val cmd = List(binaryPath,
                   "-datadir=" + instance.datadir,
                   "-rpcport=" + instance.rpcUri.getPort,
                   "-port=" + instance.uri.getPort)

    logger.debug(
      s"starting bitcoind with datadir ${instance.datadir} and binary path $binaryPath")
    val _ = Process(cmd).run()

    def isStartedF: Future[Boolean] = {
      val started: Promise[Boolean] = Promise()

      val pingF = bitcoindCall[Unit]("ping", printError = false)
      pingF.onComplete {
        case Success(_) => started.success(true)
        case Failure(_) => started.success(false)
      }

      started.future
    }

    // if we're doing cookie based authentication, we might attempt
    // to read the cookie file before it's written. this ensures
    // we avoid that
    val awaitCookie: BitcoindAuthCredentials => Future[Unit] = {
      case cookie: CookieBased =>
        val cookieExistsF =
          AsyncUtil.retryUntilSatisfied(Files.exists(cookie.cookiePath))
        cookieExistsF.onComplete {
          case Failure(exception) =>
            logger.error(s"Cookie filed was never created! $exception")
          case _: Success[_] =>
        }
        cookieExistsF
      case _: PasswordBased => Future.successful(())

    }

    val started: Future[BitcoindRpcClient] = {
      for {
        _ <- awaitCookie(instance.authCredentials)
        _ <- AsyncUtil.retryUntilSatisfiedF(() => isStartedF,
                                            duration = 1.seconds,
                                            maxTries = 60)
      } yield this.asInstanceOf[BitcoindRpcClient]
    }

    started.onComplete {
      case Success(_) => logger.debug(s"started bitcoind")
      case Failure(exc) =>
        logger.info(
          s"Could not start bitcoind instance! Message: ${exc.getMessage}")
        // When we're unable to start bitcoind that's most likely
        // either a configuration error or bug in Bitcoin-S. In either
        // case it's much easier to debug this with conf and logs
        // dumped somewhere. Especially in tests this is
        // convenient, as our test framework deletes the data directories
        // of our instances. We don't want to do this on mainnet,
        // as both the logs and conf file most likely contain sensitive
        // information
        if (network != MainNet) {
          val tempfile = Files.createTempFile("bitcoind-log-", ".dump")
          val logfile = Files.readAllBytes(logFile)
          Files.write(tempfile, logfile)
          logger.info(s"Dumped debug.log to $tempfile")

          val otherTempfile = Files.createTempFile("bitcoin-conf-", ".dump")
          val conffile = Files.readAllBytes(confFile)
          Files.write(otherTempfile, conffile)
          logger.info(s"Dumped bitcoin.conf to $otherTempfile")
        }
    }
    started
  }

  /**
    * Checks whether the underlying bitcoind daemon is running
    */
  def isStartedF: Future[Boolean] = {
    def tryPing: Future[Boolean] = {
      val request = buildRequest(instance, "ping", JsArray.empty)
      val responseF = sendRequest(request)

      val payloadF: Future[JsValue] =
        responseF.flatMap(getPayload(_, command = "ping", request = request))

      // Ping successful if no error can be parsed from the payload
      val parsedF = payloadF.map { payload =>
        (payload \ errorKey).validate[BitcoindException] match {
          case _: JsSuccess[BitcoindException] => false
          case _: JsError                      => true
        }
      }

      parsedF.recover {
        case exc: StreamTcpException
            if exc.getMessage.contains("Connection refused") =>
          false
      }
    }

    instance.authCredentials match {
      case cookie: CookieBased if Files.notExists(cookie.cookiePath) =>
        // if the cookie file doesn't exist we're not started
        Future.successful(false)
      case (CookieBased(_, _) | PasswordBased(_, _)) => tryPing
    }
  }

  /**<commit_msg>Add JsonParseException catch clause in the isStartedF method (#1334)<commit_after>package org.bitcoins.rpc.client.common

import java.util.UUID

import akka.actor.ActorSystem
import akka.http.javadsl.model.headers.HttpCredentials
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.stream.{ActorMaterializer, StreamTcpException}
import akka.util.ByteString
import org.bitcoins.core.config.{MainNet, NetworkParameters, RegTest, TestNet3}
import org.bitcoins.core.crypto.ECPrivateKey
import org.bitcoins.core.util.{BitcoinSLogger, FutureUtil, StartStop}
import org.bitcoins.rpc.config.BitcoindInstance
import org.bitcoins.commons.serializers.JsonSerializers._
import org.bitcoins.rpc.util.AsyncUtil

import scala.concurrent._
import scala.concurrent.duration.DurationInt
import scala.sys.process._
import scala.util.{Failure, Success, Try}
import java.nio.file.Files

import org.bitcoins.rpc.config.BitcoindAuthCredentials.CookieBased
import org.bitcoins.rpc.config.BitcoindAuthCredentials.PasswordBased
import java.nio.file.Path

import com.fasterxml.jackson.core.JsonParseException
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts
import org.bitcoins.rpc.config.BitcoindAuthCredentials
import org.bitcoins.rpc.BitcoindException
import play.api.libs.json._

/**
  * This is the base trait for Bitcoin Core
  * RPC clients. It defines no RPC calls
  * except for the a ping. It contains functionality
  * and utilities useful when working with an RPC
  * client, like data directories, log files
  * and whether or not the client is started.
  */
trait Client extends BitcoinSLogger with StartStop[BitcoindRpcClient] {
  def version: BitcoindVersion
  protected val instance: BitcoindInstance

  /**
    * The log file of the Bitcoin Core daemon
    */
  lazy val logFile: Path = {

    val prefix = instance.network match {
      case MainNet  => ""
      case TestNet3 => "testnet"
      case RegTest  => "regtest"
    }
    instance.datadir.toPath.resolve(prefix).resolve("debug.log")
  }

  /** The configuration file of the Bitcoin Core daemon */
  lazy val confFile: Path =
    instance.datadir.toPath.resolve("bitcoin.conf")

  implicit protected val system: ActorSystem
  implicit protected val materializer: ActorMaterializer =
    ActorMaterializer.create(system)
  implicit protected val executor: ExecutionContext = system.getDispatcher
  implicit protected val network: NetworkParameters = instance.network

  /**
    * This is here (and not in JsonWrriters)
    * so that the implicit network val is accessible
    */
  implicit object ECPrivateKeyWrites extends Writes[ECPrivateKey] {
    override def writes(o: ECPrivateKey): JsValue = JsString(o.toWIF(network))
  }

  implicit val eCPrivateKeyWrites: Writes[ECPrivateKey] = ECPrivateKeyWrites
  implicit val importMultiAddressWrites: Writes[RpcOpts.ImportMultiAddress] =
    Json.writes[RpcOpts.ImportMultiAddress]
  implicit val importMultiRequestWrites: Writes[RpcOpts.ImportMultiRequest] =
    Json.writes[RpcOpts.ImportMultiRequest]
  private val resultKey: String = "result"
  private val errorKey: String = "error"

  def getDaemon: BitcoindInstance = instance

  /** Starts bitcoind on the local system.
    * @return a future that completes when bitcoind is fully started.
    *         This future times out after 60 seconds if the client
    *         cannot be started
    */
  override def start(): Future[BitcoindRpcClient] = {
    if (version != BitcoindVersion.Unknown) {
      val foundVersion = instance.getVersion
      if (foundVersion != version) {
        throw new RuntimeException(
          s"Wrong version for bitcoind RPC client! Expected $version, got $foundVersion")
      }
    }

    val binaryPath = instance.binary.getAbsolutePath
    val cmd = List(binaryPath,
                   "-datadir=" + instance.datadir,
                   "-rpcport=" + instance.rpcUri.getPort,
                   "-port=" + instance.uri.getPort)

    logger.debug(
      s"starting bitcoind with datadir ${instance.datadir} and binary path $binaryPath")
    val _ = Process(cmd).run()

    def isStartedF: Future[Boolean] = {
      val started: Promise[Boolean] = Promise()

      val pingF = bitcoindCall[Unit]("ping", printError = false)
      pingF.onComplete {
        case Success(_) => started.success(true)
        case Failure(_) => started.success(false)
      }

      started.future
    }

    // if we're doing cookie based authentication, we might attempt
    // to read the cookie file before it's written. this ensures
    // we avoid that
    val awaitCookie: BitcoindAuthCredentials => Future[Unit] = {
      case cookie: CookieBased =>
        val cookieExistsF =
          AsyncUtil.retryUntilSatisfied(Files.exists(cookie.cookiePath))
        cookieExistsF.onComplete {
          case Failure(exception) =>
            logger.error(s"Cookie filed was never created! $exception")
          case _: Success[_] =>
        }
        cookieExistsF
      case _: PasswordBased => Future.successful(())

    }

    val started: Future[BitcoindRpcClient] = {
      for {
        _ <- awaitCookie(instance.authCredentials)
        _ <- AsyncUtil.retryUntilSatisfiedF(() => isStartedF,
                                            duration = 1.seconds,
                                            maxTries = 60)
      } yield this.asInstanceOf[BitcoindRpcClient]
    }

    started.onComplete {
      case Success(_) => logger.debug(s"started bitcoind")
      case Failure(exc) =>
        logger.info(
          s"Could not start bitcoind instance! Message: ${exc.getMessage}")
        // When we're unable to start bitcoind that's most likely
        // either a configuration error or bug in Bitcoin-S. In either
        // case it's much easier to debug this with conf and logs
        // dumped somewhere. Especially in tests this is
        // convenient, as our test framework deletes the data directories
        // of our instances. We don't want to do this on mainnet,
        // as both the logs and conf file most likely contain sensitive
        // information
        if (network != MainNet) {
          val tempfile = Files.createTempFile("bitcoind-log-", ".dump")
          val logfile = Files.readAllBytes(logFile)
          Files.write(tempfile, logfile)
          logger.info(s"Dumped debug.log to $tempfile")

          val otherTempfile = Files.createTempFile("bitcoin-conf-", ".dump")
          val conffile = Files.readAllBytes(confFile)
          Files.write(otherTempfile, conffile)
          logger.info(s"Dumped bitcoin.conf to $otherTempfile")
        }
    }
    started
  }

  /**
    * Checks whether the underlying bitcoind daemon is running
    */
  def isStartedF: Future[Boolean] = {
    def tryPing: Future[Boolean] = {
      val request = buildRequest(instance, "ping", JsArray.empty)
      val responseF = sendRequest(request)

      val payloadF: Future[JsValue] =
        responseF.flatMap(getPayload(_, command = "ping", request = request))

      // Ping successful if no error can be parsed from the payload
      val parsedF = payloadF.map { payload =>
        (payload \ errorKey).validate[BitcoindException] match {
          case _: JsSuccess[BitcoindException] => false
          case _: JsError                      => true
        }
      }

      parsedF.recover {
        case exc: StreamTcpException
            if exc.getMessage.contains("Connection refused") =>
          false
        case _: JsonParseException =>
          //see https://github.com/bitcoin-s/bitcoin-s/issues/527
          false
      }
    }

    instance.authCredentials match {
      case cookie: CookieBased if Files.notExists(cookie.cookiePath) =>
        // if the cookie file doesn't exist we're not started
        Future.successful(false)
      case (CookieBased(_, _) | PasswordBased(_, _)) => tryPing
    }
  }

  /**
{'fraction_non_alphanumeric': 0.08990068360634593, 'fraction_numerical': 0.001354314458919128, 'mean_word_length': 2.8641913780214305, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 42, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 1}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4556773', 'n_tokens_mistral': 4465, 'n_tokens_neox': 4355, 'n_words': 1197}