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-2528 Typed errors replacing exceptions in RIDE sources #3896

Merged
merged 13 commits into from
Nov 28, 2023
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
package com.wavesplatform.lang.v1

import java.util.concurrent.TimeUnit
import cats.Id
import com.wavesplatform.lang.Common
import com.wavesplatform.lang.directives.values.{V1, V3}
import com.wavesplatform.lang.v1.EvaluatorV2Benchmark.*
import com.wavesplatform.lang.v1.compiler.Terms.{EXPR, IF, TRUE}
import com.wavesplatform.lang.v1.compiler.TestCompiler
import com.wavesplatform.lang.v1.evaluator.EvaluatorV2
import com.wavesplatform.lang.v1.evaluator.ctx.{DisabledLogEvaluationContext, EvaluationContext}
import com.wavesplatform.lang.v1.evaluator.ctx.DisabledLogEvaluationContext
import com.wavesplatform.lang.v1.evaluator.ctx.impl.PureContext
import com.wavesplatform.lang.v1.traits.Environment
import org.openjdk.jmh.annotations.*
import org.openjdk.jmh.infra.Blackhole

import java.util.concurrent.TimeUnit
import scala.annotation.tailrec

object EvaluatorV2Benchmark {
val pureContext: CTX[Environment] = PureContext.build(V1, useNewPowPrecision = true).withEnvironment[Environment]
val pureEvalContext: EvaluationContext[Environment, Id] = pureContext.evaluationContext(Common.emptyBlockchainEnvironment())
val evaluatorV2: EvaluatorV2 = new EvaluatorV2(DisabledLogEvaluationContext(pureEvalContext), V1, true, true, false)
val pureContext = PureContext.build(V1, useNewPowPrecision = true).withEnvironment[Environment]
val pureEvalContext = pureContext.evaluationContext(Common.emptyBlockchainEnvironment())
val evaluatorV2 = new EvaluatorV2(DisabledLogEvaluationContext(pureEvalContext), V1, Int.MaxValue, true, false, true, true, true)
}

@OutputTimeUnit(TimeUnit.MILLISECONDS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,22 @@ import org.openjdk.jmh.infra.Blackhole
@Measurement(iterations = 10, time = 1)
class FractionIntBenchmark {
@Benchmark
def fraction1(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1, LogExtraInfo(), V5, true, true, false))
def fraction1(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1, LogExtraInfo(), V5, true, true, false, true))

@Benchmark
def fraction2(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2, LogExtraInfo(), V5, true, true, false))
def fraction2(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2, LogExtraInfo(), V5, true, true, false, true))

@Benchmark
def fraction3(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3, LogExtraInfo(), V5, true, true, false))
def fraction3(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3, LogExtraInfo(), V5, true, true, false, true))

@Benchmark
def fraction1Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1Round, LogExtraInfo(), V5, true, true, false))
def fraction1Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr1Round, LogExtraInfo(), V5, true, true, false, true))

@Benchmark
def fraction2Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2Round, LogExtraInfo(), V5, true, true, false))
def fraction2Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr2Round, LogExtraInfo(), V5, true, true, false, true))

@Benchmark
def fraction3Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3Round, LogExtraInfo(), V5, true, true, false))
def fraction3Round(bh: Blackhole, s: St): Unit = bh.consume(EvaluatorV2.applyCompleted(s.ctx, s.expr3Round, LogExtraInfo(), V5, true, true, false, true))
}

@State(Scope.Benchmark)
Expand Down
11 changes: 10 additions & 1 deletion benchmark/src/test/scala/com/wavesplatform/lang/v1/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,14 @@ package object v1 {
expr: EXPR,
stdLibVersion: StdLibVersion = StdLibVersion.VersionDic.all.max
): (Log[Id], Int, Either[ExecutionError, Terms.EVALUATED]) =
EvaluatorV2.applyCompleted(ctx, expr, LogExtraInfo(), stdLibVersion, newMode = true, correctFunctionCallScope = true, enableExecutionLog = false)
EvaluatorV2.applyCompleted(
ctx,
expr,
LogExtraInfo(),
stdLibVersion,
newMode = true,
correctFunctionCallScope = true,
enableExecutionLog = false,
fixedThrownError = true
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ object FastBase58 extends BaseXXEncDec {
outArray(outIndex) = longValue & 0xffffffffL
}

// called only from Try structure, see BaseXXEncDec.tryDecode(str: String)
if (base58EncMask > 0) throw new IllegalArgumentException("Output number too big (carry to the next int32)")
if ((outArray(0) & zeroMask) != 0) throw new IllegalArgumentException("Output number too big (last int32 filled too far)")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ case class CommonError(details: String, cause: Option[ValidationError] = None) e
override def toString: String = s"CommonError($message)"
override def message: String = cause.map(_.toString).getOrElse(details)
}
case class ThrownError(message: String) extends ExecutionError
case class FailOrRejectError(message: String, skipInvokeComplexity: Boolean = true) extends ExecutionError with ValidationError
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,7 @@ object BaseGlobal {
def apply(n: Int): T =
if (from + n < until) arr(from + n)
else throw new ArrayIndexOutOfBoundsException(n)
// should be never thrown due to passing Random.nextInt(arr.size) at the single point of call

def partitionInPlace(p: T => Boolean): (ArrayView[T], ArrayView[T]) = {
var upper = until - 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ object ContractEvaluator {
limit: Int,
correctFunctionCallScope: Boolean,
newMode: Boolean,
enableExecutionLog: Boolean
enableExecutionLog: Boolean,
fixedThrownError: Boolean
): Coeval[Either[(ExecutionError, Int, Log[Id]), (ScriptResult, Log[Id])]] =
Coeval
.now(buildExprFromInvocation(dApp, i, version).leftMap((_, limit, Nil)))
Expand All @@ -134,7 +135,8 @@ object ContractEvaluator {
limit,
correctFunctionCallScope,
newMode,
enableExecutionLog
enableExecutionLog,
fixedThrownError
)
case Left(error) => Coeval.now(Left(error))
}
Expand All @@ -148,10 +150,21 @@ object ContractEvaluator {
limit: Int,
correctFunctionCallScope: Boolean,
newMode: Boolean,
enableExecutionLog: Boolean
enableExecutionLog: Boolean,
fixedThrownError: Boolean
): Coeval[Either[(ExecutionError, Int, Log[Id]), (ScriptResult, Log[Id])]] =
EvaluatorV2
.applyLimitedCoeval(expr, logExtraInfo, limit, ctx, version, correctFunctionCallScope, newMode, enableExecutionLog = enableExecutionLog)
.applyLimitedCoeval(
expr,
logExtraInfo,
limit,
ctx,
version,
correctFunctionCallScope,
newMode,
enableExecutionLog = enableExecutionLog,
fixedThrownError = fixedThrownError
)
.map(_.flatMap { case (expr, unusedComplexity, log) =>
val result =
expr match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,13 @@ import com.wavesplatform.lang.v1.FunctionHeader
import com.wavesplatform.lang.v1.compiler.Terms.*
import com.wavesplatform.lang.v1.compiler.Types.CASETYPEREF
import com.wavesplatform.lang.v1.evaluator.ContextfulNativeFunction.{Extended, Simple}
import com.wavesplatform.lang.v1.evaluator.ctx.{
DisabledLogEvaluationContext,
EnabledLogEvaluationContext,
EvaluationContext,
LoggedEvaluationContext,
NativeFunction,
UserFunction
}
import com.wavesplatform.lang.v1.evaluator.ContractEvaluator.LogExtraInfo
import com.wavesplatform.lang.v1.evaluator.EvaluatorV2.logFunc
import com.wavesplatform.lang.v1.evaluator.EvaluatorV2.LogKeys.*
import com.wavesplatform.lang.v1.evaluator.EvaluatorV2.logFunc
import com.wavesplatform.lang.v1.evaluator.ctx.impl.waves.Bindings
import com.wavesplatform.lang.v1.evaluator.ctx.*
import com.wavesplatform.lang.v1.traits.Environment
import com.wavesplatform.lang.{CommonError, ExecutionError}
import com.wavesplatform.lang.{CommonError, ExecutionError, ThrownError}
import monix.eval.Coeval
import shapeless.syntax.std.tuple.*

Expand All @@ -32,10 +25,12 @@ import scala.collection.mutable.ListBuffer
class EvaluatorV2(
val ctx: LoggedEvaluationContext[Environment, Id],
val stdLibVersion: StdLibVersion,
val limit: Int,
val correctFunctionCallScope: Boolean,
val newMode: Boolean,
val enableExecutionLog: Boolean,
val checkConstructorArgsTypes: Boolean = false
val checkConstructorArgsTypes: Boolean,
val fixedThrownError: Boolean
) {
private val overheadCost: Int = if (newMode) 0 else 1

Expand Down Expand Up @@ -110,13 +105,21 @@ class EvaluatorV2(
evaluated
} -> unusedComplexity
}
case f: Simple[Environment] => Coeval((f.evaluate(ctx.ec.environment, args), limit - cost))
case f: Simple[Environment] =>
Coeval((f.evaluate(ctx.ec.environment, args), limit - cost))
}
for {
(result, unusedComplexity) <- EvaluationResult(
evaluation
.map { case (result, evaluatedComplexity) =>
result.bimap((_, evaluatedComplexity), (_, evaluatedComplexity))
result.bimap(
{
case e: ThrownError if !fixedThrownError && function.ev.isInstanceOf[Extended[Environment]] => (e, this.limit)
case e: ThrownError if !fixedThrownError => (e, 0)
case e => (e, evaluatedComplexity)
},
(_, evaluatedComplexity)
)
}
.onErrorHandleWith {
case _: SecurityException =>
Expand Down Expand Up @@ -359,7 +362,8 @@ object EvaluatorV2 {
correctFunctionCallScope: Boolean,
newMode: Boolean,
checkConstructorArgsTypes: Boolean = false,
enableExecutionLog: Boolean = false
enableExecutionLog: Boolean = false,
fixedThrownError: Boolean
): Coeval[Either[(ExecutionError, Int, Log[Id]), (EXPR, Int, Log[Id])]] = {
val log = ListBuffer[LogItem[Id]]()

Expand All @@ -370,7 +374,16 @@ object EvaluatorV2 {
}
var ref = expr.deepCopy.value
logCall(loggedCtx, logExtraInfo, ref, enableExecutionLog)
new EvaluatorV2(loggedCtx, stdLibVersion, correctFunctionCallScope, newMode, enableExecutionLog, checkConstructorArgsTypes)
new EvaluatorV2(
loggedCtx,
stdLibVersion,
limit,
correctFunctionCallScope,
newMode,
enableExecutionLog,
checkConstructorArgsTypes,
fixedThrownError
)
.root(ref, v => EvaluationResult { ref = v }, limit, Nil)
.map((ref, _))
.value
Expand All @@ -389,7 +402,8 @@ object EvaluatorV2 {
correctFunctionCallScope: Boolean,
newMode: Boolean,
handleExpr: EXPR => Either[ExecutionError, EVALUATED],
enableExecutionLog: Boolean
enableExecutionLog: Boolean,
fixedThrownError: Boolean
): (Log[Id], Int, Either[ExecutionError, EVALUATED]) =
EvaluatorV2
.applyLimitedCoeval(
Expand All @@ -400,7 +414,8 @@ object EvaluatorV2 {
stdLibVersion,
correctFunctionCallScope,
newMode,
enableExecutionLog = enableExecutionLog
enableExecutionLog = enableExecutionLog,
fixedThrownError = fixedThrownError
)
.value()
.fold(
Expand All @@ -420,7 +435,8 @@ object EvaluatorV2 {
stdLibVersion: StdLibVersion,
correctFunctionCallScope: Boolean,
newMode: Boolean,
enableExecutionLog: Boolean
enableExecutionLog: Boolean,
fixedThrownError: Boolean
): (Log[Id], Int, Either[ExecutionError, EVALUATED]) =
applyOrDefault(
ctx,
Expand All @@ -431,7 +447,8 @@ object EvaluatorV2 {
correctFunctionCallScope,
newMode,
expr => Left(s"Unexpected incomplete evaluation result $expr"),
enableExecutionLog
enableExecutionLog,
fixedThrownError
)

private def logCall(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import cats.implicits.*
import cats.{Id, Monad}
import com.wavesplatform.common.merkle.Merkle.createRoot
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.lang.{ExecutionError, CommonError}
import com.wavesplatform.lang.directives.values.{StdLibVersion, V3, *}
import com.wavesplatform.lang.directives.values.*
import com.wavesplatform.lang.v1.compiler.Terms.*
import com.wavesplatform.lang.v1.compiler.Types.*
import com.wavesplatform.lang.v1.compiler.{CompilerContext, Terms}
Expand All @@ -15,8 +14,10 @@ import com.wavesplatform.lang.v1.evaluator.FunctionIds.*
import com.wavesplatform.lang.v1.evaluator.ctx.impl.crypto.RSA.DigestAlgorithm
import com.wavesplatform.lang.v1.evaluator.ctx.{BaseFunction, EvaluationContext, NativeFunction}
import com.wavesplatform.lang.v1.{BaseGlobal, CTX}
import com.wavesplatform.lang.{CommonError, ExecutionError, ThrownError}

import scala.collection.mutable
import scala.util.Try

object CryptoContext {

Expand All @@ -30,7 +31,7 @@ object CryptoContext {
UNION.create(rsaHashAlgs(v), if (v > V3 && v < V6) Some("RsaDigestAlgs") else None)

private val rsaHashLib = {
import com.wavesplatform.lang.v1.evaluator.ctx.impl.crypto.RSA._
import com.wavesplatform.lang.v1.evaluator.ctx.impl.crypto.RSA.*
rsaTypeNames.zip(List(NONE, MD5, SHA1, SHA224, SHA256, SHA384, SHA512, SHA3224, SHA3256, SHA3384, SHA3512)).toMap
}

Expand Down Expand Up @@ -370,16 +371,16 @@ object CryptoContext {
("index", LONG)
) {
case xs @ ARR(proof) :: CONST_BYTESTR(value) :: CONST_LONG(index) :: Nil =>
val filteredProofs = proof.collect {
case bs @ CONST_BYTESTR(v) if v.size == 32 => bs
}

if (value.size == 32 && proof.length <= 16 && filteredProofs.size == proof.size) {
CONST_BYTESTR(ByteStr(createRoot(value.arr, Math.toIntExact(index), filteredProofs.reverse.map(_.bs.arr))))
val sizeCheckedProofs = proof.collect { case bs @ CONST_BYTESTR(v) if v.size == 32 => bs }
if (value.size == 32 && proof.length <= 16 && sizeCheckedProofs.size == proof.size) {
Try(createRoot(value.arr, Math.toIntExact(index), sizeCheckedProofs.reverse.map(_.bs.arr)))
.toEither
.leftMap(e => ThrownError(if (e.getMessage != null) e.getMessage else "error"))
.flatMap(r => CONST_BYTESTR(ByteStr(r)))
} else {
notImplemented[Id, EVALUATED](s"createMerkleRoot(merkleProof: ByteVector, valueBytes: ByteVector)", xs)
notImplemented[Id, EVALUATED](s"createMerkleRoot(merkleProof: ByteVector, valueBytes: ByteVector, index: Int)", xs)
}
case xs => notImplemented[Id, EVALUATED](s"createMerkleRoot(merkleProof: ByteVector, valueBytes: ByteVector)", xs)
case xs => notImplemented[Id, EVALUATED](s"createMerkleRoot(merkleProof: ByteVector, valueBytes: ByteVector, index: Int)", xs)
}

def toBase16StringF(checkLength: Boolean): BaseFunction[NoContext] = NativeFunction("toBase16String", 10, TOBASE16, STRING, ("bytes", BYTESTR)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ object PureContext {
val MaxListLengthV4 = 1000

// As an optimization, JVM might throw an ArithmeticException with empty stack trace and null message.
// The workaround below retrows an exception with the message explicitly set.
// The workaround below rethrows an exception with the message explicitly set.
lazy val divLong: BaseFunction[NoContext] =
createTryOp(DIV_OP, LONG, LONG, DIV_LONG) { (a, b) =>
try Math.floorDiv(a, b)
Expand Down
Loading
Loading