<commit_before>package com.wavesplatform.it.transactions

import com.wavesplatform.it.api.SyncHttpApi._
import com.wavesplatform.it.api.UnexpectedStatusCodeException
import com.wavesplatform.it.util._
import com.wavesplatform.state2.{BinaryDataEntry, BooleanDataEntry, DataEntry, IntegerDataEntry}
import org.scalatest.{Assertion, Assertions}
import play.api.libs.json._
import scorex.api.http.SignedDataRequest
import scorex.crypto.encode.Base58
import scorex.transaction.DataTransaction
import scorex.transaction.TransactionParser.TransactionType

import scala.concurrent.duration._
import scala.util.{Failure, Random, Try}

class DataTransactionSuite extends BaseTransactionSuite {
  private val fee = 100000

  test("sender's waves balance is decreased by fee.") {
    val (balance1, eff1) = notMiner.accountBalances(firstAddress)
    val entry = IntegerDataEntry("int", 0xcafebabe)
    val size = entry.valueBytes.size
    val data = List(entry)
    val transferFee = calcDataFee(size)
    val txId = sender.putData(firstAddress, data, transferFee).id
    nodes.waitForHeightAraiseAndTxPresent(txId)
    
    notMiner.assertBalances(firstAddress, balance1 - transferFee, eff1 - transferFee)
  }

  test("cannot transact without having enough waves") {
    val (balance1, eff1) = notMiner.accountBalances(firstAddress)

    val data = List(BooleanDataEntry("bool", false))
    assertBadRequestAndResponse(sender.putData(firstAddress, data, balance1 + 1), "negative waves balance")
    nodes.waitForHeightAraise()
    notMiner.assertBalances(firstAddress, balance1, eff1)

    val leaseAmount = 1.waves
    val leaseFee = 100000
    val leaseId = sender.lease(firstAddress, secondAddress, leaseAmount, leaseFee).id
    nodes.waitForHeightAraiseAndTxPresent(leaseId)

    assertBadRequestAndResponse(sender.putData(firstAddress, data, balance1 - leaseAmount), "negative effective balance")
    nodes.waitForHeightAraise()
    notMiner.assertBalances(firstAddress, balance1 - leaseFee, eff1 - leaseAmount - leaseFee)
  }

  test("invalid transaction should not be in UTX or blockchain") {
    def data(entries: List[DataEntry[_]] = List(IntegerDataEntry("int", 177)),
             fee: Long = 100000,
             timestamp: Long = System.currentTimeMillis,
             version: Byte = DataTransaction.Version): DataTransaction =
      DataTransaction.selfSigned(version, sender.privateKey, entries, fee, timestamp).right.get

    def request(tx: DataTransaction): SignedDataRequest =
      SignedDataRequest(DataTransaction.Version, Base58.encode(tx.sender.publicKey), tx.data, tx.fee, tx.timestamp, tx.proofs.base58().toList)

    implicit val w = Json.writes[SignedDataRequest].transform((jsobj: JsObject) =>
      jsobj + ("type" -> JsNumber(TransactionType.DataTransaction.id)))

    val (balance1, eff1) = notMiner.accountBalances(firstAddress)
    val invalidTxs = Seq(
      (data(timestamp = System.currentTimeMillis + 1.day.toMillis), "Transaction .* is from far future"),
      (data(fee = 99999), "Fee .* does not exceed minimal value"))

    for ((tx, diag) <- invalidTxs) {
      assertBadRequestAndResponse(sender.broadcastRequest(request(tx)), diag)
      nodes.foreach(_.ensureTxDoesntExist(tx.id().base58))
    }

    nodes.waitForHeightAraise()
    notMiner.assertBalances(firstAddress, balance1, eff1)
  }

  test("max transaction size") {
    import DataTransaction.MaxEntryCount
    import DataEntry.{MaxKeySize, MaxValueSize}

    val maxKey = "a" * MaxKeySize
    val data = List.tabulate(MaxEntryCount)(n => BinaryDataEntry(maxKey, Array.fill(MaxValueSize)(n.toByte)))
    val kilobytes = (MaxEntryCount * (MaxKeySize + MaxValueSize + 3) + 120) / 1024 + 1
    val fee = kilobytes * 100000
    val txId = sender.putData(firstAddress, data, fee).id

    nodes.waitForHeightAraiseAndTxPresent(txId)
  }

  test("data definition and retrieval") {
    // define first data item
    val item1 = IntegerDataEntry("int", 8)
    val tx1 = sender.putData(secondAddress, List(item1), fee).id
    nodes.waitForHeightAraiseAndTxPresent(tx1)

    sender.getData(secondAddress, "int") shouldBe item1
    sender.getData(secondAddress) shouldBe List(item1)

    // define another one
    val item2 = BooleanDataEntry("bool", true)
    val tx2 = sender.putData(secondAddress, List(item2), fee).id
    nodes.waitForHeightAraiseAndTxPresent(tx2)

    sender.getData(secondAddress, "int") shouldBe item1
    sender.getData(secondAddress, "bool") shouldBe item2
    sender.getData(secondAddress) shouldBe List(item2, item1)

    // redefine item 1
    val item11 = IntegerDataEntry("int", 10)
    val tx3 = sender.putData(secondAddress, List(item11), fee).id
    nodes.waitForHeightAraiseAndTxPresent(tx3)

    sender.getData(secondAddress, "int") shouldBe item11
    sender.getData(secondAddress, "bool") shouldBe item2
    sender.getData(secondAddress) shouldBe List(item2, item11)
    
    // define tx with all types
    val (balance2, eff2) = notMiner.accountBalances(secondAddress)
    val item41 = IntegerDataEntry("int", -127)
    val item42 = BooleanDataEntry("bool", false)
    val item43 = BinaryDataEntry("blob", Array[Byte](127.toByte, 0, 1, 1))
    val data = List(item41, item42, item43)
    val txId = sender.putData(secondAddress, data, fee).id
    nodes.waitForHeightAraiseAndTxPresent(txId)

    sender.getData(secondAddress, "int") shouldBe item41
    sender.getData(secondAddress, "bool") shouldBe item42
    sender.getData(secondAddress, "blob") shouldBe item43

    sender.getData(secondAddress) shouldBe List(item41, item42, item43)

    notMiner.assertBalances(secondAddress, balance2 - fee, eff2 - fee)
  }

  test("queries for nonexistent data") {
    def assertNotFound(url: String): Assertion = Try(sender.get(url)) match {
      case Failure(UnexpectedStatusCodeException(_, statusCode, responseBody)) =>
        statusCode shouldBe 404
        responseBody should include("no data for this key")
      case _ => Assertions.fail("Expected 404")
    }

    assertNotFound(s"/addresses/data/$secondAddress/foo")
    assertNotFound(s"/addresses/data/$thirdAddress/foo")
    sender.getData(thirdAddress) shouldBe List.empty
  }

  test("data definition corner cases") {
    val noDataTx = sender.putData(thirdAddress, List.empty, fee).id
    nodes.waitForHeightAraiseAndTxPresent(noDataTx)
    sender.getData(thirdAddress) shouldBe List.empty

    val key = "intKey"
    val multiKey = List.tabulate(5)(IntegerDataEntry(key, _))
    val twinKeyTx = sender.putData(thirdAddress, multiKey, fee).id<commit_msg>Update DataTransactionSuite.scala<commit_after>package com.wavesplatform.it.transactions

import com.wavesplatform.it.api.SyncHttpApi._
import com.wavesplatform.it.api.UnexpectedStatusCodeException
import com.wavesplatform.it.util._
import com.wavesplatform.state2.{BinaryDataEntry, BooleanDataEntry, DataEntry, IntegerDataEntry}
import org.scalatest.{Assertion, Assertions}
import play.api.libs.json._
import scorex.api.http.SignedDataRequest
import scorex.crypto.encode.Base58
import scorex.transaction.DataTransaction
import scorex.transaction.TransactionParser.TransactionType

import scala.concurrent.duration._
import scala.util.{Failure, Random, Try}

class DataTransactionSuite extends BaseTransactionSuite {
  private val fee = 100000

  test("sender's waves balance is decreased by fee.") {
    val (balance1, eff1) = notMiner.accountBalances(firstAddress)
    val entry = IntegerDataEntry("int", 0xcafebabe)
    val size = entry.valueBytes.size
    val data = List(entry)
    val transferFee = calcDataFee(size)
    val txId = sender.putData(firstAddress, data, transferFee).id
    nodes.waitForHeightAraiseAndTxPresent(txId)
    
    notMiner.assertBalances(firstAddress, balance1 - transferFee, eff1 - transferFee)
  }

  test("cannot transact without having enough waves") {
    val (balance1, eff1) = notMiner.accountBalances(firstAddress)

    val data = List(BooleanDataEntry("bool", false))
    assertBadRequestAndResponse(sender.putData(firstAddress, data, balance1 + 1), "negative waves balance")
    nodes.waitForHeightAraise()
    notMiner.assertBalances(firstAddress, balance1, eff1)

    val leaseAmount = 1.waves
    val leaseFee = 100000
    val leaseId = sender.lease(firstAddress, secondAddress, leaseAmount, leaseFee).id
    nodes.waitForHeightAraiseAndTxPresent(leaseId)

    assertBadRequestAndResponse(sender.putData(firstAddress, data, balance1 - leaseAmount), "negative effective balance")
    nodes.waitForHeightAraise()
    notMiner.assertBalances(firstAddress, balance1 - leaseFee, eff1 - leaseAmount - leaseFee)
  }

  test("invalid transaction should not be in UTX or blockchain") {
    def data(entries: List[DataEntry[_]] = List(IntegerDataEntry("int", 177)),
             fee: Long = 100000,
             timestamp: Long = System.currentTimeMillis,
             version: Byte = DataTransaction.Version): DataTransaction =
      DataTransaction.selfSigned(version, sender.privateKey, entries, fee, timestamp).right.get

    def request(tx: DataTransaction): SignedDataRequest =
      SignedDataRequest(DataTransaction.Version, Base58.encode(tx.sender.publicKey), tx.data, tx.fee, tx.timestamp, tx.proofs.base58().toList)

    implicit val w = Json.writes[SignedDataRequest].transform((jsobj: JsObject) =>
      jsobj + ("type" -> JsNumber(TransactionType.DataTransaction.id)))

    val (balance1, eff1) = notMiner.accountBalances(firstAddress)
    val invalidTxs = Seq(
      (data(timestamp = System.currentTimeMillis + 1.day.toMillis), "Transaction .* is from far future"),
      (data(fee = 99999), "Fee .* does not exceed minimal value"))

    for ((tx, diag) <- invalidTxs) {
      assertBadRequestAndResponse(sender.broadcastRequest(request(tx)), diag)
      nodes.foreach(_.ensureTxDoesntExist(tx.id().base58))
    }

    nodes.waitForHeightAraise()
    notMiner.assertBalances(firstAddress, balance1, eff1)
  }

  test("max transaction size") {
    import DataTransaction.MaxEntryCount
    import DataEntry.{MaxKeySize, MaxValueSize}

    val maxKey = "a" * MaxKeySize
    val data = List.tabulate(MaxEntryCount)(n => BinaryDataEntry(maxKey, Array.fill(MaxValueSize)(n.toByte)))
    val kilobytes = (MaxEntryCount * (MaxKeySize + MaxValueSize + 3) + 120) / 1024 + 1
    val fee = kilobytes * 100000
    val txId = sender.putData(firstAddress, data, fee).id

    nodes.waitForHeightAraiseAndTxPresent(txId)
  }

  test("data definition and retrieval") {
    // define first data item
    val item1 = IntegerDataEntry("int", 8)
    val tx1 = sender.putData(secondAddress, List(item1), fee).id
    nodes.waitForHeightAraiseAndTxPresent(tx1)

    sender.getData(secondAddress, "int") shouldBe item1
    sender.getData(secondAddress) shouldBe List(item1)

    // define another one
    val item2 = BooleanDataEntry("bool", true)
    val tx2 = sender.putData(secondAddress, List(item2), fee).id
    nodes.waitForHeightAraiseAndTxPresent(tx2)

    sender.getData(secondAddress, "int") shouldBe item1
    sender.getData(secondAddress, "bool") shouldBe item2
    sender.getData(secondAddress) shouldBe List(item2, item1)

    // redefine item 1
    val item11 = IntegerDataEntry("int", 10)
    val tx3 = sender.putData(secondAddress, List(item11), fee).id
    nodes.waitForHeightAraiseAndTxPresent(tx3)

    sender.getData(secondAddress, "int") shouldBe item11
    sender.getData(secondAddress, "bool") shouldBe item2
    sender.getData(secondAddress) shouldBe List(item2, item11)
    
    // define tx with all types
    val (balance2, eff2) = notMiner.accountBalances(secondAddress)
    val item41 = IntegerDataEntry("int", -127)
    val item42 = BooleanDataEntry("bool", false)
    val item43 = BinaryDataEntry("blob", Array[Byte](127.toByte, 0, 1, 1))
    val data = List(item41, item42, item43)
    val txId = sender.putData(secondAddress, data, fee).id
    nodes.waitForHeightAraiseAndTxPresent(txId)

    sender.getData(secondAddress, "int") shouldBe item41
    sender.getData(secondAddress, "bool") shouldBe item42
    sender.getData(secondAddress, "blob").equals(item43)

    sender.getData(secondAddress).equals(List(item41, item42, item43))

    notMiner.assertBalances(secondAddress, balance2 - fee, eff2 - fee)
  }

  test("queries for nonexistent data") {
    def assertNotFound(url: String): Assertion = Try(sender.get(url)) match {
      case Failure(UnexpectedStatusCodeException(_, statusCode, responseBody)) =>
        statusCode shouldBe 404
        responseBody should include("no data for this key")
      case _ => Assertions.fail("Expected 404")
    }

    assertNotFound(s"/addresses/data/$secondAddress/foo")
    assertNotFound(s"/addresses/data/$thirdAddress/foo")
    sender.getData(thirdAddress) shouldBe List.empty
  }

  test("data definition corner cases") {
    val noDataTx = sender.putData(thirdAddress, List.empty, fee).id
    nodes.waitForHeightAraiseAndTxPresent(noDataTx)
    sender.getData(thirdAddress) shouldBe List.empty

    val key = "intKey"
    val multiKey = List.tabulate(5)(IntegerDataEntry(key, _))
    val twinKeyTx = sender.putData(thirdAddress, multiKey, fee).id
{'fraction_non_alphanumeric': 0.10638460363525279, 'fraction_numerical': 0.02199480678173209, 'mean_word_length': 4.467640918580376, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 13, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, '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': '3021542', 'n_tokens_mistral': 4140, 'n_tokens_neox': 3924, 'n_words': 860}