Skip to content

Commit

Permalink
NODE-2537 State changes field in result of /utils/script/evaluate (#3787
Browse files Browse the repository at this point in the history
)
  • Loading branch information
darksyd94 authored Jan 24, 2023
1 parent 7800159 commit d9f5453
Show file tree
Hide file tree
Showing 16 changed files with 1,264 additions and 1,075 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,22 @@ sealed trait ScriptResult {
def returnedValue: EVALUATED = unit
def invokes: Seq[(Address, String, Seq[EVALUATED], Seq[CaseObj], ScriptResult)] = Nil
def unusedComplexity: Int
def actions: List[CallableAction]
}
case class ScriptResultV3(ds: List[DataItem[_]], ts: List[AssetTransfer], unusedComplexity: Int) extends ScriptResult

case class ScriptResultV3(ds: List[DataItem[_]], ts: List[AssetTransfer], unusedComplexity: Int) extends ScriptResult {
override lazy val actions: List[CallableAction] = ds ++ ts
}

case class ScriptResultV4(
actions: List[CallableAction],
unusedComplexity: Int,
override val returnedValue: EVALUATED = unit
) extends ScriptResult
case class IncompleteResult(expr: EXPR, unusedComplexity: Int) extends ScriptResult

case class IncompleteResult(expr: EXPR, unusedComplexity: Int) extends ScriptResult {
override val actions: List[CallableAction] = Nil
}

object ScriptResult {
type ActionInput = (EvaluationContext[Environment, Id], ByteStr, Map[String, EVALUATED])
Expand Down
17 changes: 17 additions & 0 deletions node/src/main/resources/swagger-ui/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1852,6 +1852,23 @@ paths:
type:
type: string
value: { }
complexity:
type: integer
format: int64
stateChanges:
$ref: '#/components/schemas/StateChanges'
vars:
type: array
items:
type: object
properties:
name:
type: string
type:
type: string
value: { }
error:
type: string
'400':
description: Conflicting request structure
content:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package com.wavesplatform.api.http

import akka.NotUsed
import akka.http.scaladsl.common.EntityStreamingSupport
import akka.http.scaladsl.marshalling._
import akka.http.scaladsl.marshalling.*
import akka.http.scaladsl.model.*
import akka.http.scaladsl.model.MediaTypes.{`application/json`, `text/plain`}
import akka.http.scaladsl.model._
import akka.http.scaladsl.unmarshalling.{FromEntityUnmarshaller, PredefinedFromEntityUnmarshallers, Unmarshaller}
import akka.http.scaladsl.util.FastFuture
import akka.stream.scaladsl.{Flow, Source}
import akka.util.ByteString
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.transaction.smart.script.trace.TracedResult
import play.api.libs.json._
import play.api.libs.json.*

import scala.util.control.Exception.nonFatalCatch
import scala.util.control.NoStackTrace
Expand Down Expand Up @@ -87,12 +86,6 @@ trait ApiMarshallers extends JsonFormats {
// preserve support for using plain strings as request entities
implicit val stringMarshaller: ToEntityMarshaller[String] = PredefinedToEntityMarshallers.stringMarshaller(`text/plain`)

def jsonStream(prefix: String, delimiter: String, suffix: String): EntityStreamingSupport =
EntityStreamingSupport
.json()
.withContentType(ContentType(CustomJson.jsonWithNumbersAsStrings))
.withFramingRenderer(Flow[ByteString].intersperse(ByteString(prefix), ByteString(delimiter), ByteString(suffix)))

private def selectMarshallingForContentType[T](marshallings: Seq[Marshalling[T]], contentType: ContentType): Option[() => T] = {
contentType match {
case _: ContentType.Binary | _: ContentType.WithFixedCharset | _: ContentType.WithMissingCharset =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import com.wavesplatform.api.http.*
import com.wavesplatform.api.http.ApiError.{CustomValidationError, InvalidIds}
import com.wavesplatform.api.http.assets.AssetsApiRoute
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.state.Blockchain
import com.wavesplatform.transaction.Asset.IssuedAsset
import com.wavesplatform.transaction.TxValidationError.GenericError
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,10 +276,11 @@ case class UtilsApiRoute(
val evaluated = for {
expr <- exprE
limit = settings.evaluateScriptComplexityLimit
(result, complexity, log) <- UtilsEvaluator.executeExpression(blockchain, script, address, pk, limit)(expr)
(result, complexity, log, scriptResult) <- UtilsEvaluator.executeExpression(blockchain, script, address, pk, limit)(expr)
} yield Json.obj(
"result" -> ScriptValuesJson.serializeValue(result),
"complexity" -> complexity
"result" -> ScriptValuesJson.serializeValue(result),
"complexity" -> complexity,
"stateChanges" -> scriptResult
) ++ (if (trace) Json.obj(TraceStep.logJson(log)) else Json.obj())
evaluated.leftMap {
case e: InvokeRejectError => Json.obj("error" -> ApiError.ScriptExecutionError.Id, "message" -> e.toStringWithLog(maxTxErrorLogSize))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.wavesplatform.api.http.utils

import cats.Id
import cats.implicits.{catsSyntaxApply, catsSyntaxSemigroup}
import cats.syntax.either.*
import com.wavesplatform.account.{Address, AddressOrAlias, AddressScheme, PublicKey}
import com.wavesplatform.common.state.ByteStr
Expand All @@ -13,13 +14,14 @@ import com.wavesplatform.lang.script.Script
import com.wavesplatform.lang.v1.compiler.Terms.{EVALUATED, EXPR}
import com.wavesplatform.lang.v1.compiler.{ContractScriptCompactor, ExpressionCompiler, Terms}
import com.wavesplatform.lang.v1.evaluator.ContractEvaluator.{Invocation, LogExtraInfo}
import com.wavesplatform.lang.v1.evaluator.{ContractEvaluator, EvaluatorV2, Log}
import com.wavesplatform.lang.v1.evaluator.{ContractEvaluator, EvaluatorV2, Log, ScriptResult}
import com.wavesplatform.lang.v1.traits.Environment.Tthis
import com.wavesplatform.lang.v1.traits.domain.Recipient
import com.wavesplatform.lang.v1.{ContractLimits, FunctionHeader}
import com.wavesplatform.lang.{ValidationError, utils}
import com.wavesplatform.state.diffs.invoke.InvokeScriptTransactionLike
import com.wavesplatform.state.{Blockchain, Diff}
import com.wavesplatform.state.diffs.invoke.InvokeDiffsCommon.{actionsToScriptResult, checkActions}
import com.wavesplatform.state.diffs.invoke.{InvokeScriptTransactionLike, StructuredCallableActions}
import com.wavesplatform.state.{Blockchain, Diff, InvokeScriptResult}
import com.wavesplatform.transaction.Asset.IssuedAsset
import com.wavesplatform.transaction.TransactionType.TransactionType
import com.wavesplatform.transaction.TxValidationError.{GenericError, InvokeRejectError}
Expand All @@ -42,7 +44,7 @@ object UtilsEvaluator {

def executeExpression(blockchain: Blockchain, script: Script, address: Address, pk: PublicKey, limit: Int)(
expr: EXPR
): Either[ValidationError, (EVALUATED, Int, Log[Id])] =
): Either[ValidationError, (EVALUATED, Int, Log[Id], InvokeScriptResult)] =
for {
ds <- DirectiveSet(script.stdLibVersion, Account, DAppType).leftMap(GenericError(_))
invoke = new InvokeScriptTransactionLike {
Expand Down Expand Up @@ -99,9 +101,21 @@ object UtilsEvaluator {
)
.value()
.leftMap { case (err, _, log) => InvokeRejectError(err.message, log) }
result <- limitedResult match {
(evaluated, usedComplexity, log) <- limitedResult match {
case (eval: EVALUATED, unusedComplexity, log) => Right((eval, limit - unusedComplexity, log))
case (_: EXPR, _, log) => Left(InvokeRejectError(s"Calculation complexity limit exceeded", log))
}
} yield result
rootScriptResult <- ScriptResult
.fromObj(ctx, ByteStr.empty, evaluated, ds.stdLibVersion, 0)
.bimap(
_ => Right(InvokeScriptResult.empty),
{ r =>
val actions = StructuredCallableActions(r.actions, blockchain)
val check = checkActions(actions, ds.stdLibVersion, address, usedComplexity, invoke, limitedExecution = false, limit, log)
(check *> actionsToScriptResult(actions, usedComplexity, invoke, log)).resultE
}
)
.merge
scriptResult = environment.currentDiff.scriptResults.values.fold(InvokeScriptResult.empty)(_ |+| _) |+| rootScriptResult
} yield (evaluated, usedComplexity, log, scriptResult)
}
Original file line number Diff line number Diff line change
Expand Up @@ -535,10 +535,13 @@ abstract class LevelDBWriter private[database] (
rw.put(Keys.wavesAmount(height), wavesAmount(height - 1) + lastReward)
}

for (case (asset, sp: SponsorshipValue) <- sponsorship) {
rw.put(Keys.sponsorship(asset)(height), sp)
expiredKeys ++= updateHistory(rw, Keys.sponsorshipHistory(asset), threshold, Keys.sponsorship(asset))
}
for (case sp <- sponsorship)
sp match {
case (asset, value: SponsorshipValue) =>
rw.put(Keys.sponsorship(asset)(height), value)
expiredKeys ++= updateHistory(rw, Keys.sponsorshipHistory(asset), threshold, Keys.sponsorship(asset))
case _ =>
}

rw.put(Keys.issuedAssets(height), issuedAssets.keySet.toSeq)
rw.put(Keys.updatedAssets(height), updatedAssets.keySet.toSeq)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ object InvokeDiffsCommon {
}

def processActions(
actions: List[CallableAction],
actions: StructuredCallableActions,
version: StdLibVersion,
dAppAddress: Address,
dAppPublicKey: PublicKey,
Expand All @@ -171,73 +171,26 @@ object InvokeDiffsCommon {
otherIssues: Seq[Issue],
log: Log[Id]
): TracedResult[ValidationError, Diff] = {
val complexityLimit =
if (limitedExecution) ContractLimits.FailFreeInvokeComplexity - storingComplexity
else Int.MaxValue

val actionsByType = actions.groupBy(a => if (classOf[DataOp].isAssignableFrom(a.getClass)) classOf[DataOp] else a.getClass).withDefaultValue(Nil)
val transferList = actionsByType(classOf[AssetTransfer]).asInstanceOf[List[AssetTransfer]]
val issueList = actionsByType(classOf[Issue]).asInstanceOf[List[Issue]]
val reissueList = actionsByType(classOf[Reissue]).asInstanceOf[List[Reissue]]
val burnList = actionsByType(classOf[Burn]).asInstanceOf[List[Burn]]
val sponsorFeeList = actionsByType(classOf[SponsorFee]).asInstanceOf[List[SponsorFee]]
val leaseList = actionsByType(classOf[Lease]).asInstanceOf[List[Lease]]
val leaseCancelList = actionsByType(classOf[LeaseCancel]).asInstanceOf[List[LeaseCancel]]
val dataEntries = actionsByType(classOf[DataOp]).asInstanceOf[List[DataOp]].map(dataItemToEntry)

val verifierCount = if (blockchain.hasPaidVerifier(tx.sender.toAddress)) 1 else 0
val additionalScriptsCount = actions.complexities.size + verifierCount + tx.paymentAssets.count(blockchain.hasAssetScript)
for {
_ <- TracedResult(checkDataEntries(blockchain, tx, dataEntries, version)).leftMap(
FailedTransactionError.dAppExecution(_, storingComplexity, log)
)
_ <- TracedResult(checkLeaseCancels(leaseCancelList)).leftMap(FailedTransactionError.dAppExecution(_, storingComplexity, log))
_ <- TracedResult(
checkScriptActionsAmount(version, actions, transferList, leaseList, leaseCancelList, dataEntries)
.leftMap(FailedTransactionError.dAppExecution(_, storingComplexity, log))
)

_ <- TracedResult(checkSelfPayments(dAppAddress, blockchain, tx, version, transferList))
.leftMap(FailedTransactionError.dAppExecution(_, storingComplexity, log))
_ <- TracedResult(
Either.cond(transferList.map(_.amount).forall(_ >= 0), (), FailedTransactionError.dAppExecution("Negative amount", storingComplexity, log))
)
_ <- TracedResult(checkOverflow(transferList.map(_.amount))).leftMap(FailedTransactionError.dAppExecution(_, storingComplexity, log))

actionAssets = transferList.flatMap(_.assetId).map(IssuedAsset(_)) ++
reissueList.map(r => IssuedAsset(r.assetId)) ++
burnList.map(b => IssuedAsset(b.assetId)) ++
sponsorFeeList.map(sf => IssuedAsset(sf.assetId))

actionComplexities = actionAssets.flatMap(blockchain.assetScript(_).map(_.complexity))
verifierCount = if (blockchain.hasPaidVerifier(tx.sender.toAddress)) 1 else 0
additionalScriptsCount = actionComplexities.size + verifierCount + tx.paymentAssets.count(blockchain.hasAssetScript)

_ <- checkActions(actions, version, dAppAddress, storingComplexity, tx, limitedExecution, totalComplexityLimit, log)
feeDiff <-
if (isSyncCall)
TracedResult.wrapValue(Map[Address, Portfolio]())
else {
val feeActionsCount = if (blockchain.isFeatureActivated(SynchronousCalls)) verifierCount else additionalScriptsCount
val stepLimit = ContractLimits.MaxComplexityByVersion(version)

calcAndCheckFee(
FailedTransactionError.feeForActions(_, _, log),
tx.root,
blockchain,
stepLimit,
storingComplexity.min(stepLimit), // complexity increased by sync calls should not require fee for additional steps
issueList ++ otherIssues,
actions.issueList ++ otherIssues,
feeActionsCount
).map(_._2)

}

_ <- TracedResult(
Either.cond(
actionComplexities.sum + storingComplexity <= totalComplexityLimit || limitedExecution, // limited execution has own restriction "complexityLimit"
(),
FailedTransactionError.feeForActions(s"Invoke complexity limit = $totalComplexityLimit is exceeded", storingComplexity, log)
)
)

paymentsAndFeeDiff <-
if (isSyncCall) {
TracedResult.wrapValue(Diff.empty)
Expand All @@ -246,43 +199,90 @@ object InvokeDiffsCommon {
} else {
TracedResult.wrapValue(Diff(portfolios = txFeeDiff(blockchain, tx.root).explicitGet()._2))
}

resultTransfers <- transferList.traverse { transfer =>
resolveAddress(transfer.address, blockchain)
.map(InvokeScriptResult.Payment(_, Asset.fromCompatId(transfer.assetId), transfer.amount))
.leftMap {
case f: FailedTransactionError => f.addComplexity(storingComplexity).withLog(log)
case e => e
}
}

compositeDiff <- foldActions(blockchain, blockTime, tx, dAppAddress, dAppPublicKey)(actions, paymentsAndFeeDiff, complexityLimit)
complexityLimit =
if (limitedExecution) ContractLimits.FailFreeInvokeComplexity - storingComplexity
else Int.MaxValue
compositeDiff <- foldActions(blockchain, blockTime, tx, dAppAddress, dAppPublicKey)(actions.list, paymentsAndFeeDiff, complexityLimit)
.leftMap {
case failed: FailedTransactionError => failed.addComplexity(storingComplexity).withLog(log)
case other => other
}

isr = InvokeScriptResult(
dataEntries,
resultTransfers,
issueList,
reissueList,
burnList,
sponsorFeeList,
leaseList.map { case l @ Lease(recipient, amount, nonce) =>
val id = Lease.calculateId(l, tx.txId)
InvokeScriptResult.Lease(AddressOrAlias.fromRide(recipient).explicitGet(), amount, nonce, id)
},
leaseCancelList
)

isr <- actionsToScriptResult(actions, storingComplexity, tx, log)
resultDiff = compositeDiff
.withScriptRuns(if (isSyncCall) 0 else additionalScriptsCount + 1)
.withScriptResults(Map(tx.txId -> isr))
.withScriptsComplexity(storingComplexity + compositeDiff.scriptsComplexity)
} yield resultDiff
}

def checkActions(
actions: StructuredCallableActions,
version: StdLibVersion,
dAppAddress: Address,
storingComplexity: Int,
tx: InvokeScriptLike,
limitedExecution: Boolean,
totalComplexityLimit: Int,
log: Log[Id]
): TracedResult[ValidationError, Unit] = {
import actions.*
for {
_ <- TracedResult(checkDataEntries(blockchain, tx, dataEntries, version)).leftMap(
FailedTransactionError.dAppExecution(_, storingComplexity, log)
)
_ <- TracedResult(checkLeaseCancels(leaseCancelList)).leftMap(FailedTransactionError.dAppExecution(_, storingComplexity, log))
_ <- TracedResult(
checkScriptActionsAmount(version, actions.list, transferList, leaseList, leaseCancelList, dataEntries)
.leftMap(FailedTransactionError.dAppExecution(_, storingComplexity, log))
)
_ <- TracedResult(checkSelfPayments(dAppAddress, blockchain, tx, version, transferList))
.leftMap(FailedTransactionError.dAppExecution(_, storingComplexity, log))
_ <- TracedResult(
Either.cond(transferList.map(_.amount).forall(_ >= 0), (), FailedTransactionError.dAppExecution("Negative amount", storingComplexity, log))
)
_ <- TracedResult(checkOverflow(transferList.map(_.amount))).leftMap(FailedTransactionError.dAppExecution(_, storingComplexity, log))
_ <- TracedResult(
Either.cond(
actions.complexities.sum + storingComplexity <= totalComplexityLimit || limitedExecution, // limited execution has own restriction "complexityLimit"
(),
FailedTransactionError.feeForActions(s"Invoke complexity limit = $totalComplexityLimit is exceeded", storingComplexity, log)
)
)
} yield ()
}

def actionsToScriptResult(
actions: StructuredCallableActions,
storingComplexity: Int,
tx: InvokeScriptLike,
log: Log[Id]
): TracedResult[ValidationError, InvokeScriptResult] = {
import actions.*
for {
resultTransfers <- transferList.traverse { transfer =>
resolveAddress(transfer.address, blockchain)
.map(InvokeScriptResult.Payment(_, Asset.fromCompatId(transfer.assetId), transfer.amount))
.leftMap {
case f: FailedTransactionError => f.addComplexity(storingComplexity).withLog(log)
case e => e
}
}
leaseListWithIds <- leaseList.traverse { case l @ Lease(recipient, amount, nonce) =>
val id = Lease.calculateId(l, tx.txId)
AddressOrAlias.fromRide(recipient).map(r => InvokeScriptResult.Lease(r, amount, nonce, id))
}
} yield InvokeScriptResult(
dataEntries,
resultTransfers,
issueList,
reissueList,
burnList,
sponsorFeeList,
leaseListWithIds,
leaseCancelList
)
}

def paymentsPart(tx: InvokeScriptLike, dAppAddress: Address, feePart: Map[Address, Portfolio]): Either[GenericError, Diff] =
tx.payments
.traverse { case InvokeScriptTransaction.Payment(amt, assetId) =>
Expand Down Expand Up @@ -460,7 +460,7 @@ object InvokeDiffsCommon {
val AssetTransfer(addressRepr, recipient, amount, asset) = transfer

for {
address <- TracedResult(Address.fromBytes(addressRepr.bytes.arr))
address <- resolveAddress(addressRepr, blockchain)
diff <- Asset.fromCompatId(asset) match {
case Waves =>
TracedResult(Diff.combine(Map(address -> Portfolio(amount)), Map(dAppAddress -> Portfolio(-amount))).map(p => Diff(portfolios = p)))
Expand Down
Loading

0 comments on commit d9f5453

Please sign in to comment.