Skip to content

Commit

Permalink
NODE-2528 Typed errors replacing exceptions in RIDE sources (#3896)
Browse files Browse the repository at this point in the history
  • Loading branch information
xrtm000 authored Nov 28, 2023
1 parent aa2cbf6 commit bb322af
Show file tree
Hide file tree
Showing 29 changed files with 475 additions and 165 deletions.
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

0 comments on commit bb322af

Please sign in to comment.