Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NODE-2547 Fix lease action trace for /debug/validate/ #3790

Merged
merged 12 commits into from
Mar 16, 2023
27 changes: 11 additions & 16 deletions node/src/main/scala/com/wavesplatform/api/http/JsonFormats.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,28 @@ package com.wavesplatform.api.http
import com.wavesplatform.account.Address
import com.wavesplatform.lang.contract.meta.FunctionSignatures
import com.wavesplatform.transaction.Transaction
import com.wavesplatform.transaction.smart.script.trace.TraceStep
import play.api.libs.json.Json.JsValueWrapper
import play.api.libs.json.*
import play.api.libs.json.Json.JsValueWrapper

trait JsonFormats {
implicit lazy val wavesAddressWrites: Writes[Address] = Writes(w => JsString(w.toString))

implicit lazy val TransactionJsonWrites: OWrites[Transaction] = OWrites(_.json())

implicit lazy val logWrites: Writes[TraceStep] = Writes(_.json)

implicit lazy val functionSignaturesWrites: Writes[FunctionSignatures] =
(o: FunctionSignatures) =>
Json.obj(
"version" -> o.version.toString,
"version" -> o.version.toString,
"callableFuncTypes" -> Json.obj(
o.argsWithFuncName.map {
case (functionName, args) =>
val functionArgs: JsValueWrapper =
args.map {
case (argName, argType) =>
Json.obj(
"name" -> argName,
"type" -> argType.name
)
}
functionName -> functionArgs
o.argsWithFuncName.map { case (functionName, args) =>
val functionArgs: JsValueWrapper =
args.map { case (argName, argType) =>
Json.obj(
"name" -> argName,
"type" -> argType.name
)
}
functionName -> functionArgs
}.toSeq*
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@ import cats.instances.list.*
import cats.syntax.alternative.*
import cats.syntax.either.*
import cats.syntax.traverse.*
import com.wavesplatform.account.{Address, Alias}
import com.wavesplatform.account.{Address, AddressOrAlias, Alias}
import com.wavesplatform.api.common.{CommonTransactionsApi, TransactionMeta}
import com.wavesplatform.api.http.ApiError.*
import com.wavesplatform.block.Block
import com.wavesplatform.block.Block.TransactionProof
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.{Base58, *}
import com.wavesplatform.common.utils.Base58
import com.wavesplatform.database.protobuf.EthereumTransactionMeta.Payload
import com.wavesplatform.features.BlockchainFeatures
import com.wavesplatform.lang.v1.serialization.SerdeV1
import com.wavesplatform.network.TransactionPublisher
import com.wavesplatform.protobuf.transaction.PBAmounts
import com.wavesplatform.settings.RestAPISettings
import com.wavesplatform.state.{Blockchain, InvokeScriptResult, TxMeta}
import com.wavesplatform.state.reader.LeaseDetails
import com.wavesplatform.state.{Blockchain, InvokeScriptResult, TxMeta}
import com.wavesplatform.transaction.*
import com.wavesplatform.transaction.lease.*
import com.wavesplatform.transaction.serialization.impl.InvokeScriptTxSerializer
Expand Down Expand Up @@ -287,7 +287,7 @@ object TransactionsApiRoute {
def transactionMetaJson(meta: TransactionMeta): JsObject = {
val specificInfo = meta.transaction match {
case lease: LeaseTransaction =>
import com.wavesplatform.api.http.TransactionsApiRoute.LeaseStatus._
import com.wavesplatform.api.http.TransactionsApiRoute.LeaseStatus.*
Json.obj("status" -> (if (blockchain.leaseDetails(lease.id()).exists(_.isActive)) active else canceled))

case leaseCancel: LeaseCancelTransaction =>
Expand Down Expand Up @@ -355,22 +355,39 @@ object TransactionsApiRoute {
private[this] def isBlockV5(height: Int): Boolean = blockchain.isFeatureActivated(BlockchainFeatures.BlockV5, height)

// Extended lease format. Overrides default
private[this] def leaseIdToLeaseRef(leaseId: ByteStr): LeaseRef = {
val ld = blockchain.leaseDetails(leaseId).get
val tm = blockchain.transactionMeta(ld.sourceId).get
val recipient = blockchain.resolveAlias(ld.recipient).explicitGet()

val (status, cancelHeight, cancelTxId) = ld.status match {
case LeaseDetails.Status.Active => (true, None, None)
case LeaseDetails.Status.Cancelled(height, txId) => (false, Some(height), txId)
case LeaseDetails.Status.Expired(height) => (false, Some(height), None)
private[this] def leaseIdToLeaseRef(
leaseId: ByteStr,
recipientParamOpt: Option[AddressOrAlias] = None,
amountOpt: Option[Long] = None
): LeaseRef = {
val detailsOpt = blockchain.leaseDetails(leaseId)
val txMetaOpt = detailsOpt.flatMap(d => blockchain.transactionMeta(d.sourceId))
val recipientOpt = recipientParamOpt.orElse(detailsOpt.map(_.recipient))
val resolvedRecipientOpt = recipientOpt.flatMap(r => blockchain.resolveAlias(r).toOption)

val statusOpt = detailsOpt.map(_.status)
val status = LeaseStatus(statusOpt.contains(LeaseDetails.Status.Active))
val statusDataOpt = statusOpt.map {
case LeaseDetails.Status.Active => (None, None)
case LeaseDetails.Status.Cancelled(height, txId) => (Some(height), txId)
case LeaseDetails.Status.Expired(height) => (Some(height), None)
}

LeaseRef(leaseId, ld.sourceId, ld.sender.toAddress, recipient, ld.amount, tm.height, LeaseStatus(status), cancelHeight, cancelTxId)
LeaseRef(
leaseId,
detailsOpt.map(_.sourceId),
detailsOpt.map(_.sender.toAddress),
resolvedRecipientOpt,
amountOpt orElse detailsOpt.map(_.amount),
txMetaOpt.map(_.height),
status,
statusDataOpt.flatMap(_._1),
statusDataOpt.flatMap(_._2)
)
}

private[http] implicit val leaseWrites: OWrites[InvokeScriptResult.Lease] =
LeaseRef.jsonWrites.contramap((l: InvokeScriptResult.Lease) => leaseIdToLeaseRef(l.id))
LeaseRef.jsonWrites.contramap((l: InvokeScriptResult.Lease) => leaseIdToLeaseRef(l.id, Some(l.recipient), Some(l.amount)))

private[http] implicit val leaseCancelWrites: OWrites[InvokeScriptResult.LeaseCancel] =
LeaseRef.jsonWrites.contramap((l: InvokeScriptResult.LeaseCancel) => leaseIdToLeaseRef(l.id))
Expand All @@ -392,11 +409,11 @@ object TransactionsApiRoute {

private[this] final case class LeaseRef(
id: ByteStr,
originTransactionId: ByteStr,
sender: Address,
recipient: Address,
amount: Long,
height: Int,
originTransactionId: Option[ByteStr],
sender: Option[Address],
recipient: Option[Address],
amount: Option[Long],
height: Option[Int],
status: LeaseStatus = LeaseStatus.active,
cancelHeight: Option[Int] = None,
cancelTransactionId: Option[ByteStr] = None
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package com.wavesplatform.api

import java.util.NoSuchElementException
import java.util.concurrent.ExecutionException

import akka.http.scaladsl.marshalling.ToResponseMarshallable
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.*
Expand All @@ -23,6 +20,7 @@ import monix.execution.Scheduler
import org.slf4j.LoggerFactory
import play.api.libs.json.*

import java.util.concurrent.ExecutionException
import scala.concurrent.Future
import scala.util.control.NonFatal
import scala.util.{Failure, Success, Try}
Expand Down Expand Up @@ -137,7 +135,6 @@ package object http {
val jsonExceptionHandler: ExceptionHandler = ExceptionHandler {
case JsResultException(err) => complete(WrongJson(errors = err))
case PlayJsonException(cause, errors) => complete(WrongJson(cause, errors))
case e: NoSuchElementException => complete(WrongJson(Some(e)))
case e: IllegalArgumentException => complete(ApiError.fromValidationError(GenericError(e)))
case e: AssertionError => complete(ApiError.fromValidationError(GenericError(e)))
case e: ExecutionException if e.getCause != null && e.getCause != e => jsonExceptionHandler(e.getCause)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ case class InvokeScriptTrace(
) ++ (resultE match {
case Right(value) => TraceStep.maybeErrorJson(None) ++ Json.obj("result" -> TraceStep.scriptResultJson(invokeId, value))
case Left(e) => TraceStep.maybeErrorJson(Some(e))
}) ++ (if (logged && resultE.isRight) Json.obj(TraceStep.logJson(log)) else JsObject.empty)
}) ++ (if (logged) Json.obj(TraceStep.logJson(log)) else JsObject.empty)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,12 @@ import cats.kernel.Semigroup
import cats.syntax.either.*
import cats.syntax.semigroup.*
import cats.{Monad, StackSafeMonad}
import com.wavesplatform.api.http.ApiError
import com.wavesplatform.transaction.Transaction
import play.api.libs.json.{JsObject, Json}

final case class TracedResult[+E, +A](
resultE: Either[E, A],
trace: List[TraceStep] = Nil,
attributes: TracedResult.Attributes = Map.empty
) {
def transformE[B, E1](f: Either[E, A] => TracedResult[E1, B]): TracedResult[E1, B] = {
val newResultE = f(resultE)
newResultE.copy(trace = this.trace ::: newResultE.trace, attributes = this.attributes ++ newResultE.attributes)
}

def flatMap[B, E1 >: E](f: A => TracedResult[E1, B]): TracedResult[E1, B] = {
resultE match {
case Left(_) => this.asInstanceOf[TracedResult[E1, B]]
Expand All @@ -37,18 +29,6 @@ final case class TracedResult[+E, +A](
def withFilter(f: A => Boolean): TracedResult[E, A] =
copy(resultE.filterOrElse(f, throw new MatchError("TracedResult destructuring error")))

def json(implicit ev1: E => ApiError, ev2: A => Transaction): JsObject = {
val resultJson = resultE match {
case Right(value) => value.json()
case Left(e) => e.json
}
resultJson ++ Json.obj("trace" -> trace.map(_.json))
}

def loggedJson(implicit ev1: E => ApiError, ev2: A => Transaction): JsObject = {
this.json ++ Json.obj("trace" -> trace.map(_.loggedJson))
}

def attribute[T](key: TracedResult.Attribute): T =
attributes(key).asInstanceOf[T]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import com.wavesplatform.test.PropSpec
import com.wavesplatform.transaction.Asset.Waves
import com.wavesplatform.transaction.smart.InvokeScriptTransaction
import com.wavesplatform.transaction.smart.script.trace.{InvokeScriptTrace, TracedResult}
import com.wavesplatform.transaction.{Proofs, TxValidationError}
import com.wavesplatform.transaction.{Proofs, Transaction, TxValidationError}
import com.wavesplatform.utils.JsonMatchers
import play.api.libs.json.{JsObject, Json}

class TraceResultJsonTest extends PropSpec with JsonMatchers {
private val tx = (
Expand All @@ -37,6 +38,17 @@ class TraceResultJsonTest extends PropSpec with JsonMatchers {
} yield tx
).explicitGet()

def json[E, A](result: TracedResult[E, A])(implicit ev1: E => ApiError, ev2: A => Transaction): JsObject = {
val resultJson = result.resultE match {
case Right(value) => value.json()
case Left(e) => e.json
}
resultJson ++ Json.obj("trace" -> result.trace.map(_.json))
}

def loggedJson[E, A](result: TracedResult[E, A])(implicit ev1: E => ApiError, ev2: A => Transaction): JsObject =
json(result) ++ Json.obj("trace" -> result.trace.map(_.loggedJson))

property("suitable TracedResult json") {
val vars = List(
"amount" -> Right(CONST_LONG(12345)),
Expand All @@ -61,7 +73,7 @@ class TraceResultJsonTest extends PropSpec with JsonMatchers {
)

val result = TracedResult(Right(tx), trace)
result.json should matchJson("""{
json(result) should matchJson("""{
| "type": 16,
| "id": "2hoMeTHAneLExjFo2a9ei7D4co5zzr9VyT7tmBmAGmeu",
| "sender": "3MvtiFpnSA7uYKXV3myLwRK3u2NEV91iJYW",
Expand Down Expand Up @@ -131,7 +143,7 @@ class TraceResultJsonTest extends PropSpec with JsonMatchers {
| ]
|}""".stripMargin)

result.loggedJson should matchJson(
loggedJson(result) should matchJson(
"""{
| "type": 16,
| "id": "2hoMeTHAneLExjFo2a9ei7D4co5zzr9VyT7tmBmAGmeu",
Expand Down Expand Up @@ -236,7 +248,7 @@ class TraceResultJsonTest extends PropSpec with JsonMatchers {
val scriptExecutionError = ScriptExecutionError(tx, reason, isTokenScript = false)

val result = TracedResult(Left(scriptExecutionError), trace)
result.json should matchJson("""{
json(result) should matchJson("""{
| "error": 306,
| "message": "Error while executing account-script: error reason",
| "transaction": {
Expand Down