<commit_before>package scorex.waves

import akka.actor.ActorSystem
import akka.pattern.ask
import akka.util.Timeout
import com.ning.http.client.Response
import com.typesafe.config.ConfigFactory
import com.wavesplatform.Application
import com.wavesplatform.settings.WavesSettings
import dispatch.{Http, Req, url}
import org.scalatest.concurrent.Eventually
import org.scalatest.{BeforeAndAfterAll, Suite}
import play.api.libs.json._
import scorex.account.Account
import scorex.api.http.ApiKeyNotValid
import scorex.api.http.assets._
import scorex.api.http.leasing.{LeaseCancelRequest, LeaseRequest}
import scorex.consensus.mining.BlockGeneratorController.{GetBlockGenerationStatus, Idle, StartGeneration, StopGeneration}
import scorex.crypto.encode.Base58
import scorex.transaction.AssetAcc
import scorex.transaction.state.database.blockchain.StoredState
import scorex.utils._

import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.util.Random

trait TestingCommons extends Suite with BeforeAndAfterAll with Eventually {

  override def beforeAll(): Unit = {
    super.beforeAll()
  }

  override def afterAll(): Unit = {
    super.afterAll()
    stop()
  }

  def randomFrom[T](seq: Seq[T]): T = {
    require(seq.nonEmpty)
    seq(Random.nextInt(seq.length))
  }

  implicit val timeout = Timeout(1.second)

  private val config1 = ConfigFactory.parseString(
    """
      |waves {
      |  directory: ""
      |  logging-level: DEBUG
      |  network {
      |    file: ""
      |    bind-address: "127.0.0.1"
      |    port: 9091
      |    node-name: "Node-1"
      |    known-peers = ["127.0.0.1:9088","127.0.0.1:9084"]
      |    local-only: yes
      |    max-connections: 10
      |    peers-broadcast-interval: 500
      |    upnp {
      |      enable: no
      |    }
      |  }
      |  wallet {
      |    file: ""
      |    password: "<PASSWORD>"
      |    seed: "FQgbSAm6swGbtqA3NE8PttijPhT4N3Ufh4bHFAkyVnQz"
      |  }
      |  blockchain {
      |    file: ""
      |    type: CUSTOM
      |    custom {
      |      address-scheme-character: "T"
      |      functionality {
      |        allow-temporary-negative-until: 1477958400000
      |        allow-invalid-payment-transactions-by-timestamp: 1477958400000
      |        require-sorted-transactions-after: 1477958400000
      |        generation-balance-depth-from-50-to-1000-after-height: 0
      |        minimal-generating-balance-after: 0
      |        allow-transactions-from-future-until: 0
      |        allow-unissued-assets-until: 1479416400000
      |        allow-burn-transaction-after: 1481110521000
      |        allow-lease-transaction-after: 0
      |        require-payment-unique-id-after: 0
      |        allow-exchange-transaction-after: 0
      |      }
      |      genesis {
      |        timestamp: 1478000000000
      |        signature: "s1ohWATVbaej8m8wKC9jec5NjZTGR3f4DUtSUoVnmSBwetPcfwYrBLFuWM4bnRXa8gJKcWgAzdyie6fqxLUuDiY"
      |        initial-balance: 10000000000000000
      |        transactions = [
      |          {recipient: "3N3keodUiS8WLEw9W4BKDNxgNdUpwSnpb3K", amount: 400000000000000}
      |          {recipient: "3NBVqYXrapgJP9atQccdBPAgJP<KEY>", amount: 200000000000000}
      |          {recipient: "<KEY>", amount: 200000000000000}
      |          {recipient: "<KEY>", amount: 200000000000000}
      |          {recipient: "3N18z4B8kyyQ96PhN5eyhCAbg4j49CgwZJx", amount: 9000000000000000}
      |        ]
      |      }
      |    }
      |  }
      |  checkpoints {
      |    public-key: ""
      |  }
      |  fees {
      |    payment {
      |      WAVES = 0
      |    }
      |    issue {
      |      WAVES = 10000000
      |    }
      |    transfer {
      |      WAVES = 100000
      |    }
      |    reissue {
      |      WAVES = 100000
      |    }
      |    burn {
      |      WAVES = 100000
      |    }
      |    exchange {
      |      WAVES = 100000
      |    }
      |  }
      |  matcher {
      |    enable: yes
      |    account: "<KEY>"
      |    port: 9093
      |    journal-directory: ${waves.directory}"/journal"
      |    snapshots-directory: ${waves.directory}"/snapshots"
      |  }
      |  miner {
      |    enable: yes
      |    generation-delay: 500
      |    tf-like-scheduling: no
      |  }
      |  rest-api {
      |    enable: yes
      |    port: 9092
      |    api-key-hash: "<KEY>"
      |  }
      |  synchronization {
      |    score-broadcast-interval: 500
      |  }
      |  utx {
      |    size: 1000
      |    broadcast-interval: 500
      |  }
      |}
    """.stripMargin).withFallback(ConfigFactory.load()).resolve()
  val wavesSettings1: WavesSettings = WavesSettings.fromConfig(config1)

  private val config2 = ConfigFactory.parseString(
    """
      |waves {
      |  directory: ""
      |  logging-level: DEBUG
      |  network {
      |    file: ""
      |    bind-address: "127.0.0.1"
      |    port: 8084
      |    node-name: "Node-2"
      |    known-peers = ["127.0.0.1:9088","127.0.0.1:9091"]
      |    local-only: yes
      |    max-connections: 10
      |    peers-broadcast-interval: 900
      |    upnp {
      |      enable: no
      |    }
      |  }
      |  wallet {
      |    file: ""
      |    password: "<PASSWORD>"
      |    seed: "111"
      |  }
      |  blockchain {
      |    file: ""
      |    type: CUSTOM
      |    custom {
      |      address-scheme-character: "T"
      |      functionality {
      |        allow-temporary-negative-until: 1477958400000
      |        allow-invalid-payment-transactions-by-timestamp: 1477958400000
      |        require-sorted-transactions-after: 1477958400000
      |        generation-balance-depth-from-50-to-1000-after-height: 0
      |        minimal-generating-balance-after: 0
      |        allow-transactions-from-future-until: 0
      |        allow-unissued-assets-until: 1479416400000
      |        allow-burn-transaction-after: 1481110521000
      |        allow-lease-transaction-after: 0
      |        require-payment-unique-id-after: 0
      |        allow-exchange-transaction-after: 0
      |      }
      |      genesis {
      |        timestamp: 1478000000000
      |        signature: "s1ohWATVbaej8m8wKC9jec5NjZTGR3f4DUtSUoVnmSBwetPcfwYrBLFuWM4bnRXa8gJKcWgAzdyie6fqxLUuDiY"
      |        initial-balance: 10000000000000000
      |        transactions = [
      |          {recipient: "<KEY>", amount: 400000000000000}
      |          {recipient: "<KEY>", amount: 200000000000000}
      |          {recipient: "<KEY>", amount: 200000000000000}
      |          {recipient: "<KEY>", amount: 200000000000000}
      |          {recipient: "<KEY>", amount: 9000000000000000}
      |        ]
      |      }
      |    }
      |  }
      |  checkpoints {
      |    public-key: ""
      |  }
      |  matcher {
      |    enable: false
      |  }
      |  miner {
      |    enable: yes
      |    generation-delay: 500
      |  }
      |  rest-api {
      |    enable: yes
      |    port: 9085
      |    api-key-hash: "<KEY>"
      |  }
      |  synchronization {
      |    score-broadcast-interval: 500
      |  }
      |  utx {
      |    size: 1234
      |    broadcast-interval: 500
      |  }
      |}
    """.stripMargin).withFallback(ConfigFactory.load()).resolve()
  val wavesSettings2: WavesSettings = WavesSettings.fromConfig(config2)

  private val config3 = ConfigFactory.parseString(
    """
      |waves {
      |  directory: ""
      |  logging-level: DEBUG
      |  network {
      |    file: ""
      |    bind-address: "127.0.0.1"
      |    port: 9088
      |    node-name: "Node-3"
      |    known-peers = ["127.0.0.1:9084","127.0.0.1:9091"]
      |    local-only: yes
      |    max-connections: 10
      |    peers-broadcast-interval: 900
      |    upnp {
      |      enable: no
      |    }
      |  }
      |  wallet {
      |    file: ""
      |    password: "<PASSWORD>"
      |    seed: "222"
      |  }
      |  blockchain {
      |    file: ""
      |    type: CUSTOM
      |    custom {
      |      address-scheme-character: "T"
      |      functionality {
      |        allow-temporary-negative-until: 1477958400000
      |        allow-invalid-payment-transactions-by-timestamp: 1477958400000
      |        require-sorted-transactions-after: 1477958400000
      |        generation-balance-depth-from-50-to-1000-after-height: 0
      |        minimal-generating-balance-after: 0
      |        allow-transactions-from-future-until: 0
      |        allow-unissued-assets-until: 1479416400000
      |        allow-burn-transaction-after: 1481110521000
      |        allow-lease-transaction-after: 0
      |        require-payment-unique-id-after: 0
      |        allow-exchange-transaction-after: 0
      |      }
      |      genesis {
      |        timestamp: 1478000000000
      |        signature: "s1ohWATVbaej8m8wKC9jec5NjZTGR3f4DUtSUoVnmSBwetPcfwYrBLFuWM4bnRXa8gJKcWgAzdyie6fqxLUuDiY"
      |        initial-balance: 10000000000000000
      |        transactions = [
      |          {recipient: "<KEY>", amount: 400000000000000}
      |          {recipient: "<KEY>", amount: 200000000000000}
      |          {recipient: "<KEY>", amount: 200000000000000}
      |          {recipient: "<KEY>", amount: 200000000000000}
      |          {recipient: "3N18z4B8kyyQ96PhN5eyhCAbg4j49CgwZJx", amount: 9000000000000000}
      |        ]
      |      }
      |    }
      |  }
      |  checkpoints {
      |    public-key: ""
      |  }
      |  matcher {
      |    enable: no
      |  }
      |  miner {
      |    enable: yes
      |    generation-delay: 500
      |  }
      |  rest-api {
      |    enable: yes
      |    port: 9086
      |    api-key-hash: "<KEY>"
      |  }
      |  synchronization {
      |    score-broadcast-interval: 500
      |  }
      |  utx {
      |    size: 1234
      |    broadcast-interval: 500
      |  }
      |}
    """.stripMargin).withFallback(ConfigFactory.load()).resolve()
  val wavesSettings3: WavesSettings = WavesSettings.fromConfig(config3)


  val applications = List(
    new Application(ActorSystem("test0"), WavesSettings.fromConfig(config1)),
    new Application(ActorSystem("test1"), WavesSettings.fromConfig(config2)),
    new Application(ActorSystem("test2"), WavesSettings.fromConfig(config3)))

  applications.foreach(_.run())
  applications.foreach { a =>
    if (a.wallet.privateKeyAccounts().isEmpty) a.wallet.generateNewAccounts(3)
    untilTimeout(30.seconds, 3.second) {
      val request = Http(url(peerUrl(a) + "/consensus/algo").GET)
      val response = Await.result(request, 10.seconds)
      val json = Json.parse(response.getResponseBody).as[JsObject]
      assert((json \ "consensusAlgo").asOpt[String].isDefined)
    }
  }

  val application: Application = applications.head

  def stop(): Unit = {
    applications.foreach(a => {
      if (a.settings.matcherSettings.enable) {
        a.shutdownMatcher()
      }
      a.shutdown()
    })
  }

  def waitForNextBlock(application: Application): Unit = {
    val history = application.transactionModule.blockStorage.history
    val initialHeight = history.height()
    untilTimeout(15.seconds, 1.second) {
      require(history.height() > initialHeight)
    }
  }

  def getConnectedPeersCount(app: Application): Int = {
    val response = GET.requestJson("/peers/connected", peer = peerUrl(app))
    val peers = (response \ "peers").asOpt[JsArray]

    peers.get.value.size
  }

  def waitForSingleConnection(application: Application): Unit = {
    untilTimeout(30.seconds, 1.second) {
      require(getConnectedPeersCount(application) > 0)
    }
  }

  def assetTransfer(from: Account, to: Account, amount: Long, fee: Long = 100000L, assetId: Option[String] = None, feeAssetId: Option[String] = None, assertSuccess: Boolean = true)(implicit storedState: StoredState): Option[String] = {
    val json = Json.toJson(TransferRequest(assetId, feeAssetId, amount, fee, from.address, None, to.address)).toString()
    makeTxRequest(POST.requestRaw(us = "/assets/transfer", body = json), assertSuccess)
  }

  def payment(from: Account, to: Account, amount: Long, fee: Long = 100000L, assetId: Option[String] = None, assertSuccess: Boolean = true)(implicit storedState: StoredState): Option[String] = {
    val json = Json.toJson(PaymentRequest(amount, fee, from.address, to.address)).toString()
    makeTxRequest(POST.requestRaw(us = "/waves/payment", body = json), assertSuccess, idField = "signature")
  }

  def issue(from: Account, name: String, description: String, quantity: Long, decimals: Byte, reissuable: Boolean, fee: Long = 100000L, assertSuccess: Boolean = true)(implicit storedState: StoredState): Option[String] = {
    val json = Json.toJson(IssueRequest(from.address, name, description, quantity, decimals, reissuable, fee)).toString()
    makeTxRequest(POST.requestRaw(us = "/assets/issue", body = json), assertSuccess)
  }

  def reissue(from: Account, assetId: String, quantity: Long, reissuable: Boolean, fee: Long = 100000L, assertSuccess: Boolean = true)(implicit storedState: StoredState): Option[String] = {
    val json = Json.toJson(ReissueRequest(from.address, assetId, quantity, reissuable, fee)).toString()
    makeTxRequest(POST.requestRaw(us = "/assets/reissue", body = json), assertSuccess)
  }

  def burn(from: Account, assetId: String, quantity: Long, fee: Long = 100000L, assertSuccess: Boolean = true)(implicit storedState: StoredState): Option[String] = {
    val json = Json.toJson(BurnRequest(from.address, assetId, quantity, fee)).toString()
    makeTxRequest(POST.requestRaw(us = "/assets/burn", body = json), assertSuccess)
  }

  def lease(from: Account, to: Account, amount: Long, fee: Long = 100000L, assetId: Option[String] = None, assertSuccess: Boolean = true)(implicit storedState: StoredState): Option[String] = {
    val json = Json.toJson(LeaseRequest(from.address, amount, fee, to.address)).toString()
    makeTxRequest(POST.requestRaw(us = "/leasing/lease", body = json), assertSuccess)
  }

  def cancelLease(from: Account, leaseTxId: String, fee: Long = 100000L, assertSuccess: Boolean = true)(implicit storedState: StoredState): Unit = {
    val json = Json.toJson(LeaseCancelRequest(from.address, leaseTxId, fee)).toString()
    makeTxRequest(POST.requestRaw(us = "/leasing/cancel", body = json), assertSuccess)
  }

  def makeTxRequest(request: => Response, assertSuccess: Boolean = true, idField: String = "id"): Option[String] = {
    val response = request
    if (assertSuccess) {
      require(response.getStatusCode == 200)
      waitForNextBlock(application)
      Some((Json.parse(response.getResponseBody) \ idField).asOpt[String].get) // id should exists
    } else {
      require(response.getStatusCode != 200)
      None
    }
  }

  def waitForBalance(balance: Long, acc: Account, asset: Option[String] = None)(implicit storedState: StoredState): Unit = {
    val assetId = asset.flatMap(Base58.decode(_).toOption)
    eventually(timeout(5.seconds), interval(500.millis)) {
      Thread.sleep(100)
      require(storedState.assetBalance(AssetAcc(acc, assetId)) == balance)
    }
  }

  def waitForEffectiveBalance(balance: Long, acc: Account)(implicit storedState: StoredState): Unit = {
     eventually(timeout(5.seconds), interval(500.millis)) {
      Thread.sleep(100)
      require(storedState.effectiveBalance(acc) == balance)
    }
  }

  def forgeSignature(signature: Array[Byte]): Array[Byte] = {
    val modifier: BigInt = BigInt("7237005577332262213973186563042994240857116359379907606001950938285454250989")
    signature.take(32) ++ (BigInt(signature.takeRight(32).reverse) + modifier).toByteArray.reverse
  }

  def peerUrl(a: Application = application): String =
    "http://" + a.settings.restAPISettings.bindAddress + ":" + a.settings.restAPISettings.port

  def matcherUrl(a: Application = application): String =
    "http://" + a.settings.matcherSettings.bindAddress + ":" + a.settings.matcherSettings.port + "/matcher"

  def startGeneration(nodes: Seq[Application]): Unit = {
    nodes.foreach(_.blockGenerator ! StartGeneration)
  }

  def stopGeneration(nodes: Seq[Application]): Unit = {
    nodes.foreach(_.blockGenerator ! StopGeneration)
    untilTimeout(5.seconds) {
      Thread.sleep(100)
      nodes.foreach { p =>
        require(Await.result(p.blockGenerator ? GetBlockGenerationStatus, timeout.duration) == Idle.name)
      }
    }
  }

  object Request {<commit_msg>T473: implicit state is not used<commit_after>package scorex.waves

import akka.actor.ActorSystem
import akka.pattern.ask
import akka.util.Timeout
import com.ning.http.client.Response
import com.typesafe.config.ConfigFactory
import com.wavesplatform.Application
import com.wavesplatform.settings.WavesSettings
import dispatch.{Http, Req, url}
import org.scalatest.concurrent.Eventually
import org.scalatest.{BeforeAndAfterAll, Suite}
import play.api.libs.json._
import scorex.account.Account
import scorex.api.http.ApiKeyNotValid
import scorex.api.http.assets._
import scorex.api.http.leasing.{LeaseCancelRequest, LeaseRequest}
import scorex.consensus.mining.BlockGeneratorController.{GetBlockGenerationStatus, Idle, StartGeneration, StopGeneration}
import scorex.crypto.encode.Base58
import scorex.transaction.AssetAcc
import scorex.transaction.state.database.blockchain.StoredState
import scorex.utils._

import scala.concurrent.Await
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.util.Random

trait TestingCommons extends Suite with BeforeAndAfterAll with Eventually {

  override def beforeAll(): Unit = {
    super.beforeAll()
  }

  override def afterAll(): Unit = {
    super.afterAll()
    stop()
  }

  def randomFrom[T](seq: Seq[T]): T = {
    require(seq.nonEmpty)
    seq(Random.nextInt(seq.length))
  }

  implicit val timeout = Timeout(1.second)

  private val config1 = ConfigFactory.parseString(
    """
      |waves {
      |  directory: ""
      |  logging-level: DEBUG
      |  network {
      |    file: ""
      |    bind-address: "127.0.0.1"
      |    port: 9091
      |    node-name: "Node-1"
      |    known-peers = ["127.0.0.1:9088","127.0.0.1:9084"]
      |    local-only: yes
      |    max-connections: 10
      |    peers-broadcast-interval: 500
      |    upnp {
      |      enable: no
      |    }
      |  }
      |  wallet {
      |    file: ""
      |    password: "<PASSWORD>"
      |    seed: "<KEY>"
      |  }
      |  blockchain {
      |    file: ""
      |    type: CUSTOM
      |    custom {
      |      address-scheme-character: "T"
      |      functionality {
      |        allow-temporary-negative-until: 1477958400000
      |        allow-invalid-payment-transactions-by-timestamp: 1477958400000
      |        require-sorted-transactions-after: 1477958400000
      |        generation-balance-depth-from-50-to-1000-after-height: 0
      |        minimal-generating-balance-after: 0
      |        allow-transactions-from-future-until: 0
      |        allow-unissued-assets-until: 1479416400000
      |        allow-burn-transaction-after: 1481110521000
      |        allow-lease-transaction-after: 0
      |        require-payment-unique-id-after: 0
      |        allow-exchange-transaction-after: 0
      |      }
      |      genesis {
      |        timestamp: 1478000000000
      |        signature: "s1ohWATVbaej8m8wKC9jec5NjZTGR3f4DUtSUoVnmSBwetPcfwYrBLFuWM4bnRXa8gJKcWgAzdyie6fqxLUuDiY"
      |        initial-balance: 10000000000000000
      |        transactions = [
      |          {recipient: "<KEY>", amount: 400000000000000}
      |          {recipient: "<KEY>", amount: 200000000000000}
      |          {recipient: "<KEY>", amount: 200000000000000}
      |          {recipient: "<KEY>", amount: 200000000000000}
      |          {recipient: "<KEY>", amount: 9000000000000000}
      |        ]
      |      }
      |    }
      |  }
      |  checkpoints {
      |    public-key: ""
      |  }
      |  fees {
      |    payment {
      |      WAVES = 0
      |    }
      |    issue {
      |      WAVES = 10000000
      |    }
      |    transfer {
      |      WAVES = 100000
      |    }
      |    reissue {
      |      WAVES = 100000
      |    }
      |    burn {
      |      WAVES = 100000
      |    }
      |    exchange {
      |      WAVES = 100000
      |    }
      |  }
      |  matcher {
      |    enable: yes
      |    account: "3N3keodUiS8WLEw9W4BKDNxgNdUpwSnpb3K"
      |    port: 9093
      |    journal-directory: ${waves.directory}"/journal"
      |    snapshots-directory: ${waves.directory}"/snapshots"
      |  }
      |  miner {
      |    enable: yes
      |    generation-delay: 500
      |    tf-like-scheduling: no
      |  }
      |  rest-api {
      |    enable: yes
      |    port: 9092
      |    api-key-hash: "JDJkZrg24XwvBgBUi6PgpHzrAFgeefb7nU8LJPRR58ga"
      |  }
      |  synchronization {
      |    score-broadcast-interval: 500
      |  }
      |  utx {
      |    size: 1000
      |    broadcast-interval: 500
      |  }
      |}
    """.stripMargin).withFallback(ConfigFactory.load()).resolve()
  val wavesSettings1: WavesSettings = WavesSettings.fromConfig(config1)

  private val config2 = ConfigFactory.parseString(
    """
      |waves {
      |  directory: ""
      |  logging-level: DEBUG
      |  network {
      |    file: ""
      |    bind-address: "127.0.0.1"
      |    port: 8084
      |    node-name: "Node-2"
      |    known-peers = ["127.0.0.1:9088","127.0.0.1:9091"]
      |    local-only: yes
      |    max-connections: 10
      |    peers-broadcast-interval: 900
      |    upnp {
      |      enable: no
      |    }
      |  }
      |  wallet {
      |    file: ""
      |    password: "<PASSWORD>"
      |    seed: "111"
      |  }
      |  blockchain {
      |    file: ""
      |    type: CUSTOM
      |    custom {
      |      address-scheme-character: "T"
      |      functionality {
      |        allow-temporary-negative-until: 1477958400000
      |        allow-invalid-payment-transactions-by-timestamp: 1477958400000
      |        require-sorted-transactions-after: 1477958400000
      |        generation-balance-depth-from-50-to-1000-after-height: 0
      |        minimal-generating-balance-after: 0
      |        allow-transactions-from-future-until: 0
      |        allow-unissued-assets-until: 1479416400000
      |        allow-burn-transaction-after: 1481110521000
      |        allow-lease-transaction-after: 0
      |        require-payment-unique-id-after: 0
      |        allow-exchange-transaction-after: 0
      |      }
      |      genesis {
      |        timestamp: 1478000000000
      |        signature: "s1ohWATVbaej8m8wKC9jec5NjZTGR3f4DUtSUoVnmSBwetPcfwYrBLFuWM4bnRXa8gJKcWgAzdyie6fqxLUuDiY"
      |        initial-balance: 10000000000000000
      |        transactions = [
      |          {recipient: "<KEY>", amount: 400000000000000}
      |          {recipient: "<KEY>", amount: 200000000000000}
      |          {recipient: "<KEY>", amount: 200000000000000}
      |          {recipient: "<KEY>EEffSXy11L6hUi6fcBpd", amount: 200000000000000}
      |          {recipient: "3N18z4B8kyyQ96PhN5eyhCAbg4j49CgwZJx", amount: 9000000000000000}
      |        ]
      |      }
      |    }
      |  }
      |  checkpoints {
      |    public-key: ""
      |  }
      |  matcher {
      |    enable: false
      |  }
      |  miner {
      |    enable: yes
      |    generation-delay: 500
      |  }
      |  rest-api {
      |    enable: yes
      |    port: 9085
      |    api-key-hash: "<KEY>"
      |  }
      |  synchronization {
      |    score-broadcast-interval: 500
      |  }
      |  utx {
      |    size: 1234
      |    broadcast-interval: 500
      |  }
      |}
    """.stripMargin).withFallback(ConfigFactory.load()).resolve()
  val wavesSettings2: WavesSettings = WavesSettings.fromConfig(config2)

  private val config3 = ConfigFactory.parseString(
    """
      |waves {
      |  directory: ""
      |  logging-level: DEBUG
      |  network {
      |    file: ""
      |    bind-address: "127.0.0.1"
      |    port: 9088
      |    node-name: "Node-3"
      |    known-peers = ["127.0.0.1:9084","127.0.0.1:9091"]
      |    local-only: yes
      |    max-connections: 10
      |    peers-broadcast-interval: 900
      |    upnp {
      |      enable: no
      |    }
      |  }
      |  wallet {
      |    file: ""
      |    password: "<PASSWORD>"
      |    seed: "222"
      |  }
      |  blockchain {
      |    file: ""
      |    type: CUSTOM
      |    custom {
      |      address-scheme-character: "T"
      |      functionality {
      |        allow-temporary-negative-until: 1477958400000
      |        allow-invalid-payment-transactions-by-timestamp: 1477958400000
      |        require-sorted-transactions-after: 1477958400000
      |        generation-balance-depth-from-50-to-1000-after-height: 0
      |        minimal-generating-balance-after: 0
      |        allow-transactions-from-future-until: 0
      |        allow-unissued-assets-until: 1479416400000
      |        allow-burn-transaction-after: 1481110521000
      |        allow-lease-transaction-after: 0
      |        require-payment-unique-id-after: 0
      |        allow-exchange-transaction-after: 0
      |      }
      |      genesis {
      |        timestamp: 1478000000000
      |        signature: "s1ohWATVbaej8m8wKC9jec5NjZTGR3f4DUtSUoVnmSBwetPcfwYrBLFuWM4bnRXa8gJKcWgAzdyie6fqxLUuDiY"
      |        initial-balance: 10000000000000000
      |        transactions = [
      |          {recipient: "3N3keodUiS8WLEw9W4BKDNxgNdUpwSnpb3K", amount: 400000000000000}
      |          {recipient: "3NBVqYXrapgJP9atQccdBPAgJP<KEY>", amount: 200000000000000}
      |          {recipient: "<KEY>", amount: 200000000000000}
      |          {recipient: "3NCBMxgdghg4tUhEEffSXy11L6hUi6fcBpd", amount: 200000000000000}
      |          {recipient: "<KEY>", amount: 9000000000000000}
      |        ]
      |      }
      |    }
      |  }
      |  checkpoints {
      |    public-key: ""
      |  }
      |  matcher {
      |    enable: no
      |  }
      |  miner {
      |    enable: yes
      |    generation-delay: 500
      |  }
      |  rest-api {
      |    enable: yes
      |    port: 9086
      |    api-key-hash: "<KEY>"
      |  }
      |  synchronization {
      |    score-broadcast-interval: 500
      |  }
      |  utx {
      |    size: 1234
      |    broadcast-interval: 500
      |  }
      |}
    """.stripMargin).withFallback(ConfigFactory.load()).resolve()
  val wavesSettings3: WavesSettings = WavesSettings.fromConfig(config3)


  val applications = List(
    new Application(ActorSystem("test0"), WavesSettings.fromConfig(config1)),
    new Application(ActorSystem("test1"), WavesSettings.fromConfig(config2)),
    new Application(ActorSystem("test2"), WavesSettings.fromConfig(config3)))

  applications.foreach(_.run())
  applications.foreach { a =>
    if (a.wallet.privateKeyAccounts().isEmpty) a.wallet.generateNewAccounts(3)
    untilTimeout(30.seconds, 3.second) {
      val request = Http(url(peerUrl(a) + "/consensus/algo").GET)
      val response = Await.result(request, 10.seconds)
      val json = Json.parse(response.getResponseBody).as[JsObject]
      assert((json \ "consensusAlgo").asOpt[String].isDefined)
    }
  }

  val application: Application = applications.head

  def stop(): Unit = {
    applications.foreach(a => {
      if (a.settings.matcherSettings.enable) {
        a.shutdownMatcher()
      }
      a.shutdown()
    })
  }

  def waitForNextBlock(application: Application): Unit = {
    val history = application.transactionModule.blockStorage.history
    val initialHeight = history.height()
    untilTimeout(15.seconds, 1.second) {
      require(history.height() > initialHeight)
    }
  }

  def getConnectedPeersCount(app: Application): Int = {
    val response = GET.requestJson("/peers/connected", peer = peerUrl(app))
    val peers = (response \ "peers").asOpt[JsArray]

    peers.get.value.size
  }

  def waitForSingleConnection(application: Application): Unit = {
    untilTimeout(30.seconds, 1.second) {
      require(getConnectedPeersCount(application) > 0)
    }
  }

  def assetTransfer(from: Account, to: Account, amount: Long, fee: Long = 100000L, assetId: Option[String] = None, feeAssetId: Option[String] = None, assertSuccess: Boolean = true): Option[String] = {
    val json = Json.toJson(TransferRequest(assetId, feeAssetId, amount, fee, from.address, None, to.address)).toString()
    makeTxRequest(POST.requestRaw(us = "/assets/transfer", body = json), assertSuccess)
  }

  def payment(from: Account, to: Account, amount: Long, fee: Long = 100000L, assetId: Option[String] = None, assertSuccess: Boolean = true): Option[String] = {
    val json = Json.toJson(PaymentRequest(amount, fee, from.address, to.address)).toString()
    makeTxRequest(POST.requestRaw(us = "/waves/payment", body = json), assertSuccess, idField = "signature")
  }

  def issue(from: Account, name: String, description: String, quantity: Long, decimals: Byte, reissuable: Boolean, fee: Long = 100000L, assertSuccess: Boolean = true): Option[String] = {
    val json = Json.toJson(IssueRequest(from.address, name, description, quantity, decimals, reissuable, fee)).toString()
    makeTxRequest(POST.requestRaw(us = "/assets/issue", body = json), assertSuccess)
  }

  def reissue(from: Account, assetId: String, quantity: Long, reissuable: Boolean, fee: Long = 100000L, assertSuccess: Boolean = true): Option[String] = {
    val json = Json.toJson(ReissueRequest(from.address, assetId, quantity, reissuable, fee)).toString()
    makeTxRequest(POST.requestRaw(us = "/assets/reissue", body = json), assertSuccess)
  }

  def burn(from: Account, assetId: String, quantity: Long, fee: Long = 100000L, assertSuccess: Boolean = true): Option[String] = {
    val json = Json.toJson(BurnRequest(from.address, assetId, quantity, fee)).toString()
    makeTxRequest(POST.requestRaw(us = "/assets/burn", body = json), assertSuccess)
  }

  def lease(from: Account, to: Account, amount: Long, fee: Long = 100000L, assetId: Option[String] = None, assertSuccess: Boolean = true): Option[String] = {
    val json = Json.toJson(LeaseRequest(from.address, amount, fee, to.address)).toString()
    makeTxRequest(POST.requestRaw(us = "/leasing/lease", body = json), assertSuccess)
  }

  def cancelLease(from: Account, leaseTxId: String, fee: Long = 100000L, assertSuccess: Boolean = true): Unit = {
    val json = Json.toJson(LeaseCancelRequest(from.address, leaseTxId, fee)).toString()
    makeTxRequest(POST.requestRaw(us = "/leasing/cancel", body = json), assertSuccess)
  }

  def makeTxRequest(request: => Response, assertSuccess: Boolean = true, idField: String = "id"): Option[String] = {
    val response = request
    if (assertSuccess) {
      require(response.getStatusCode == 200)
      waitForNextBlock(application)
      Some((Json.parse(response.getResponseBody) \ idField).asOpt[String].get) // id should exists
    } else {
      require(response.getStatusCode != 200)
      None
    }
  }

  def waitForBalance(balance: Long, acc: Account, asset: Option[String] = None)(implicit storedState: StoredState): Unit = {
    val assetId = asset.flatMap(Base58.decode(_).toOption)
    eventually(timeout(5.seconds), interval(500.millis)) {
      Thread.sleep(100)
      require(storedState.assetBalance(AssetAcc(acc, assetId)) == balance)
    }
  }

  def waitForEffectiveBalance(balance: Long, acc: Account)(implicit storedState: StoredState): Unit = {
     eventually(timeout(5.seconds), interval(500.millis)) {
      Thread.sleep(100)
       require(storedState.effectiveBalance(acc) == balance)
    }
  }

  def forgeSignature(signature: Array[Byte]): Array[Byte] = {
    val modifier: BigInt = BigInt("7237005577332262213973186563042994240857116359379907606001950938285454250989")
    signature.take(32) ++ (BigInt(signature.takeRight(32).reverse) + modifier).toByteArray.reverse
  }

  def peerUrl(a: Application = application): String =
    "http://" + a.settings.restAPISettings.bindAddress + ":" + a.settings.restAPISettings.port

  def matcherUrl(a: Application = application): String =
    "http://" + a.settings.matcherSettings.bindAddress + ":" + a.settings.matcherSettings.port + "/matcher"

  def startGeneration(nodes: Seq[Application]): Unit = {
    nodes.foreach(_.blockGenerator ! StartGeneration)
  }

  def stopGeneration(nodes: Seq[Application]): Unit = {
    nodes.foreach(_.blockGenerator ! StopGeneration)
    untilTimeout(5.seconds) {
      Thread.sleep(100)
      nodes.foreach { p =>
        require(Await.result(p.blockGenerator ? GetBlockGenerationStatus, timeout.duration) == Idle.name)
      }
    }
  }

  object Request {
{'fraction_non_alphanumeric': 0.12285879453555237, 'fraction_numerical': 0.06088173547935619, 'mean_word_length': 2.3590189064895246, 'pattern_counts': {'":': 4, '<': 40, '<?xml version=': 0, '>': 52, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 18, 'substrings_counts': 44, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2267422', 'n_tokens_mistral': 12050, 'n_tokens_neox': 10524, 'n_words': 1759}