diff --git a/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala b/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala index f102ceeb9a..7e93858d33 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/IRContext.scala @@ -1,19 +1,23 @@ package sigmastate.eval -import java.lang.{Math => JMath} import sigmastate.SType -import sigmastate.Values.{Value, SValue} +import sigmastate.Values.{SValue, Value} import sigmastate.interpreter.Interpreter.ScriptEnv import sigmastate.lang.TransformingSigmaBuilder import sigmastate.lang.exceptions.CostLimitException +import sigmastate.utils.Helpers import sigmastate.utxo.CostTable +import java.util.concurrent.locks.ReentrantLock import scala.util.Try trait IRContext extends Evaluation with TreeBuilding { override val builder = TransformingSigmaBuilder + /** Can be used to synchronize access to this IR object from multiple threads. */ + val lock = new ReentrantLock() + /** Pass configuration which is used to turn-off constant propagation. * @see `beginPass(noCostPropagationPass)` */ lazy val noConstPropagationPass = new DefaultPass( @@ -125,9 +129,12 @@ trait IRContext extends Evaluation with TreeBuilding { */ def checkCostWithContext(ctx: SContext, costF: Ref[((Context, (Int, Size[Context]))) => Int], maxCost: Long, initCost: Long): Try[Int] = Try { - val costFun = compile[(SContext, (Int, SSize[SContext])), Int, (Context, (Int, Size[Context])), Int]( - getDataEnv, costF, Some(maxCost)) - val (estimatedCost, accCost) = costFun((ctx, (0, Sized.sizeOf(ctx)))) + + val (estimatedCost, accCost) = Helpers.withReentrantLock(lock) { // protect mutable access to this IR + val costFun = compile[(SContext, (Int, SSize[SContext])), Int, (Context, (Int, Size[Context])), Int]( + getDataEnv, costF, Some(maxCost)) + costFun((ctx, (0, Sized.sizeOf(ctx)))) + } if (debugModeSanityChecks) { if (estimatedCost != accCost) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala index 4efa94ff54..586aa2433f 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala @@ -2,17 +2,17 @@ package sigmastate.interpreter import java.util.concurrent.ExecutionException import java.util.concurrent.atomic.AtomicInteger - -import com.google.common.cache.{CacheBuilder, RemovalNotification, RemovalListener, LoadingCache, CacheLoader, CacheStats} +import com.google.common.cache.{CacheBuilder, CacheLoader, CacheStats, LoadingCache, RemovalListener, RemovalNotification} import org.ergoplatform.settings.ErgoAlgos import org.ergoplatform.validation.SigmaValidationSettings -import org.ergoplatform.validation.ValidationRules.{CheckCostFunc, CheckCalcFunc, trySoftForkable} +import org.ergoplatform.validation.ValidationRules.{CheckCalcFunc, CheckCostFunc, trySoftForkable} import scalan.{AVHashMap, Nullable} import sigmastate.Values import sigmastate.Values.ErgoTree -import sigmastate.eval.{RuntimeIRContext, IRContext} +import sigmastate.eval.{IRContext, RuntimeIRContext} import sigmastate.interpreter.Interpreter.{ReductionResult, WhenSoftForkReductionResult} import sigmastate.serialization.ErgoTreeSerializer +import sigmastate.utils.Helpers import sigmastate.utils.Helpers._ import spire.syntax.all.cfor @@ -84,9 +84,11 @@ case class PrecompiledScriptReducer(scriptBytes: Seq[Byte])(implicit val IR: IRC val estimatedCost = IR.checkCostWithContext(costingCtx, costF, maxCost, initCost).getOrThrow // check calc - val calcF = costingRes.calcF val calcCtx = context.toSigmaContext(isCost = false) - val res = Interpreter.calcResult(IR)(calcCtx, calcF) + val res = Helpers.withReentrantLock(IR.lock) { // protecting mutable access to IR instance + val calcF = costingRes.calcF + Interpreter.calcResult(IR)(calcCtx, calcF) + } ReductionResult(SigmaDsl.toSigmaBoolean(res), estimatedCost) } } diff --git a/sigmastate/src/main/scala/sigmastate/utils/Helpers.scala b/sigmastate/src/main/scala/sigmastate/utils/Helpers.scala index 4417b4412c..c548de6541 100644 --- a/sigmastate/src/main/scala/sigmastate/utils/Helpers.scala +++ b/sigmastate/src/main/scala/sigmastate/utils/Helpers.scala @@ -1,7 +1,6 @@ package sigmastate.utils import java.util - import io.circe.Decoder import org.ergoplatform.settings.ErgoAlgos import sigmastate.eval.{Colls, SigmaDsl} @@ -9,8 +8,9 @@ import sigmastate.interpreter.CryptoConstants.EcPointType import special.collection.Coll import special.sigma.GroupElement +import java.util.concurrent.locks.{Lock, ReentrantLock} import scala.reflect.ClassTag -import scala.util.{Failure, Try, Either, Success, Right} +import scala.util.{Either, Failure, Right, Success, Try} object Helpers { def xor(ba1: Array[Byte], ba2: Array[Byte]): Array[Byte] = ba1.zip(ba2).map(t => (t._1 ^ t._2).toByte) @@ -158,6 +158,27 @@ object Helpers { val bytes = ErgoAlgos.decodeUnsafe(base16String) Colls.fromArray(bytes) } + + /** + * Executes the given block with a reentrant mutual exclusion Lock with the same basic + * behavior and semantics as the implicit monitor lock accessed using synchronized + * methods and statements in Java. + * + * Note, using this method has an advantage of having this method in a stack trace in case of + * an exception in the block. + * @param l lock object which should be acquired by the current thread before block can start executing + * @param block block of code which will be executed retaining the lock + * @return the value produced by the block + */ + def withReentrantLock[A](l: Lock)(block: => A): A = { + l.lock() + val res = try + block + finally { + l.unlock() + } + res + } } object Overloading {