From 69258bc9b44c7e5e17049ec3e3b9b0c9ac206cad Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 23 Feb 2021 18:44:08 +0300 Subject: [PATCH 01/26] remove javacOptions from build.sbt --- build.sbt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/build.sbt b/build.sbt index e6adf65cdf..29e0c208e8 100644 --- a/build.sbt +++ b/build.sbt @@ -9,11 +9,6 @@ name := "sigma-state" lazy val scala212 = "2.12.10" lazy val scala211 = "2.11.12" -javacOptions ++= - "-source" :: "1.7" :: - "-target" :: "1.7" :: - Nil - lazy val allConfigDependency = "compile->compile;test->test" lazy val commonSettings = Seq( From 0e8d56bc1b37389f7e4101945fe3ea690c9cf8bf Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 24 Feb 2021 00:00:21 +0300 Subject: [PATCH 02/26] optimization in ErgoLikeTracation serializer --- .../org/ergoplatform/ErgoBoxCandidate.scala | 29 ++++++++++-------- .../ergoplatform/ErgoLikeTransaction.scala | 30 +++++++++---------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala index 0b11792137..d3ddc95561 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala @@ -189,19 +189,22 @@ object ErgoBoxCandidate { val nTokens = r.getUByte() // READ val tokenIds = new Array[Digest32](nTokens) val tokenAmounts = new Array[Long](nTokens) - val tokenIdSize = TokenId.size - cfor(0)(_ < nTokens, _ + 1) { i => - val tokenId = if (digestsInTx.isDefined) { - val digestIndex = r.getUInt().toInt // READ - val digests = digestsInTx.get - if (!digests.isDefinedAt(digestIndex)) sys.error(s"failed to find token id with index $digestIndex") - digests(digestIndex) - } else { - r.getBytes(tokenIdSize) // READ - } - val amount = r.getULong() // READ - tokenIds(i) = tokenId.asInstanceOf[Digest32] - tokenAmounts(i) = amount + digestsInTx match { + case Some(digests) => + cfor(0)(_ < nTokens, _ + 1) { i => + val digestIndex = r.getUInt().toInt // READ + if (!digests.isDefinedAt(digestIndex)) + sys.error(s"failed to find token id with index $digestIndex") + val amount = r.getULong() // READ + tokenIds(i) = digests(digestIndex).asInstanceOf[Digest32] + tokenAmounts(i) = amount + } + case None => + val tokenIdSize = TokenId.size // optimization: access the value once + cfor(0)(_ < nTokens, _ + 1) { i => + tokenIds(i) = r.getBytes(tokenIdSize).asInstanceOf[Digest32] // READ + tokenAmounts(i) = r.getULong() // READ + } } val tokens = Colls.pairCollFromArrays(tokenIds, tokenAmounts) diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala index c5d850d457..06c52904e5 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala @@ -12,8 +12,6 @@ import sigmastate.serialization.SigmaSerializer import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} import special.collection.ExtensionMethods._ import spire.syntax.all.cfor - -import scala.collection.mutable import scala.util.Try trait ErgoBoxReader { @@ -146,31 +144,31 @@ object ErgoLikeTransactionSerializer extends SigmaSerializer[ErgoLikeTransaction override def parse(r: SigmaByteReader): ErgoLikeTransaction = { // parse transaction inputs val inputsCount = r.getUShort() - val inputsBuilder = mutable.ArrayBuilder.make[Input]() - for (_ <- 0 until inputsCount) { - inputsBuilder += Input.serializer.parse(r) + val inputs = new Array[Input](inputsCount) + cfor(0)(_ < inputsCount, _ + 1) { i => + inputs(i) = Input.serializer.parse(r) } // parse transaction data inputs val dataInputsCount = r.getUShort() - val dataInputsBuilder = mutable.ArrayBuilder.make[DataInput]() - for (_ <- 0 until dataInputsCount) { - dataInputsBuilder += DataInput(ADKey @@ r.getBytes(ErgoBox.BoxId.size)) + val dataInputs = new Array[DataInput](dataInputsCount) + cfor(0)(_ < dataInputsCount, _ + 1) { i => + dataInputs(i) = DataInput(ADKey @@ r.getBytes(ErgoBox.BoxId.size)) } // parse distinct ids of tokens in transaction outputs val tokensCount = r.getUInt().toInt - val tokensBuilder = mutable.ArrayBuilder.make[TokenId]() - for (_ <- 0 until tokensCount) { - tokensBuilder += Digest32 @@ r.getBytes(TokenId.size) + val tokensArr = new Array[TokenId](tokensCount) + cfor(0)(_ < tokensCount, _ + 1) { i => + tokensArr(i) = Digest32 @@ r.getBytes(TokenId.size) } - val tokens = tokensBuilder.result().toColl + val tokens = tokensArr.toColl // parse outputs val outsCount = r.getUShort() - val outputCandidatesBuilder = mutable.ArrayBuilder.make[ErgoBoxCandidate]() - for (_ <- 0 until outsCount) { - outputCandidatesBuilder += ErgoBoxCandidate.serializer.parseBodyWithIndexedDigests(Some(tokens), r) + val outputCandidates = new Array[ErgoBoxCandidate](outsCount) + cfor(0)(_ < outsCount, _ + 1) { i => + outputCandidates(i) = ErgoBoxCandidate.serializer.parseBodyWithIndexedDigests(Some(tokens), r) } - new ErgoLikeTransaction(inputsBuilder.result(), dataInputsBuilder.result(), outputCandidatesBuilder.result()) + new ErgoLikeTransaction(inputs, dataInputs, outputCandidates) } } From 4d5c3e430760491f72559c6a5e3badd9f0b07700 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 24 Feb 2021 22:03:33 +0300 Subject: [PATCH 03/26] i694-box-opt: avoid allocation in ErgoBox deserialization --- .../scala/org/ergoplatform/ErgoBoxCandidate.scala | 14 ++++++++------ .../org/ergoplatform/ErgoLikeTransaction.scala | 11 ++++++----- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala index d3ddc95561..679d9f6670 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala @@ -4,6 +4,7 @@ import java.util import org.ergoplatform.ErgoBox._ import org.ergoplatform.settings.ErgoAlgos +import scalan.Nullable import scorex.crypto.hash.Digest32 import scorex.util.{bytesToId, ModifierId} import sigmastate.Values._ @@ -180,7 +181,7 @@ object ErgoBoxCandidate { /** Helper method to parse [[ErgoBoxCandidate]] previously serialized by * [[serializeBodyWithIndexedDigests()]]. */ - def parseBodyWithIndexedDigests(digestsInTx: Option[Coll[TokenId]], r: SigmaByteReader): ErgoBoxCandidate = { + def parseBodyWithIndexedDigests(digestsInTx: Nullable[Array[TokenId]], r: SigmaByteReader): ErgoBoxCandidate = { val previousPositionLimit = r.positionLimit r.positionLimit = r.position + ErgoBox.MaxBoxSize val value = r.getULong() // READ @@ -190,16 +191,17 @@ object ErgoBoxCandidate { val tokenIds = new Array[Digest32](nTokens) val tokenAmounts = new Array[Long](nTokens) digestsInTx match { - case Some(digests) => + case Nullable(digests) => + val nDigests = digests.length cfor(0)(_ < nTokens, _ + 1) { i => val digestIndex = r.getUInt().toInt // READ - if (!digests.isDefinedAt(digestIndex)) + if (digestIndex < 0 || digestIndex >= nDigests) sys.error(s"failed to find token id with index $digestIndex") val amount = r.getULong() // READ - tokenIds(i) = digests(digestIndex).asInstanceOf[Digest32] + tokenIds(i) = digests(digestIndex) tokenAmounts(i) = amount } - case None => + case _ => val tokenIdSize = TokenId.size // optimization: access the value once cfor(0)(_ < nTokens, _ + 1) { i => tokenIds(i) = r.getBytes(tokenIdSize).asInstanceOf[Digest32] // READ @@ -222,7 +224,7 @@ object ErgoBoxCandidate { } override def parse(r: SigmaByteReader): ErgoBoxCandidate = { - parseBodyWithIndexedDigests(None, r) + parseBodyWithIndexedDigests(Nullable.None, r) } } diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala index 06c52904e5..9ea01394c0 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala @@ -1,8 +1,9 @@ package org.ergoplatform import org.ergoplatform.ErgoBox.TokenId +import scalan.Nullable import scorex.crypto.authds.ADKey -import scorex.crypto.hash.{Blake2b256, Digest32} +import scorex.crypto.hash.{Digest32, Blake2b256} import scorex.util._ import sigmastate.SType._ import sigmastate.eval.Extensions._ @@ -12,6 +13,7 @@ import sigmastate.serialization.SigmaSerializer import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} import special.collection.ExtensionMethods._ import spire.syntax.all.cfor + import scala.util.Try trait ErgoBoxReader { @@ -156,17 +158,16 @@ object ErgoLikeTransactionSerializer extends SigmaSerializer[ErgoLikeTransaction } // parse distinct ids of tokens in transaction outputs val tokensCount = r.getUInt().toInt - val tokensArr = new Array[TokenId](tokensCount) + val tokens = new Array[TokenId](tokensCount) cfor(0)(_ < tokensCount, _ + 1) { i => - tokensArr(i) = Digest32 @@ r.getBytes(TokenId.size) + tokens(i) = Digest32 @@ r.getBytes(TokenId.size) } - val tokens = tokensArr.toColl // parse outputs val outsCount = r.getUShort() val outputCandidates = new Array[ErgoBoxCandidate](outsCount) cfor(0)(_ < outsCount, _ + 1) { i => - outputCandidates(i) = ErgoBoxCandidate.serializer.parseBodyWithIndexedDigests(Some(tokens), r) + outputCandidates(i) = ErgoBoxCandidate.serializer.parseBodyWithIndexedDigests(Nullable(tokens), r) } new ErgoLikeTransaction(inputs, dataInputs, outputCandidates) } From 37f8fba6c67a93e9bd792d430f3ad1b80f8ce5f5 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 26 Feb 2021 15:48:51 +0300 Subject: [PATCH 04/26] i694-box-opt: added SigmaBoolean.estimateCost --- .../src/main/scala/sigmastate/Values.scala | 34 +++++++++++++++++++ .../sigmastate/eval/RuntimeCosting.scala | 11 +----- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index 07e8e3f4d0..75e88eef2a 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -561,6 +561,40 @@ object Values { } object SigmaBoolean { + def estimateCost(sigmaTree: SigmaBoolean): Int = sigmaTree match { + case _: ProveDlog => CostTable.proveDlogEvalCost + case _: ProveDHTuple => CostTable.proveDHTupleEvalCost + case and: CAND => + val children = and.children.toArray + val nChildren = children.length + var sum = 0 + cfor(0)(_ < nChildren, _ + 1) { i => + val c = estimateCost(children(i)) + sum = Math.addExact(sum, c) + } + sum + case or: COR => + val children = or.children.toArray + val nChildren = children.length + var sum = 0 + cfor(0)(_ < nChildren, _ + 1) { i => + val c = estimateCost(children(i)) + sum = Math.addExact(sum, c) + } + sum + case th: CTHRESHOLD => + val children = th.children.toArray + val nChildren = children.length + var sum = 0 + cfor(0)(_ < nChildren, _ + 1) { i => + val c = estimateCost(children(i)) + sum = Math.addExact(sum, c) + } + sum + case _ => + CostTable.MinimalCost + } + /** @hotspot don't beautify this code */ object serializer extends SigmaSerializer[SigmaBoolean, SigmaBoolean] { val dhtSerializer = ProveDHTupleSerializer(ProveDHTuple.apply) diff --git a/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala b/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala index ffbb87e8fa..86ebf9eb89 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala @@ -165,15 +165,6 @@ trait RuntimeCosting extends CostingRules { IR: IRContext => val _costOfProveDlogEval = CostOf("ProveDlogEval", SFunc(SUnit, SSigmaProp)) val _costOfProveDHTuple = CostOf("ProveDHTuple", SFunc(SUnit, SSigmaProp)) - def costOfSigmaTree(sigmaTree: SigmaBoolean): Int = sigmaTree match { - case _: ProveDlog => _costOfProveDlogEval.eval - case _: ProveDHTuple => _costOfProveDHTuple.eval - case CAND(children) => Colls.fromArray(children.toArray).map(costOfSigmaTree(_)).sum(intPlusMonoidValue) - case COR(children) => Colls.fromArray(children.toArray).map(costOfSigmaTree(_)).sum(intPlusMonoidValue) - case CTHRESHOLD(_, children) => Colls.fromArray(children.toArray).map(costOfSigmaTree(_)).sum(intPlusMonoidValue) - case _ => CostTable.MinimalCost - } - def perKbCostOf(opName: String, opType: SFunc, dataSize: Ref[Long]): Ref[Int] = { val opNamePerKb = s"${opName}_per_kb" PerKbCostOf(OperationId(opNamePerKb, opType), dataSize) @@ -1164,7 +1155,7 @@ trait RuntimeCosting extends CostingRules { IR: IRContext => case p: SSigmaProp => assert(tpe == SSigmaProp) val resV = liftConst(p) - RCCostedPrim(resV, opCost(resV, Nil, costOfSigmaTree(p)), SizeSigmaProposition) + RCCostedPrim(resV, opCost(resV, Nil, SigmaBoolean.estimateCost(p)), SizeSigmaProposition) case bi: SBigInt => assert(tpe == SBigInt) val resV = liftConst(bi) From a7949fa45043ad8e15da6ce5065ef02fc167a569 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 26 Feb 2021 16:21:22 +0300 Subject: [PATCH 05/26] i694-box-opt: fast fullReduction for SigmaPropConstant --- .../src/main/scala/sigmastate/Values.scala | 9 +++++- .../sigmastate/interpreter/Interpreter.scala | 31 ++++++++++++------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index 75e88eef2a..74036b9280 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -561,7 +561,14 @@ object Values { } object SigmaBoolean { - def estimateCost(sigmaTree: SigmaBoolean): Int = sigmaTree match { + /** Computes the estimated cost of verifying the given sigma proposition. + * This method should be O(nNodes), where nNodes is the number of the nodes in the + * tree. + * + * @param sb sigma proposition to estimate + * @return the value of estimated cost + */ + def estimateCost(sb: SigmaBoolean): Int = sb match { case _: ProveDlog => CostTable.proveDlogEvalCost case _: ProveDHTuple => CostTable.proveDHTupleEvalCost case and: CAND => diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index 543dd6bbfb..4ac75ed471 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -3,26 +3,26 @@ package sigmastate.interpreter import java.util import java.lang.{Math => JMath} -import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{everywherebu, rule, strategy} +import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{strategy, rule, everywherebu} import org.bitbucket.inkytonik.kiama.rewriting.Strategy import org.ergoplatform.validation.SigmaValidationSettings -import sigmastate.basics.DLogProtocol.{DLogInteractiveProver, FirstDLogProverMessage} +import sigmastate.basics.DLogProtocol.{FirstDLogProverMessage, DLogInteractiveProver} import scorex.util.ScorexLogging import sigmastate.SCollection.SByteArray import sigmastate.Values._ import sigmastate.eval.{IRContext, Evaluation} import sigmastate.lang.Terms.ValueOps import sigmastate.basics._ -import sigmastate.interpreter.Interpreter.{ScriptEnv, VerificationResult} -import sigmastate.lang.exceptions.{CostLimitException, InterpreterException} -import sigmastate.serialization.{SigmaSerializer, ValueSerializer} +import sigmastate.interpreter.Interpreter.{VerificationResult, ScriptEnv} +import sigmastate.lang.exceptions.{InterpreterException, CostLimitException} +import sigmastate.serialization.{ValueSerializer, SigmaSerializer} import sigmastate.utxo.DeserializeContext import sigmastate.{SType, _} import org.ergoplatform.validation.ValidationRules._ import scalan.util.BenchmarkUtil import sigmastate.utils.Helpers._ -import scala.util.{Try, Success} +import scala.util.{Success, Try} trait Interpreter extends ScorexLogging { @@ -197,13 +197,20 @@ trait Interpreter extends ScorexLogging { env: ScriptEnv): (SigmaBoolean, Long) = { implicit val vs: SigmaValidationSettings = context.validationSettings val prop = propositionFromErgoTree(ergoTree, context) - val (propTree, context2) = trySoftForkable[(BoolValue, CTX)](whenSoftFork = (TrueLeaf, context)) { - applyDeserializeContext(context, prop) - } + prop match { + case SigmaPropConstant(p) => + val sb = SigmaDsl.toSigmaBoolean(p) + val cost = SigmaBoolean.estimateCost(sb) + (sb, cost) + case _ => + val (propTree, context2) = trySoftForkable[(BoolValue, CTX)](whenSoftFork = (TrueLeaf, context)) { + applyDeserializeContext(context, prop) + } - // here we assume that when `propTree` is TrueProp then `reduceToCrypto` always succeeds - // and the rest of the verification is also trivial - reduceToCrypto(context2, env, propTree).getOrThrow + // here we assume that when `propTree` is TrueProp then `reduceToCrypto` always succeeds + // and the rest of the verification is also trivial + reduceToCrypto(context2, env, propTree).getOrThrow + } } /** Executes the script in a given context. From e3d89c18226e345c9d7daad184be8c882a0026b9 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sun, 28 Feb 2021 17:58:00 +0300 Subject: [PATCH 06/26] i694-box-opt: PredefScriptProcessor.scala implemented --- .../ergoplatform/ErgoLikeInterpreter.scala | 4 +- .../sigmastate/interpreter/Interpreter.scala | 64 ++++++++----- .../interpreter/PredefScriptProcessor.scala | 91 +++++++++++++++++++ 3 files changed, 134 insertions(+), 25 deletions(-) create mode 100644 sigmastate/src/main/scala/sigmastate/interpreter/PredefScriptProcessor.scala diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala index 01efc82022..f3605bf9c3 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala @@ -3,7 +3,7 @@ package org.ergoplatform import sigmastate.SCollection.SByteArray import sigmastate.Values._ import sigmastate.eval.IRContext -import sigmastate.interpreter.Interpreter +import sigmastate.interpreter.{Interpreter, PredefScriptProcessor} import sigmastate.utxo._ @@ -11,6 +11,8 @@ class ErgoLikeInterpreter(implicit val IR: IRContext) extends Interpreter { override type CTX <: ErgoLikeContext + override val predefScriptProcessor: PredefScriptProcessor = new PredefScriptProcessor(Array.empty[Seq[Byte]]) + override def substDeserialize(context: CTX, updateContext: CTX => Unit, node: SValue): Option[SValue] = node match { case d: DeserializeRegister[_] => context.boxesToSpend(context.selfIndex).get(d.reg).flatMap { v => diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index 4ac75ed471..753c567fd5 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -19,6 +19,7 @@ import sigmastate.serialization.{ValueSerializer, SigmaSerializer} import sigmastate.utxo.DeserializeContext import sigmastate.{SType, _} import org.ergoplatform.validation.ValidationRules._ +import scalan.{MutableLazy, Nullable} import scalan.util.BenchmarkUtil import sigmastate.utils.Helpers._ @@ -35,6 +36,8 @@ trait Interpreter extends ScorexLogging { val IR: IRContext import IR._ + def predefScriptProcessor: PredefScriptProcessor + /** Deserializes given script bytes using ValueSerializer (i.e. assuming expression tree format). * It also measures tree complexity adding to the total estimated cost of script execution. * The new returned context contains increased `initCost` and should be used for further processing. @@ -118,23 +121,6 @@ trait Interpreter extends ScorexLogging { (res, currContext.value) } - private def calcResult(context: special.sigma.Context, calcF: Ref[IR.Context => Any]): special.sigma.SigmaProp = { - import IR._ - import Context._ - import SigmaProp._ - val res = calcF.elem.eRange.asInstanceOf[Any] match { - case _: SigmaPropElem[_] => - val valueFun = compile[SContext, SSigmaProp, Context, SigmaProp](getDataEnv, asRep[Context => SigmaProp](calcF)) - val (sp, _) = valueFun(context) - sp - case BooleanElement => - val valueFun = compile[SContext, Boolean, IR.Context, Boolean](IR.getDataEnv, asRep[Context => Boolean](calcF)) - val (b, _) = valueFun(context) - sigmaDslBuilderValue.sigmaProp(b) - } - res - } - /** This method is used in both prover and verifier to compute SigmaBoolean value. * As the first step the cost of computing the `exp` expression in the given context is estimated. * If cost is above limit @@ -168,7 +154,7 @@ trait Interpreter extends ScorexLogging { val calcF = costingRes.calcF CheckCalcFunc(IR)(calcF) val calcCtx = context.toSigmaContext(isCost = false) - val res = calcResult(calcCtx, calcF) + val res = Interpreter.calcResult(IR)(calcCtx, calcF) SigmaDsl.toSigmaBoolean(res) -> estimatedCost } } @@ -203,13 +189,18 @@ trait Interpreter extends ScorexLogging { val cost = SigmaBoolean.estimateCost(sb) (sb, cost) case _ => - val (propTree, context2) = trySoftForkable[(BoolValue, CTX)](whenSoftFork = (TrueLeaf, context)) { - applyDeserializeContext(context, prop) - } + predefScriptProcessor.getVerifier(ergoTree) match { + case Nullable(verifier) => + verifier.verify(context) + case _ => + val (propTree, context2) = trySoftForkable[(BoolValue, CTX)](whenSoftFork = (TrueLeaf, context)) { + applyDeserializeContext(context, prop) + } - // here we assume that when `propTree` is TrueProp then `reduceToCrypto` always succeeds - // and the rest of the verification is also trivial - reduceToCrypto(context2, env, propTree).getOrThrow + // here we assume that when `propTree` is TrueProp then `reduceToCrypto` always succeeds + // and the rest of the verification is also trivial + reduceToCrypto(context2, env, propTree).getOrThrow + } } } @@ -407,6 +398,31 @@ object Interpreter { */ val MaxSupportedScriptVersion: Byte = 1 // supported versions 0 and 1 + def calcResult(IR: IRContext) + (context: special.sigma.Context, + calcF: IR.Ref[IR.Context => Any]): special.sigma.SigmaProp = { + import IR._ + import IR.Context._ + import IR.SigmaProp._ + import IR.Liftables._ + val res = calcF.elem.eRange.asInstanceOf[Any] match { + case _: SigmaPropElem[_] => + val valueFun = compile[IR.Context.SContext, IR.SigmaProp.SSigmaProp, IR.Context, IR.SigmaProp]( + getDataEnv, + IR.asRep[IR.Context => IR.SigmaProp](calcF))(LiftableContext, LiftableSigmaProp) + val (sp, _) = valueFun(context) + sp + case BooleanElement => + val valueFun = compile[SContext, Boolean, IR.Context, Boolean]( + IR.getDataEnv, + asRep[IR.Context => Boolean](calcF))(LiftableContext, BooleanIsLiftable) + val (b, _) = valueFun(context) + sigmaDslBuilderValue.sigmaProp(b) + } + res + } + + def error(msg: String) = throw new InterpreterException(msg) } \ No newline at end of file diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/PredefScriptProcessor.scala b/sigmastate/src/main/scala/sigmastate/interpreter/PredefScriptProcessor.scala new file mode 100644 index 0000000000..331b3bcacf --- /dev/null +++ b/sigmastate/src/main/scala/sigmastate/interpreter/PredefScriptProcessor.scala @@ -0,0 +1,91 @@ +package sigmastate.interpreter + +import org.ergoplatform.validation.ValidationRules.{trySoftForkable, CheckCostFunc, CheckCalcFunc} +import scalan.{AVHashMap, Nullable} +import sigmastate.TrivialProp +import sigmastate.Values.ErgoTree +import sigmastate.eval.{RuntimeIRContext, IRContext} +import sigmastate.interpreter.Interpreter.ReductionResult +import sigmastate.serialization.ErgoTreeSerializer +import sigmastate.utils.Helpers._ + +/** This class implements optimized verification of the given predefined script. + * Pre-compilation of the necessary graphs is performed as part of constructor and + * the graphs are stored in the given IR instance. + * + * The code make the following assumptions: + * 1) the given script doesn't contain [[sigmastate.utxo.DeserializeContext]] and + * [[sigmastate.utxo.DeserializeRegister]] + * 2) Soft-forkability checks are not performed in constructor, thus any exception is + * propagated up the stack. + * + * The code should correspond to reduceToCrypto method, but some operations may be + * optimized due to assumptions above. + */ +case class PredefScriptVerifier(scriptBytes: Seq[Byte])(implicit val IR: IRContext) { + + /** The following operations create [[RCostingResultEx]] structure for the given + * `scriptBytes` and they should be the same as in `reduceToCrypto` method. + * This can be viewed as ahead of time pre-compilation of the cost and calc graphs + * which are reused many times in the `verify` method. + */ + val costingRes = { + val tree = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(scriptBytes.toArray) + val prop = tree.toProposition(tree.isConstantSegregation) + val res = IR.doCostingEx(Interpreter.emptyEnv, prop, true) + val costF = res.costF + CheckCostFunc(IR)(IR.asRep[Any => Int](costF)) + val calcF = res.calcF + CheckCalcFunc(IR)(calcF) + res + } + + /** Verify this pre-compiled script in the given context. + * This is equivalent to reduceToCrypto, except that graph construction is + * completely avoided. + */ + def verify(context: InterpreterContext): ReductionResult = { + import IR._ + implicit val vs = context.validationSettings + val maxCost = context.costLimit + val initCost = context.initCost + trySoftForkable[ReductionResult](whenSoftFork = TrivialProp.TrueProp -> 0) { + val costF = costingRes.costF + val costingCtx = context.toSigmaContext(isCost = true) + 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) + SigmaDsl.toSigmaBoolean(res) -> estimatedCost + } + } +} + +/** Script processor which holds pre-compiled verifiers for the given scripts. + * @param predefScripts collection of scripts to pre-compile (each given by ErgoTree bytes) + */ +case class PredefScriptProcessor(predefScripts: Seq[Seq[Byte]]) { + private implicit val IR: IRContext = new RuntimeIRContext + + /** Holds for each ErgoTree bytes the corresponding pre-compiled verifier. */ + val verifiers = { + val res = AVHashMap[Seq[Byte], PredefScriptVerifier](predefScripts.length) + predefScripts.foreach { s => + val verifier = PredefScriptVerifier(s) + res.put(s, verifier) + } + res + } + + /** Looks up verifier for the given ErgoTree using its 'bytes' property. + * @param ergoTree a tree to lookup pre-compiled verifier. + * @return non-empty Nullable instance with verifier for the given tree, otherwise + * Nullable.None + */ + def getVerifier(ergoTree: ErgoTree): Nullable[PredefScriptVerifier] = { + val key: Seq[Byte] = ergoTree.bytes + verifiers.get(key) + } +} From d208c9249d89173082141689cbede226211b061c Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sun, 28 Feb 2021 18:15:33 +0300 Subject: [PATCH 07/26] i694-box-opt: PredefScriptProcessorSpecification.scala added --- .../PredefScriptProcessorSpecification.scala | 23 +++++++++++++++++++ .../special/sigma/SigmaTestingData.scala | 16 +++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 sigmastate/src/test/scala/sigmastate/interpreter/PredefScriptProcessorSpecification.scala diff --git a/sigmastate/src/test/scala/sigmastate/interpreter/PredefScriptProcessorSpecification.scala b/sigmastate/src/test/scala/sigmastate/interpreter/PredefScriptProcessorSpecification.scala new file mode 100644 index 0000000000..3ae3aa8e36 --- /dev/null +++ b/sigmastate/src/test/scala/sigmastate/interpreter/PredefScriptProcessorSpecification.scala @@ -0,0 +1,23 @@ +package sigmastate.interpreter + +import sigmastate.serialization.ErgoTreeSerializer +import org.ergoplatform.settings.ErgoAlgos +import sigmastate.helpers.SigmaPPrint +import special.sigma.SigmaTestingData + +class PredefScriptProcessorSpecification extends SigmaTestingData { + + property("deserialize from hex") { + predefScriptHexes.foreach { hex => + val bytes = ErgoAlgos.decodeUnsafe(hex) + val tree = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(bytes) + println( + s"""Tree: '$hex' + |------------------------------------------ + |""".stripMargin) + SigmaPPrint.pprintln(tree, width = 100, height = 150) + println() + } + } + +} diff --git a/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala b/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala index d5c21c9f64..ccfbfcc991 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala @@ -98,4 +98,20 @@ trait SigmaTestingData extends SigmaTestingCommons with ObjectGenerators { minerPk = SigmaDsl.groupGenerator, votes = Colls.emptyColl[Byte] ) + + val predefScriptHexes = Seq( + "0008cd03a40b2249d6a9cc7eedf21188842acef44f3df110a58a687ba3e28cbc2e97ead2", + "1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304", + "101004020e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a7017300730110010204020404040004c0fd4f05808c82f5f6030580b8c9e5ae040580f882ad16040204c0944004c0f407040004000580f882ad16d19683030191a38cc7a7019683020193c2b2a57300007473017302830108cdeeac93a38cc7b2a573030001978302019683040193b1a5730493c2a7c2b2a573050093958fa3730673079973089c73097e9a730a9d99a3730b730c0599c1a7c1b2a5730d00938cc7b2a5730e0001a390c1a7730f", + "10160e201a6a8c16e4b1cc9d73d03183565cfb8e79dd84198cb66beeed7d3463e0da2b9805000500040008cd03b038b0783c899be6b5b98bcf55df573c87cb2e01c16604c174e5a7e6105e848e04040406040204000400040604080402040004000402040405000500050205040100d808d601b1a4d602c2a7d603c6a70405d6047300d605860272047301d6067302d607d9010763d806d609c27207d60a9372097202d60bd805d60bc17207d60cc1a7d60de47203d60e99720c720dd60f92720b720e720fd60ced720a720bd60dd802d60dc672070405d60e93720d7203720ed60eed720c720d720ed608d9010863d806d60adb63087208d60bb2720a7303017205d60c8c720b01d60d93720c7204d60ed801d60e8c720b02720ed60f95720d720e7206720feb02730495ed937201730593b1a57306d802d6097207d60a7208d1edda720901b2a57307008fda720a01b2a5730800da720a01b2a473090095ed937201730a93b1a5730bd803d609da720801b2a4730c00d60ada720801b2a4730d00d60b999a720a72099ada720801b2a5730e00da720801b2a5730f00d1edda720701b2a5731000eded917209731191720a7312ec93720b731393720b7314d17315", + "10060e2002d1541415c323527f19ef5b103eb33c220ea8b66fcb711806b0037d115d63f204000402040004040e201a6a8c16e4b1cc9d73d03183565cfb8e79dd84198cb66beeed7d3463e0da2b98d803d601e4c6a70507d602d901026393cbc27202e4c6a7070ed6037300ea02eb02cd7201cedb6a01dde4c6a70407e4c6a706077201d1ececedda720201b2a573010093cbc2b2a47302007203edda720201b2a473030093cbc2b2a47304007203afa5d9010463afdb63087204d901064d0e948c7206017305", + "100d0e201a6a8c16e4b1cc9d73d03183565cfb8e79dd84198cb66beeed7d3463e0da2b9804000e20c9ffb7bf74cd7a0fc2b76baf54b4c6192b0a1689e6b0ea6b5d988447c353a3ee0400040004020504040205000402040204000100d806d601e4c6a70407d6027300d603b2a5730100d604c672030407d6057302d606db6a01ddeb02ea02cd7201d1afa5d9010763afdb63087207d901094d0e948c720901720295e67204d808d607db63087203d608b27207730300d609db6308a7d60ab27209730400d60bd9010b63eded93cbc2720b720593e4c6720b070ecbc2a793c1720bc1a7d60cb2a5730500d60de4c672030507d60ee47204ea02d1edededededededeced938c7208018c720a01919c8c72080273068c720a0293cbc2b2a47307007205edededda720b017203da720b01720c937207db6308720cd801d60f86027202730893b27209730901720fb27207730a01720f93e4c672030607720193e4c6720c0607720193e4c6720c0407720d93e4c6720c0507e4720493c5a7c5b2a4730b0094e47204720deb02ce72067201720e720dce72067201720d720ed1730c", + "100c040004000e201a6a8c16e4b1cc9d73d03183565cfb8e79dd84198cb66beeed7d3463e0da2b98040005020400040005020402040204000400d805d601e4c6a70507d602d9010263ed93cbc27202e4c6a7070e93c17202c1a7d603b2a5730000d604b2a4730100d6057302ea02eb02cd7201cedb6a01dde4c6a70407e4c6a706077201d1ececededda720201720393c57204c5a7eddad9010663d801d608b2db63087206730300ed938c7208017205928c7208027304017203938cb2db6308720373050002998cb2db63087204730600027307ededda720201720493c5b2a4730800c5a7938cb2db6308b2a4730900730a00017205afa5d901066393b1db63087206730b", + "100c0e201008c10aea11a275eaac3bffdd08427ec6e80b7512476a89396cd25d415a2de10e206c143ec48214ce2d204c959839c8ddfd0ca030007dba4a95894a0815fe4d41380404040604000400040604080400040205c08db70108cd03b038b0783c899be6b5b98bcf55df573c87cb2e01c16604c174e5a7e6105e848ed803d601b1a4d6027300d6037301eb02d1edecededed937201730293b1a57303dad901046393cbc27204720201b2a573040093cbc2b2a47305007203ededed937201730693b1a57307dad901046393cbc27204720201b2a473080093cbc2b2a47309007203aea5d9010463ed93c27204c2a792c1720499c1a7730a730b", + "1000d801d601e4c6a70507eb02cd7201cedb6a01dde4c6a70407e4c6a706077201", + "100204a00b08cd02b3a06d6eaa8671431ba1db4dd427a77f75a5c2acbd71bfb725d38adc2b55f669ea02d192a39a8cc7a70173007301", + "100204a00b08cd02ebaaeb381c9d855af1807781fa20ef6c0c34833275ce7913a9e4469f7bcb3becea02d192a39a8cc7a70173007301", + "10060e2002d1541415c323527f19ef5b103eb33c220ea8b66fcb711806b0037d115d63f204000402040004040e201a6a8c16e4b1cc9d73d03183565cfb8e79dd84198cb66beeed7d3463e0da2b98d803d601e4c6a70507d602d901026393cbc27202e4c6a7070ed6037300ea02eb02cd7201cedb6a01dde4c6a70407e4c6a706077201d1ececedda720201b2a573010093cbc2b2a47302007203edda720201b2a473030093cbc2b2a47304007203afa5d9010463afdb63087204d901064d0e948c7206017305" + ) + } From 0935a5946328d3015373f6142044f1da62b6c2fe Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sun, 28 Feb 2021 19:15:03 +0300 Subject: [PATCH 08/26] i694-box-opt: more test in PredefScriptProcessorSpecification --- .../interpreter/PredefScriptProcessor.scala | 6 ++-- .../PredefScriptProcessorSpecification.scala | 34 ++++++++++++++++--- .../special/sigma/SigmaTestingData.scala | 3 +- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/PredefScriptProcessor.scala b/sigmastate/src/main/scala/sigmastate/interpreter/PredefScriptProcessor.scala index 331b3bcacf..f4e525e5d0 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/PredefScriptProcessor.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/PredefScriptProcessor.scala @@ -1,6 +1,7 @@ package sigmastate.interpreter -import org.ergoplatform.validation.ValidationRules.{trySoftForkable, CheckCostFunc, CheckCalcFunc} +import org.ergoplatform.settings.ErgoAlgos +import org.ergoplatform.validation.ValidationRules.{CheckCostFunc, CheckCalcFunc, trySoftForkable} import scalan.{AVHashMap, Nullable} import sigmastate.TrivialProp import sigmastate.Values.ErgoTree @@ -74,7 +75,8 @@ case class PredefScriptProcessor(predefScripts: Seq[Seq[Byte]]) { val res = AVHashMap[Seq[Byte], PredefScriptVerifier](predefScripts.length) predefScripts.foreach { s => val verifier = PredefScriptVerifier(s) - res.put(s, verifier) + val old = res.put(s, verifier) + require(old == null, s"duplicate predefined script: '${ErgoAlgos.encode(s.toArray)}'") } res } diff --git a/sigmastate/src/test/scala/sigmastate/interpreter/PredefScriptProcessorSpecification.scala b/sigmastate/src/test/scala/sigmastate/interpreter/PredefScriptProcessorSpecification.scala index 3ae3aa8e36..dc00318c1c 100644 --- a/sigmastate/src/test/scala/sigmastate/interpreter/PredefScriptProcessorSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/interpreter/PredefScriptProcessorSpecification.scala @@ -2,15 +2,21 @@ package sigmastate.interpreter import sigmastate.serialization.ErgoTreeSerializer import org.ergoplatform.settings.ErgoAlgos +import sigmastate.Values.ErgoTree import sigmastate.helpers.SigmaPPrint -import special.sigma.SigmaTestingData +import special.sigma.SigmaDslTesting -class PredefScriptProcessorSpecification extends SigmaTestingData { +class PredefScriptProcessorSpecification extends SigmaDslTesting { + + def parseTree(hex: String): ErgoTree = { + val bytes = ErgoAlgos.decodeUnsafe(hex) + val tree = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(bytes) + tree + } property("deserialize from hex") { predefScriptHexes.foreach { hex => - val bytes = ErgoAlgos.decodeUnsafe(hex) - val tree = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(bytes) + val tree = parseTree(hex) println( s"""Tree: '$hex' |------------------------------------------ @@ -20,4 +26,24 @@ class PredefScriptProcessorSpecification extends SigmaTestingData { } } + property("equality") { + val predefTrees = predefScriptHexes.map { h => parseTree(h) } + val extraTrees = Seq(TrueTree, FalseTree) + val trees = extraTrees ++ predefTrees + val scripts = trees.map { t => t.bytes: Seq[Byte] } + val processor = PredefScriptProcessor(scripts) + trees.foreach { t => + processor.getVerifier(t).isDefined shouldBe true + } + } + + property("rejects duplicates") { + val trees = Seq(TrueTree, TrueTree) + val scripts = trees.map { t => t.bytes: Seq[Byte] } + assertExceptionThrown( + PredefScriptProcessor(scripts), + { case _: IllegalArgumentException => true + case _ => false } + ) + } } diff --git a/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala b/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala index ccfbfcc991..0317c4e842 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala @@ -110,8 +110,7 @@ trait SigmaTestingData extends SigmaTestingCommons with ObjectGenerators { "100c0e201008c10aea11a275eaac3bffdd08427ec6e80b7512476a89396cd25d415a2de10e206c143ec48214ce2d204c959839c8ddfd0ca030007dba4a95894a0815fe4d41380404040604000400040604080400040205c08db70108cd03b038b0783c899be6b5b98bcf55df573c87cb2e01c16604c174e5a7e6105e848ed803d601b1a4d6027300d6037301eb02d1edecededed937201730293b1a57303dad901046393cbc27204720201b2a573040093cbc2b2a47305007203ededed937201730693b1a57307dad901046393cbc27204720201b2a473080093cbc2b2a47309007203aea5d9010463ed93c27204c2a792c1720499c1a7730a730b", "1000d801d601e4c6a70507eb02cd7201cedb6a01dde4c6a70407e4c6a706077201", "100204a00b08cd02b3a06d6eaa8671431ba1db4dd427a77f75a5c2acbd71bfb725d38adc2b55f669ea02d192a39a8cc7a70173007301", - "100204a00b08cd02ebaaeb381c9d855af1807781fa20ef6c0c34833275ce7913a9e4469f7bcb3becea02d192a39a8cc7a70173007301", - "10060e2002d1541415c323527f19ef5b103eb33c220ea8b66fcb711806b0037d115d63f204000402040004040e201a6a8c16e4b1cc9d73d03183565cfb8e79dd84198cb66beeed7d3463e0da2b98d803d601e4c6a70507d602d901026393cbc27202e4c6a7070ed6037300ea02eb02cd7201cedb6a01dde4c6a70407e4c6a706077201d1ececedda720201b2a573010093cbc2b2a47302007203edda720201b2a473030093cbc2b2a47304007203afa5d9010463afdb63087204d901064d0e948c7206017305" + "100204a00b08cd02ebaaeb381c9d855af1807781fa20ef6c0c34833275ce7913a9e4469f7bcb3becea02d192a39a8cc7a70173007301" ) } From 55afc0ff30706e774ef0cfdbf41a4cde5e7facd1 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sun, 28 Feb 2021 21:18:30 +0300 Subject: [PATCH 09/26] i694-box-opt: rename PredefScriptProcessor -> PrecompiledScriptProcessor --- .../org/ergoplatform/ErgoLikeInterpreter.scala | 4 ++-- .../scala/sigmastate/interpreter/Interpreter.scala | 4 ++-- ...ssor.scala => PrecompiledScriptProcessor.scala} | 14 +++++++------- ... PrecompiledScriptProcessorSpecification.scala} | 6 +++--- 4 files changed, 14 insertions(+), 14 deletions(-) rename sigmastate/src/main/scala/sigmastate/interpreter/{PredefScriptProcessor.scala => PrecompiledScriptProcessor.scala} (87%) rename sigmastate/src/test/scala/sigmastate/interpreter/{PredefScriptProcessorSpecification.scala => PrecompiledScriptProcessorSpecification.scala} (88%) diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala index f3605bf9c3..1ad65baa5a 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala @@ -3,7 +3,7 @@ package org.ergoplatform import sigmastate.SCollection.SByteArray import sigmastate.Values._ import sigmastate.eval.IRContext -import sigmastate.interpreter.{Interpreter, PredefScriptProcessor} +import sigmastate.interpreter.{Interpreter, PrecompiledScriptProcessor} import sigmastate.utxo._ @@ -11,7 +11,7 @@ class ErgoLikeInterpreter(implicit val IR: IRContext) extends Interpreter { override type CTX <: ErgoLikeContext - override val predefScriptProcessor: PredefScriptProcessor = new PredefScriptProcessor(Array.empty[Seq[Byte]]) + override val precompiledScriptProcessor: PrecompiledScriptProcessor = new PrecompiledScriptProcessor(Array.empty[Seq[Byte]]) override def substDeserialize(context: CTX, updateContext: CTX => Unit, node: SValue): Option[SValue] = node match { case d: DeserializeRegister[_] => diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index 753c567fd5..705c7fb811 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -36,7 +36,7 @@ trait Interpreter extends ScorexLogging { val IR: IRContext import IR._ - def predefScriptProcessor: PredefScriptProcessor + def precompiledScriptProcessor: PrecompiledScriptProcessor /** Deserializes given script bytes using ValueSerializer (i.e. assuming expression tree format). * It also measures tree complexity adding to the total estimated cost of script execution. @@ -189,7 +189,7 @@ trait Interpreter extends ScorexLogging { val cost = SigmaBoolean.estimateCost(sb) (sb, cost) case _ => - predefScriptProcessor.getVerifier(ergoTree) match { + precompiledScriptProcessor.getVerifier(ergoTree) match { case Nullable(verifier) => verifier.verify(context) case _ => diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/PredefScriptProcessor.scala b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala similarity index 87% rename from sigmastate/src/main/scala/sigmastate/interpreter/PredefScriptProcessor.scala rename to sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala index f4e525e5d0..f8a226c2ea 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/PredefScriptProcessor.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala @@ -10,7 +10,7 @@ import sigmastate.interpreter.Interpreter.ReductionResult import sigmastate.serialization.ErgoTreeSerializer import sigmastate.utils.Helpers._ -/** This class implements optimized verification of the given predefined script. +/** This class implements optimized verification of the given pre-compiled script. * Pre-compilation of the necessary graphs is performed as part of constructor and * the graphs are stored in the given IR instance. * @@ -23,7 +23,7 @@ import sigmastate.utils.Helpers._ * The code should correspond to reduceToCrypto method, but some operations may be * optimized due to assumptions above. */ -case class PredefScriptVerifier(scriptBytes: Seq[Byte])(implicit val IR: IRContext) { +case class PrecompiledScriptVerifier(scriptBytes: Seq[Byte])(implicit val IR: IRContext) { /** The following operations create [[RCostingResultEx]] structure for the given * `scriptBytes` and they should be the same as in `reduceToCrypto` method. @@ -65,16 +65,16 @@ case class PredefScriptVerifier(scriptBytes: Seq[Byte])(implicit val IR: IRConte } /** Script processor which holds pre-compiled verifiers for the given scripts. - * @param predefScripts collection of scripts to pre-compile (each given by ErgoTree bytes) + * @param predefScripts collection of scripts to ALWAYS pre-compile (each given by ErgoTree bytes) */ -case class PredefScriptProcessor(predefScripts: Seq[Seq[Byte]]) { +class PrecompiledScriptProcessor(val predefScripts: Seq[Seq[Byte]]) { private implicit val IR: IRContext = new RuntimeIRContext /** Holds for each ErgoTree bytes the corresponding pre-compiled verifier. */ val verifiers = { - val res = AVHashMap[Seq[Byte], PredefScriptVerifier](predefScripts.length) + val res = AVHashMap[Seq[Byte], PrecompiledScriptVerifier](predefScripts.length) predefScripts.foreach { s => - val verifier = PredefScriptVerifier(s) + val verifier = PrecompiledScriptVerifier(s) val old = res.put(s, verifier) require(old == null, s"duplicate predefined script: '${ErgoAlgos.encode(s.toArray)}'") } @@ -86,7 +86,7 @@ case class PredefScriptProcessor(predefScripts: Seq[Seq[Byte]]) { * @return non-empty Nullable instance with verifier for the given tree, otherwise * Nullable.None */ - def getVerifier(ergoTree: ErgoTree): Nullable[PredefScriptVerifier] = { + def getVerifier(ergoTree: ErgoTree): Nullable[PrecompiledScriptVerifier] = { val key: Seq[Byte] = ergoTree.bytes verifiers.get(key) } diff --git a/sigmastate/src/test/scala/sigmastate/interpreter/PredefScriptProcessorSpecification.scala b/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala similarity index 88% rename from sigmastate/src/test/scala/sigmastate/interpreter/PredefScriptProcessorSpecification.scala rename to sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala index dc00318c1c..f938417ce1 100644 --- a/sigmastate/src/test/scala/sigmastate/interpreter/PredefScriptProcessorSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala @@ -6,7 +6,7 @@ import sigmastate.Values.ErgoTree import sigmastate.helpers.SigmaPPrint import special.sigma.SigmaDslTesting -class PredefScriptProcessorSpecification extends SigmaDslTesting { +class PrecompiledScriptProcessorSpecification extends SigmaDslTesting { def parseTree(hex: String): ErgoTree = { val bytes = ErgoAlgos.decodeUnsafe(hex) @@ -31,7 +31,7 @@ class PredefScriptProcessorSpecification extends SigmaDslTesting { val extraTrees = Seq(TrueTree, FalseTree) val trees = extraTrees ++ predefTrees val scripts = trees.map { t => t.bytes: Seq[Byte] } - val processor = PredefScriptProcessor(scripts) + val processor = PrecompiledScriptProcessor(scripts) trees.foreach { t => processor.getVerifier(t).isDefined shouldBe true } @@ -41,7 +41,7 @@ class PredefScriptProcessorSpecification extends SigmaDslTesting { val trees = Seq(TrueTree, TrueTree) val scripts = trees.map { t => t.bytes: Seq[Byte] } assertExceptionThrown( - PredefScriptProcessor(scripts), + PrecompiledScriptProcessor(scripts), { case _: IllegalArgumentException => true case _ => false } ) From d4df33158868d7483984c9dbb1af42f10b58c487 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 1 Mar 2021 15:15:51 +0300 Subject: [PATCH 10/26] i694-box-opt: version of PrecompiledScriptProcessor which uses createIR method --- .../ergoplatform/ErgoLikeInterpreter.scala | 2 +- .../sigmastate/interpreter/Interpreter.scala | 23 +++--- .../PrecompiledScriptProcessor.scala | 75 ++++++++++++++----- .../TestingInterpreterSpecification.scala | 28 +++++-- ...compiledScriptProcessorSpecification.scala | 12 +-- .../scala/special/sigma/SigmaDslTesting.scala | 14 +++- .../special/sigma/SigmaTestingData.scala | 9 +++ 7 files changed, 115 insertions(+), 48 deletions(-) diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala index 1ad65baa5a..ad3410ee08 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala @@ -11,7 +11,7 @@ class ErgoLikeInterpreter(implicit val IR: IRContext) extends Interpreter { override type CTX <: ErgoLikeContext - override val precompiledScriptProcessor: PrecompiledScriptProcessor = new PrecompiledScriptProcessor(Array.empty[Seq[Byte]]) + override val precompiledScriptProcessor: PrecompiledScriptProcessor = PrecompiledScriptProcessor.Default override def substDeserialize(context: CTX, updateContext: CTX => Unit, node: SValue): Option[SValue] = node match { case d: DeserializeRegister[_] => diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index 705c7fb811..947e8a48ff 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -82,15 +82,6 @@ trait Interpreter extends ScorexLogging { case _ => None } - def toValidScriptType(exp: SValue): BoolValue = exp match { - case v: Value[SBoolean.type]@unchecked if v.tpe == SBoolean => v - case p: SValue if p.tpe == SSigmaProp => p.asSigmaProp.isProven - case x => - // This case is not possible, due to exp is always of Boolean/SigmaProp type. - // In case it will ever change, leave it here to throw an explaining message. - throw new Error(s"Context-dependent pre-processing should produce tree of type Boolean or SigmaProp but was $x") - } - class MutableCell[T](var value: T) /** Extracts proposition for ErgoTree handing soft-fork condition. @@ -117,7 +108,7 @@ trait Interpreter extends ScorexLogging { substDeserialize(currContext.value, { ctx: CTX => currContext.value = ctx }, x) } val Some(substTree: SValue) = everywherebu(substRule)(exp) - val res = toValidScriptType(substTree) + val res = Interpreter.toValidScriptType(substTree) (res, currContext.value) } @@ -189,9 +180,9 @@ trait Interpreter extends ScorexLogging { val cost = SigmaBoolean.estimateCost(sb) (sb, cost) case _ => - precompiledScriptProcessor.getVerifier(ergoTree) match { + precompiledScriptProcessor.getReducer(ergoTree) match { case Nullable(verifier) => - verifier.verify(context) + verifier.reduce(context) case _ => val (propTree, context2) = trySoftForkable[(BoolValue, CTX)](whenSoftFork = (TrueLeaf, context)) { applyDeserializeContext(context, prop) @@ -422,6 +413,14 @@ object Interpreter { res } + def toValidScriptType(exp: SValue): BoolValue = exp match { + case v: Value[SBoolean.type]@unchecked if v.tpe == SBoolean => v + case p: SValue if p.tpe == SSigmaProp => p.asSigmaProp.isProven + case x => + // This case is not possible, due to exp is always of Boolean/SigmaProp type. + // In case it will ever change, leave it here to throw an explaining message. + throw new Error(s"Context-dependent pre-processing should produce tree of type Boolean or SigmaProp but was $x") + } def error(msg: String) = throw new InterpreterException(msg) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala index f8a226c2ea..680b34d9ff 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala @@ -1,5 +1,6 @@ package sigmastate.interpreter +import com.google.common.cache.{RemovalListener, CacheBuilder, RemovalNotification, CacheLoader} import org.ergoplatform.settings.ErgoAlgos import org.ergoplatform.validation.ValidationRules.{CheckCostFunc, CheckCalcFunc, trySoftForkable} import scalan.{AVHashMap, Nullable} @@ -10,30 +11,31 @@ import sigmastate.interpreter.Interpreter.ReductionResult import sigmastate.serialization.ErgoTreeSerializer import sigmastate.utils.Helpers._ -/** This class implements optimized verification of the given pre-compiled script. +import scala.collection.mutable + +/** This class implements optimized reduction of the given pre-compiled script. * Pre-compilation of the necessary graphs is performed as part of constructor and * the graphs are stored in the given IR instance. * * The code make the following assumptions: - * 1) the given script doesn't contain [[sigmastate.utxo.DeserializeContext]] and + * 1) the given script doesn't contain both [[sigmastate.utxo.DeserializeContext]] and * [[sigmastate.utxo.DeserializeRegister]] - * 2) Soft-forkability checks are not performed in constructor, thus any exception is - * propagated up the stack. * * The code should correspond to reduceToCrypto method, but some operations may be * optimized due to assumptions above. */ -case class PrecompiledScriptVerifier(scriptBytes: Seq[Byte])(implicit val IR: IRContext) { +case class PrecompiledScriptReducer(scriptBytes: Seq[Byte])(implicit val IR: IRContext) { /** The following operations create [[RCostingResultEx]] structure for the given * `scriptBytes` and they should be the same as in `reduceToCrypto` method. * This can be viewed as ahead of time pre-compilation of the cost and calc graphs - * which are reused many times in the `verify` method. + * which are reused over many invocations of the `reduce` method. */ val costingRes = { val tree = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(scriptBytes.toArray) val prop = tree.toProposition(tree.isConstantSegregation) - val res = IR.doCostingEx(Interpreter.emptyEnv, prop, true) + val validProp = Interpreter.toValidScriptType(prop) + val res = IR.doCostingEx(Interpreter.emptyEnv, validProp, true) val costF = res.costF CheckCostFunc(IR)(IR.asRep[Any => Int](costF)) val calcF = res.calcF @@ -41,11 +43,11 @@ case class PrecompiledScriptVerifier(scriptBytes: Seq[Byte])(implicit val IR: IR res } - /** Verify this pre-compiled script in the given context. + /** Reduce this pre-compiled script in the given context. * This is equivalent to reduceToCrypto, except that graph construction is * completely avoided. */ - def verify(context: InterpreterContext): ReductionResult = { + def reduce(context: InterpreterContext): ReductionResult = { import IR._ implicit val vs = context.validationSettings val maxCost = context.costLimit @@ -64,30 +66,67 @@ case class PrecompiledScriptVerifier(scriptBytes: Seq[Byte])(implicit val IR: IR } } -/** Script processor which holds pre-compiled verifiers for the given scripts. +/** Script processor which holds pre-compiled reducers for the given scripts. * @param predefScripts collection of scripts to ALWAYS pre-compile (each given by ErgoTree bytes) */ class PrecompiledScriptProcessor(val predefScripts: Seq[Seq[Byte]]) { - private implicit val IR: IRContext = new RuntimeIRContext + /** Convenience synonym */ + type PSR = PrecompiledScriptReducer + + /** Creates a new instance of IRContex to be used in reducers. + * The default implementation can be overriden in derived classes. + */ + protected def createIR(): IRContext = new RuntimeIRContext - /** Holds for each ErgoTree bytes the corresponding pre-compiled verifier. */ - val verifiers = { - val res = AVHashMap[Seq[Byte], PrecompiledScriptVerifier](predefScripts.length) + /** Holds for each ErgoTree bytes the corresponding pre-compiled reducer. */ + val reducers = { + implicit val IR: IRContext = createIR() + val res = AVHashMap[Seq[Byte], PSR](predefScripts.length) predefScripts.foreach { s => - val verifier = PrecompiledScriptVerifier(s) - val old = res.put(s, verifier) + val r = PrecompiledScriptReducer(s) + val old = res.put(s, r) require(old == null, s"duplicate predefined script: '${ErgoAlgos.encode(s.toArray)}'") } res } + private val CacheListener = new RemovalListener[Seq[Byte], PSR]() { + override def onRemoval(notification: RemovalNotification[Seq[Byte], PSR]): Unit = { + if (notification.wasEvicted()) { + val scriptHex = ErgoAlgos.encode(notification.getKey.toArray) + println(s"Evicted: ${scriptHex}") + } + } + } + + val cache = { + CacheBuilder.newBuilder + .maximumSize(1000) + .removalListener(CacheListener) + .recordStats() + .build(new CacheLoader[Seq[Byte], PSR]() { + override def load(key: Seq[Byte]): PSR = { + PrecompiledScriptReducer(scriptBytes = key)(createIR()) + } + }) + } + /** Looks up verifier for the given ErgoTree using its 'bytes' property. * @param ergoTree a tree to lookup pre-compiled verifier. * @return non-empty Nullable instance with verifier for the given tree, otherwise * Nullable.None */ - def getVerifier(ergoTree: ErgoTree): Nullable[PrecompiledScriptVerifier] = { + def getReducer(ergoTree: ErgoTree): Nullable[PSR] = { val key: Seq[Byte] = ergoTree.bytes - verifiers.get(key) + reducers.get(key) match { + case Nullable.None => + val verifier = cache.get(key) + Nullable(verifier) + case v => v + } } } + +object PrecompiledScriptProcessor { + val Default = new PrecompiledScriptProcessor(mutable.WrappedArray.empty[Seq[Byte]]) +} diff --git a/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala b/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala index e29d97838e..2f53dea87f 100644 --- a/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala @@ -1,6 +1,6 @@ package sigmastate -import sigmastate.basics.DLogProtocol.{DLogProverInput, ProveDlog} +import sigmastate.basics.DLogProtocol.{ProveDlog, DLogProverInput} import scorex.crypto.hash.Blake2b256 import sigmastate.Values._ import sigmastate.interpreter._ @@ -8,19 +8,32 @@ import scalan.util.Extensions._ import Interpreter._ import sigmastate.lang.Terms._ import org.ergoplatform._ +import org.scalatest.BeforeAndAfterAll import scorex.util.encode.Base58 -import sigmastate.helpers.{ErgoLikeContextTesting, ErgoLikeTestInterpreter, ErgoLikeTestProvingInterpreter, SigmaTestingCommons} +import sigmastate.eval.IRContext +import sigmastate.helpers.{ErgoLikeContextTesting, ErgoLikeTestProvingInterpreter, SigmaTestingCommons, ErgoLikeTestInterpreter} import sigmastate.helpers.TestingHelpers._ import sigmastate.serialization.ValueSerializer import sigmastate.utils.Helpers._ +import scala.collection.mutable import scala.util.Random class TestingInterpreterSpecification extends SigmaTestingCommons - with CrossVersionProps { + with CrossVersionProps with BeforeAndAfterAll { implicit lazy val IR = new TestingIRContext - lazy val prover = new ErgoLikeTestProvingInterpreter() - lazy val verifier = new ErgoLikeTestInterpreter + + lazy val processor = new PrecompiledScriptProcessor(mutable.WrappedArray.empty) { + override protected def createIR(): IRContext = new TestingIRContext + } + + lazy val prover = new ErgoLikeTestProvingInterpreter() { + override val precompiledScriptProcessor = processor + } + + lazy val verifier = new ErgoLikeTestInterpreter { + override val precompiledScriptProcessor = processor + } implicit val soundness = CryptoConstants.soundnessBits @@ -364,5 +377,10 @@ class TestingInterpreterSpecification extends SigmaTestingCommons val str = Base58.encode(ValueSerializer.serialize(ByteArrayConstant(Array[Byte](2)))) testEval(s"""deserialize[Coll[Byte]]("$str")(0) == 2""") } + + override protected def afterAll(): Unit = { + println(processor.cache.stats()) + } + } diff --git a/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala b/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala index f938417ce1..a16f0bbce0 100644 --- a/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala @@ -8,12 +8,6 @@ import special.sigma.SigmaDslTesting class PrecompiledScriptProcessorSpecification extends SigmaDslTesting { - def parseTree(hex: String): ErgoTree = { - val bytes = ErgoAlgos.decodeUnsafe(hex) - val tree = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(bytes) - tree - } - property("deserialize from hex") { predefScriptHexes.foreach { hex => val tree = parseTree(hex) @@ -31,9 +25,9 @@ class PrecompiledScriptProcessorSpecification extends SigmaDslTesting { val extraTrees = Seq(TrueTree, FalseTree) val trees = extraTrees ++ predefTrees val scripts = trees.map { t => t.bytes: Seq[Byte] } - val processor = PrecompiledScriptProcessor(scripts) + val processor = new PrecompiledScriptProcessor(scripts) trees.foreach { t => - processor.getVerifier(t).isDefined shouldBe true + processor.getReducer(t).isDefined shouldBe true } } @@ -41,7 +35,7 @@ class PrecompiledScriptProcessorSpecification extends SigmaDslTesting { val trees = Seq(TrueTree, TrueTree) val scripts = trees.map { t => t.bytes: Seq[Byte] } assertExceptionThrown( - PrecompiledScriptProcessor(scripts), + new PrecompiledScriptProcessor(scripts), { case _: IllegalArgumentException => true case _ => false } ) diff --git a/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala b/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala index 2917899a5a..8e8e1543ff 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala @@ -12,7 +12,7 @@ import scala.util.{Success, Failure, Try} import sigmastate.Values.{Constant, SValue, ConstantNode, ByteArrayConstant, IntConstant, ErgoTree} import scalan.RType import scalan.util.Extensions._ -import org.ergoplatform.dsl.{SigmaContractSyntax, TestContractSpec, ContractSpec} +import org.ergoplatform.dsl.{SigmaContractSyntax, ContractSpec, TestContractSpec} import org.ergoplatform.validation.{ValidationRules, SigmaValidationSettings} import sigmastate.{eval, SSigmaProp, SType} import SType.AnyOps @@ -28,7 +28,7 @@ import sigmastate.utils.Helpers._ import sigmastate.lang.Terms.ValueOps import sigmastate.helpers.{ErgoLikeContextTesting, SigmaPPrint} import sigmastate.helpers.TestingHelpers._ -import sigmastate.interpreter.{ProverResult, ContextExtension, ProverInterpreter} +import sigmastate.interpreter.{ProverResult, ContextExtension, ProverInterpreter, PrecompiledScriptProcessor} import sigmastate.serialization.ValueSerializer import sigmastate.serialization.generators.ObjectGenerators import sigmastate.utxo.{DeserializeContext, DeserializeRegister} @@ -122,6 +122,8 @@ class SigmaDslTesting extends PropSpec val LogScriptDefault: Boolean = false + val predefScripts = Seq[String]() + /** Descriptor of the language feature. */ trait Feature[A, B] { @@ -265,7 +267,13 @@ class SigmaDslTesting extends PropSpec val tpeB = Evaluation.rtypeToSType(oldF.tB) val prover = new FeatureProvingInterpreter() - val verifier = new ErgoLikeInterpreter()(createIR()) { type CTX = ErgoLikeContext } + val verifier = new ErgoLikeInterpreter()(createIR()) { + type CTX = ErgoLikeContext + override val precompiledScriptProcessor = + new PrecompiledScriptProcessor( + predefScripts.map { hex => parseTree(hex).bytes: Seq[Byte] } + ) + } // Create synthetic ErgoTree which uses all main capabilities of evaluation machinery. // 1) first-class functions (lambdas); 2) Context variables; 3) Registers; 4) Equality diff --git a/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala b/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala index 0317c4e842..96f9fdc296 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala @@ -1,5 +1,6 @@ package special.sigma +import org.ergoplatform.settings.ErgoAlgos import org.scalacheck.Gen.containerOfN import sigmastate.AvlTreeFlags import org.scalacheck.{Arbitrary, Gen} @@ -10,6 +11,8 @@ import org.scalacheck.util.Buildable import scalan.RType import scorex.crypto.hash.{Digest32, Blake2b256} import scorex.crypto.authds.{ADKey, ADValue} +import sigmastate.Values.ErgoTree +import sigmastate.serialization.ErgoTreeSerializer import sigmastate.serialization.generators.ObjectGenerators import special.collection.Coll @@ -113,4 +116,10 @@ trait SigmaTestingData extends SigmaTestingCommons with ObjectGenerators { "100204a00b08cd02ebaaeb381c9d855af1807781fa20ef6c0c34833275ce7913a9e4469f7bcb3becea02d192a39a8cc7a70173007301" ) + /** Parses ErgoTree instance from hex string. */ + def parseTree(hex: String): ErgoTree = { + val bytes = ErgoAlgos.decodeUnsafe(hex) + val tree = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(bytes) + tree + } } From fc04ae62b803a9956bb4b8c647aa358eb18b8229 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 1 Mar 2021 15:29:26 +0300 Subject: [PATCH 11/26] i694-box-opt: use CompiletimeIRContext in tests --- .../sigmastate/helpers/ErgoTransactionValidator.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala b/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala index 22f8bae18d..73b435738a 100644 --- a/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala +++ b/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala @@ -1,13 +1,19 @@ package sigmastate.helpers import org.ergoplatform._ -import sigmastate.eval.IRContext +import sigmastate.eval.{IRContext, CompiletimeIRContext} import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv} +import sigmastate.interpreter.PrecompiledScriptProcessor -import scala.util.{Failure, Success} +import scala.collection.mutable +import scala.util.{Success, Failure} class ErgoLikeTestInterpreter(implicit override val IR: IRContext) extends ErgoLikeInterpreter { override type CTX = ErgoLikeContext + override val precompiledScriptProcessor: PrecompiledScriptProcessor = + new PrecompiledScriptProcessor(mutable.WrappedArray.empty) { + override protected def createIR(): IRContext = new CompiletimeIRContext + } } class ErgoTransactionValidator(activatedVersion: Byte)(implicit IR: IRContext) { From 5b396d29b0719e077e57e09d608010ef6b52b7c4 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 1 Mar 2021 20:41:35 +0300 Subject: [PATCH 12/26] i694-box-opt: fixed tests (except Deserialize) --- .../sigmastate/interpreter/Interpreter.scala | 2 +- .../PrecompiledScriptProcessor.scala | 62 +++++++++++++------ .../SoftForkabilitySpecification.scala | 2 +- ...compiledScriptProcessorSpecification.scala | 10 ++- .../scala/special/sigma/SigmaDslTesting.scala | 4 +- 5 files changed, 52 insertions(+), 28 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index 947e8a48ff..d648af66df 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -180,7 +180,7 @@ trait Interpreter extends ScorexLogging { val cost = SigmaBoolean.estimateCost(sb) (sb, cost) case _ => - precompiledScriptProcessor.getReducer(ergoTree) match { + precompiledScriptProcessor.getReducer(ergoTree, context) match { case Nullable(verifier) => verifier.reduce(context) case _ => diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala index 680b34d9ff..f78253d011 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala @@ -1,10 +1,13 @@ package sigmastate.interpreter +import java.util.concurrent.ExecutionException + import com.google.common.cache.{RemovalListener, CacheBuilder, RemovalNotification, CacheLoader} import org.ergoplatform.settings.ErgoAlgos +import org.ergoplatform.validation.{ValidationRules, SigmaValidationSettings} import org.ergoplatform.validation.ValidationRules.{CheckCostFunc, CheckCalcFunc, trySoftForkable} import scalan.{AVHashMap, Nullable} -import sigmastate.TrivialProp +import sigmastate.{Values, TrivialProp} import sigmastate.Values.ErgoTree import sigmastate.eval.{RuntimeIRContext, IRContext} import sigmastate.interpreter.Interpreter.ReductionResult @@ -13,6 +16,20 @@ import sigmastate.utils.Helpers._ import scala.collection.mutable +trait ScriptReducer { + /** Reduce this pre-compiled script in the given context. + * This is equivalent to reduceToCrypto, except that graph construction is + * completely avoided. + */ + def reduce(context: InterpreterContext): ReductionResult +} + +case object WhenSoftForkReducer extends ScriptReducer { + override def reduce(context: InterpreterContext): (Values.SigmaBoolean, Long) = { + TrivialProp.TrueProp -> 0 + } +} + /** This class implements optimized reduction of the given pre-compiled script. * Pre-compilation of the necessary graphs is performed as part of constructor and * the graphs are stored in the given IR instance. @@ -24,7 +41,8 @@ import scala.collection.mutable * The code should correspond to reduceToCrypto method, but some operations may be * optimized due to assumptions above. */ -case class PrecompiledScriptReducer(scriptBytes: Seq[Byte])(implicit val IR: IRContext) { +case class PrecompiledScriptReducer(scriptBytes: Seq[Byte])(implicit val IR: IRContext) + extends ScriptReducer { /** The following operations create [[RCostingResultEx]] structure for the given * `scriptBytes` and they should be the same as in `reduceToCrypto` method. @@ -66,12 +84,13 @@ case class PrecompiledScriptReducer(scriptBytes: Seq[Byte])(implicit val IR: IRC } } +case class CacheKey(scriptBytes: Seq[Byte], vs: SigmaValidationSettings) + + /** Script processor which holds pre-compiled reducers for the given scripts. * @param predefScripts collection of scripts to ALWAYS pre-compile (each given by ErgoTree bytes) */ -class PrecompiledScriptProcessor(val predefScripts: Seq[Seq[Byte]]) { - /** Convenience synonym */ - type PSR = PrecompiledScriptReducer +class PrecompiledScriptProcessor(val predefScripts: Seq[CacheKey]) { /** Creates a new instance of IRContex to be used in reducers. * The default implementation can be overriden in derived classes. @@ -81,19 +100,19 @@ class PrecompiledScriptProcessor(val predefScripts: Seq[Seq[Byte]]) { /** Holds for each ErgoTree bytes the corresponding pre-compiled reducer. */ val reducers = { implicit val IR: IRContext = createIR() - val res = AVHashMap[Seq[Byte], PSR](predefScripts.length) + val res = AVHashMap[CacheKey, ScriptReducer](predefScripts.length) predefScripts.foreach { s => - val r = PrecompiledScriptReducer(s) + val r = PrecompiledScriptReducer(s.scriptBytes) val old = res.put(s, r) - require(old == null, s"duplicate predefined script: '${ErgoAlgos.encode(s.toArray)}'") + require(old == null, s"duplicate predefined script: '${ErgoAlgos.encode(s.scriptBytes.toArray)}'") } res } - private val CacheListener = new RemovalListener[Seq[Byte], PSR]() { - override def onRemoval(notification: RemovalNotification[Seq[Byte], PSR]): Unit = { + private val CacheListener = new RemovalListener[CacheKey, ScriptReducer]() { + override def onRemoval(notification: RemovalNotification[CacheKey, ScriptReducer]): Unit = { if (notification.wasEvicted()) { - val scriptHex = ErgoAlgos.encode(notification.getKey.toArray) + val scriptHex = ErgoAlgos.encode(notification.getKey.scriptBytes.toArray) println(s"Evicted: ${scriptHex}") } } @@ -104,9 +123,11 @@ class PrecompiledScriptProcessor(val predefScripts: Seq[Seq[Byte]]) { .maximumSize(1000) .removalListener(CacheListener) .recordStats() - .build(new CacheLoader[Seq[Byte], PSR]() { - override def load(key: Seq[Byte]): PSR = { - PrecompiledScriptReducer(scriptBytes = key)(createIR()) + .build(new CacheLoader[CacheKey, ScriptReducer]() { + override def load(key: CacheKey): ScriptReducer = { + trySoftForkable[ScriptReducer](whenSoftFork = WhenSoftForkReducer) { + PrecompiledScriptReducer(key.scriptBytes)(createIR()) + }(key.vs) } }) } @@ -116,11 +137,16 @@ class PrecompiledScriptProcessor(val predefScripts: Seq[Seq[Byte]]) { * @return non-empty Nullable instance with verifier for the given tree, otherwise * Nullable.None */ - def getReducer(ergoTree: ErgoTree): Nullable[PSR] = { - val key: Seq[Byte] = ergoTree.bytes + def getReducer(ergoTree: ErgoTree, context: InterpreterContext): Nullable[ScriptReducer] = { + val key = CacheKey(ergoTree.bytes, context.validationSettings) reducers.get(key) match { case Nullable.None => - val verifier = cache.get(key) + val verifier = try { + cache.get(key) + } catch { + case e: ExecutionException => + throw e.getCause + } Nullable(verifier) case v => v } @@ -128,5 +154,5 @@ class PrecompiledScriptProcessor(val predefScripts: Seq[Seq[Byte]]) { } object PrecompiledScriptProcessor { - val Default = new PrecompiledScriptProcessor(mutable.WrappedArray.empty[Seq[Byte]]) + val Default = new PrecompiledScriptProcessor(mutable.WrappedArray.empty[CacheKey]) } diff --git a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala index f830fbb2cd..0c64ccbdf1 100644 --- a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala @@ -256,7 +256,7 @@ class SoftForkabilitySpecification extends SigmaTestingData { ValueSerializer.serialize(booleanPropV2) } - // v1 main script which deserializes from context v2 script + // v1 main script which deserializes v2 script from context val mainProp = BinAnd(GT(Height, IntConstant(deadline)), DeserializeContext(1, SBoolean)).toSigmaProp val mainTree = ErgoTree.fromProposition( headerFlags = ErgoTree.headerWithVersion(0), // ErgoTree v0 diff --git a/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala b/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala index a16f0bbce0..116538000b 100644 --- a/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala @@ -1,8 +1,6 @@ package sigmastate.interpreter -import sigmastate.serialization.ErgoTreeSerializer -import org.ergoplatform.settings.ErgoAlgos -import sigmastate.Values.ErgoTree +import org.ergoplatform.validation.ValidationRules import sigmastate.helpers.SigmaPPrint import special.sigma.SigmaDslTesting @@ -24,16 +22,16 @@ class PrecompiledScriptProcessorSpecification extends SigmaDslTesting { val predefTrees = predefScriptHexes.map { h => parseTree(h) } val extraTrees = Seq(TrueTree, FalseTree) val trees = extraTrees ++ predefTrees - val scripts = trees.map { t => t.bytes: Seq[Byte] } + val scripts = trees.map { t => CacheKey(t.bytes, ValidationRules.currentSettings) } val processor = new PrecompiledScriptProcessor(scripts) trees.foreach { t => - processor.getReducer(t).isDefined shouldBe true + processor.getReducer(t, fakeContext).isDefined shouldBe true } } property("rejects duplicates") { val trees = Seq(TrueTree, TrueTree) - val scripts = trees.map { t => t.bytes: Seq[Byte] } + val scripts = trees.map { t => CacheKey(t.bytes, ValidationRules.currentSettings) } assertExceptionThrown( new PrecompiledScriptProcessor(scripts), { case _: IllegalArgumentException => true diff --git a/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala b/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala index 8e8e1543ff..a24c690694 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala @@ -28,7 +28,7 @@ import sigmastate.utils.Helpers._ import sigmastate.lang.Terms.ValueOps import sigmastate.helpers.{ErgoLikeContextTesting, SigmaPPrint} import sigmastate.helpers.TestingHelpers._ -import sigmastate.interpreter.{ProverResult, ContextExtension, ProverInterpreter, PrecompiledScriptProcessor} +import sigmastate.interpreter.{ProverResult, PrecompiledScriptProcessor, ContextExtension, ProverInterpreter, CacheKey} import sigmastate.serialization.ValueSerializer import sigmastate.serialization.generators.ObjectGenerators import sigmastate.utxo.{DeserializeContext, DeserializeRegister} @@ -271,7 +271,7 @@ class SigmaDslTesting extends PropSpec type CTX = ErgoLikeContext override val precompiledScriptProcessor = new PrecompiledScriptProcessor( - predefScripts.map { hex => parseTree(hex).bytes: Seq[Byte] } + predefScripts.map { hex => CacheKey(parseTree(hex).bytes, ValidationRules.currentSettings) } ) } From 5dd70f2c46c486427ca158c92da3b3025a38f9b6 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 2 Mar 2021 01:52:34 +0300 Subject: [PATCH 13/26] i694-box-opt: ErgoTree.hasDeserialize property added --- .../src/main/scala/sigmastate/Values.scala | 39 +++++++++++++++++-- .../serialization/ErgoTreeSerializer.scala | 12 +++++- .../sigmastate/utils/SigmaByteReader.scala | 6 +++ 3 files changed, 51 insertions(+), 6 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index 74036b9280..027e2ffa4a 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -5,7 +5,7 @@ import java.util import java.util.Objects import org.bitbucket.inkytonik.kiama.relation.Tree -import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{strategy, everywherebu} +import org.bitbucket.inkytonik.kiama.rewriting.Rewriter.{strategy, everywherebu, count} import org.ergoplatform.validation.ValidationException import scalan.{Nullable, RType} import scalan.util.CollectionUtil._ @@ -120,6 +120,15 @@ object Values { /** Immutable empty Seq of values. Can be used to avoid allocation. */ val EmptySeq: IndexedSeq[SValue] = EmptyArray + + def hasDeserialize(exp: SValue): Boolean = { + val deserializeNode: PartialFunction[Any, Int] = { + case _: DeserializeContext[_] => 1 + case _: DeserializeRegister[_] => 1 + } + val c = count(deserializeNode)(exp) + c > 0 + } } trait ValueCompanion extends SigmaNodeCompanion { @@ -982,13 +991,20 @@ object Values { constants: IndexedSeq[Constant[SType]], root: Either[UnparsedErgoTree, SigmaPropValue], private val givenComplexity: Int, - private val propositionBytes: Array[Byte] + private val propositionBytes: Array[Byte], + private val givenDeserialize: Option[Boolean] ) { def this(header: Byte, constants: IndexedSeq[Constant[SType]], root: Either[UnparsedErgoTree, SigmaPropValue]) = - this(header, constants, root, 0, DefaultSerializer.serializeErgoTree(ErgoTree(header, constants, root, 0, null))) + this( + header, constants, root, 0, + propositionBytes = DefaultSerializer.serializeErgoTree( + ErgoTree(header, constants, root, 0, null, None) + ), + givenDeserialize = None + ) require(isConstantSegregation || constants.isEmpty) require(version == 0 || hasSize, s"For newer version the size bit is required: $this") @@ -1026,7 +1042,22 @@ object Values { _complexity } - /** Serialized proposition expression of SigmaProp type with + private[sigmastate] var _hasDeserialize: Option[Boolean] = givenDeserialize + + /** Structural complexity estimation of this tree. + * @see ComplexityTable + */ + lazy val hasDeserialize: Boolean = { + if (_hasDeserialize.isEmpty) { + _hasDeserialize = Some(root match { + case Right(p) => Value.hasDeserialize(p) + case _ => false + }) + } + _hasDeserialize.get + } + + /** Serialized proposition expression of SigmaProp type with * ConstantPlaceholder nodes instead of Constant nodes */ lazy val template: Array[Byte] = { diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala index ad4401dd7f..c562160537 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala @@ -151,7 +151,13 @@ class ErgoTreeSerializer { val previousConstantStore = r.constantStore // reader with constant store attached is required (to get tpe for a constant placeholder) r.constantStore = new ConstantStore(cs) + + val wasDeserialize_saved = r.wasDeserialize + r.wasDeserialize = false + val root = ValueSerializer.deserialize(r) + val hasDeserialize = r.wasDeserialize + r.wasDeserialize = wasDeserialize_saved if (checkType) { CheckDeserializedScriptIsSigmaProp(root) @@ -165,7 +171,9 @@ class ErgoTreeSerializer { r.position = startPos val propositionBytes = r.getBytes(treeSize) - new ErgoTree(h, cs, Right(root.asSigmaProp), complexity, propositionBytes) + new ErgoTree( + h, cs, Right(root.asSigmaProp), complexity, + propositionBytes, Some(hasDeserialize)) } catch { case e: InputSizeLimitExceeded => @@ -180,7 +188,7 @@ class ErgoTreeSerializer { r.position = startPos val bytes = r.getBytes(numBytes) val complexity = ComplexityTable.OpCodeComplexity(Constant.opCode) - new ErgoTree(ErgoTree.DefaultHeader, EmptyConstants, Left(UnparsedErgoTree(bytes, ve)), complexity, bytes) + new ErgoTree(ErgoTree.DefaultHeader, EmptyConstants, Left(UnparsedErgoTree(bytes, ve)), complexity, bytes, None) case None => throw new SerializerException( s"Cannot handle ValidationException, ErgoTree serialized without size bit.", None, Some(ve)) diff --git a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala index c736dc1d0a..0260597dc2 100644 --- a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala +++ b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala @@ -162,4 +162,10 @@ class SigmaByteReader(val r: Reader, @inline final def addComplexity(delta: Int): Unit = { _complexity += delta } + + private var _wasDeserialize: Boolean = false + @inline final def wasDeserialize: Boolean = _wasDeserialize + @inline final def wasDeserialize_=(v: Boolean): Unit = { + _wasDeserialize = v + } } From 5e54f107802a25f6d58dc4c7074a57cb65b08362 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 2 Mar 2021 01:53:11 +0300 Subject: [PATCH 14/26] i694-box-opt: Value.hasDeserialize method tests --- .../sigmastate/ErgoTreeSpecification.scala | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/sigmastate/src/test/scala/sigmastate/ErgoTreeSpecification.scala b/sigmastate/src/test/scala/sigmastate/ErgoTreeSpecification.scala index 0d22c7400c..49460488ed 100644 --- a/sigmastate/src/test/scala/sigmastate/ErgoTreeSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/ErgoTreeSpecification.scala @@ -1,5 +1,6 @@ package sigmastate +import org.ergoplatform.ErgoBox import org.ergoplatform.settings.ErgoAlgos import org.ergoplatform.validation.{ValidationException, ValidationRules} import scalan.Nullable @@ -8,6 +9,7 @@ import sigmastate.lang.SourceContext import special.sigma.SigmaTestingData import sigmastate.lang.Terms._ import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer +import sigmastate.utxo.{DeserializeContext, DeserializeRegister} /** Regression tests with ErgoTree related test vectors. * This test vectors verify various constants which are consensus critical and should not change. @@ -80,6 +82,44 @@ class ErgoTreeSpecification extends SigmaTestingData { t._bytes shouldBe expectedBytes } + property("Value.hasDeserialize") { + val const = IntConstant(10) + Value.hasDeserialize(const) shouldBe false + val dc = DeserializeContext(1.toByte, SInt) + Value.hasDeserialize(dc) shouldBe true + val dr = DeserializeRegister(ErgoBox.R4, SInt) + Value.hasDeserialize(dr) shouldBe true + Value.hasDeserialize(EQ(const, dc)) shouldBe true + Value.hasDeserialize(Plus(Plus(const, dc), dr)) shouldBe true + val t = new ErgoTree( + 16.toByte, + Array(IntConstant(1)), + Right(BoolToSigmaProp(EQ(ConstantPlaceholder(0, SInt), IntConstant(1)))) + ) + Value.hasDeserialize(t.toProposition(replaceConstants = true)) shouldBe false + } + + property("ErgoTree.hasDeserialize") { + { + val t = new ErgoTree( + 0.toByte, + Array[Constant[SType]](), + Right(TrueSigmaProp)) + t._hasDeserialize shouldBe None + t.hasDeserialize shouldBe false + } + + { + val t = new ErgoTree( + 16.toByte, + Array(IntConstant(1)), + Right(BoolToSigmaProp(EQ(ConstantPlaceholder(0, SInt), DeserializeContext(1.toByte, SInt)))) + ) + t._hasDeserialize shouldBe None + t.hasDeserialize shouldBe true + } + } + property("ErgoTree equality") { val t1 = new ErgoTree( 16.toByte, From 023f5e6e6375cab2368b911a714fcb204ef06ec2 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 2 Mar 2021 02:46:51 +0300 Subject: [PATCH 15/26] i694-box-opt: unit tests for ErgoTree.hasDeserialize method --- .../DeserializeContextSerializer.scala | 1 + .../DeserializeRegisterSerializer.scala | 1 + .../ErgoTreeSerializerSpecification.scala | 32 ++++++++++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/sigmastate/src/main/scala/sigmastate/serialization/transformers/DeserializeContextSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/transformers/DeserializeContextSerializer.scala index e172028221..f2fe92434d 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/transformers/DeserializeContextSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/transformers/DeserializeContextSerializer.scala @@ -21,6 +21,7 @@ case class DeserializeContextSerializer(cons: (Byte, SType) => Value[SType]) override def parse(r: SigmaByteReader): Value[SType] = { val tpe = r.getType() val id = r.getByte() + r.wasDeserialize ||= true // mark the flag cons(id, tpe) } } diff --git a/sigmastate/src/main/scala/sigmastate/serialization/transformers/DeserializeRegisterSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/transformers/DeserializeRegisterSerializer.scala index 9925d3eeed..47c1ead8e4 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/transformers/DeserializeRegisterSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/transformers/DeserializeRegisterSerializer.scala @@ -28,6 +28,7 @@ case class DeserializeRegisterSerializer(cons: (RegisterId, SType, Option[Value[ val registerId = ErgoBox.findRegisterByIndex(r.getByte()).get val tpe = r.getType() val dv = r.getOption(r.getValue()) + r.wasDeserialize ||= true // mark the flag cons(registerId, tpe, dv) } diff --git a/sigmastate/src/test/scala/sigmastate/serialization/ErgoTreeSerializerSpecification.scala b/sigmastate/src/test/scala/sigmastate/serialization/ErgoTreeSerializerSpecification.scala index 4dd1cb21d4..2d51e541c7 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/ErgoTreeSerializerSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/ErgoTreeSerializerSpecification.scala @@ -2,12 +2,14 @@ package sigmastate.serialization import java.math.BigInteger -import sigmastate.Values.{ShortConstant, BigIntConstant, SigmaPropValue, IntConstant, ErgoTree, ByteConstant} +import org.ergoplatform.ErgoBox +import sigmastate.Values.{ShortConstant, BigIntConstant, ConstantPlaceholder, Value, SigmaPropValue, IntConstant, ErgoTree, ByteConstant} import sigmastate._ import sigmastate.eval.{IRContext, CBigInt} import sigmastate.helpers.SigmaTestingCommons import sigmastate.lang.exceptions.{SerializerException, InputSizeLimitExceeded} import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer +import sigmastate.utxo.{DeserializeContext, DeserializeRegister} class ErgoTreeSerializerSpecification extends SerializationSpecification with SigmaTestingCommons { @@ -115,4 +117,32 @@ class ErgoTreeSerializerSpecification extends SerializationSpecification DefaultSerializer.deserializeErgoTree(r, SigmaSerializer.MaxPropositionSize) shouldEqual tree r.positionLimit shouldBe 1 } + + property("should compute hasDeserialize during parsing") { + val const = IntConstant(10) + val dc = DeserializeContext(1.toByte, SInt) + val dr = DeserializeRegister(ErgoBox.R4, SInt) + + val samples = Table(("exp", "hasDeserialize"), + const -> false, + dc -> true, + dr -> true, + Plus(Plus(const, dc), dr) -> true, + Plus(Plus(const, const), const) -> false + ) + + forAll(samples) { (exp, hasDeserialize) => + val t = new ErgoTree( + 16.toByte, + Array(IntConstant(1)), + Right(BoolToSigmaProp(EQ(ConstantPlaceholder(0, SInt), exp))) + ) + t._hasDeserialize shouldBe None + + val parsedTree = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(t.bytes) + parsedTree shouldBe t + parsedTree._hasDeserialize.isDefined shouldBe true + parsedTree.hasDeserialize shouldBe hasDeserialize + } + } } From 91df69419ca7f5d018cb1b2bead3d95e171ba832 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 2 Mar 2021 13:56:50 +0300 Subject: [PATCH 16/26] i694-box-opt: make all tests pass --- .../sigmastate/interpreter/Interpreter.scala | 33 ++++++++++++------- .../PrecompiledScriptProcessor.scala | 20 +++++++---- .../SoftForkabilitySpecification.scala | 12 +++++-- .../helpers/ErgoTransactionValidator.scala | 10 +++--- ...compiledScriptProcessorSpecification.scala | 2 +- .../sigmastate/utxo/examples/IcoExample.scala | 11 +++++-- 6 files changed, 57 insertions(+), 31 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index d648af66df..f05843911c 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -36,6 +36,10 @@ trait Interpreter extends ScorexLogging { val IR: IRContext import IR._ + /** Processor instance which is used by this interpreter to execute ErgoTrees that + * contain neither [[DeserializeContext]] nor [[sigmastate.utxo.DeserializeRegister]] + * operations. + */ def precompiledScriptProcessor: PrecompiledScriptProcessor /** Deserializes given script bytes using ValueSerializer (i.e. assuming expression tree format). @@ -172,27 +176,32 @@ trait Interpreter extends ScorexLogging { def fullReduction(ergoTree: ErgoTree, context: CTX, env: ScriptEnv): (SigmaBoolean, Long) = { - implicit val vs: SigmaValidationSettings = context.validationSettings val prop = propositionFromErgoTree(ergoTree, context) prop match { case SigmaPropConstant(p) => val sb = SigmaDsl.toSigmaBoolean(p) val cost = SigmaBoolean.estimateCost(sb) (sb, cost) + case _ if !ergoTree.hasDeserialize => + val r = precompiledScriptProcessor.getReducer(ergoTree, context.validationSettings) + r.reduce(context) case _ => - precompiledScriptProcessor.getReducer(ergoTree, context) match { - case Nullable(verifier) => - verifier.reduce(context) - case _ => - val (propTree, context2) = trySoftForkable[(BoolValue, CTX)](whenSoftFork = (TrueLeaf, context)) { - applyDeserializeContext(context, prop) - } + reductionWithDeserialize(prop, context, env) + } + } - // here we assume that when `propTree` is TrueProp then `reduceToCrypto` always succeeds - // and the rest of the verification is also trivial - reduceToCrypto(context2, env, propTree).getOrThrow - } + /** Perfroms reduction of proposition which contains deserialization operations. */ + private def reductionWithDeserialize(prop: SigmaPropValue, + context: CTX, + env: ScriptEnv) = { + implicit val vs: SigmaValidationSettings = context.validationSettings + val (propTree, context2) = trySoftForkable[(BoolValue, CTX)](whenSoftFork = (TrueLeaf, context)) { + applyDeserializeContext(context, prop) } + + // here we assume that when `propTree` is TrueProp then `reduceToCrypto` always succeeds + // and the rest of the verification is also trivial + reduceToCrypto(context2, env, propTree).getOrThrow } /** Executes the script in a given context. diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala index f78253d011..000d198921 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala @@ -9,7 +9,7 @@ import org.ergoplatform.validation.ValidationRules.{CheckCostFunc, CheckCalcFunc import scalan.{AVHashMap, Nullable} import sigmastate.{Values, TrivialProp} import sigmastate.Values.ErgoTree -import sigmastate.eval.{RuntimeIRContext, IRContext} +import sigmastate.eval.{RuntimeIRContext, IRContext, CompiletimeIRContext} import sigmastate.interpreter.Interpreter.ReductionResult import sigmastate.serialization.ErgoTreeSerializer import sigmastate.utils.Helpers._ @@ -137,22 +137,28 @@ class PrecompiledScriptProcessor(val predefScripts: Seq[CacheKey]) { * @return non-empty Nullable instance with verifier for the given tree, otherwise * Nullable.None */ - def getReducer(ergoTree: ErgoTree, context: InterpreterContext): Nullable[ScriptReducer] = { - val key = CacheKey(ergoTree.bytes, context.validationSettings) + def getReducer(ergoTree: ErgoTree, vs: SigmaValidationSettings): ScriptReducer = { + val key = CacheKey(ergoTree.bytes, vs) reducers.get(key) match { - case Nullable.None => - val verifier = try { + case Nullable(r) => r + case _ => + val r = try { cache.get(key) } catch { case e: ExecutionException => throw e.getCause } - Nullable(verifier) - case v => v + r } } } object PrecompiledScriptProcessor { + /** Default script processor which uses [[RuntimeIRContext]] to process graphs. */ val Default = new PrecompiledScriptProcessor(mutable.WrappedArray.empty[CacheKey]) + + /** Script processor which uses [[CompiletimeIRContext]] to process graphs. */ + val WithCompiletimeIRContext = new PrecompiledScriptProcessor(mutable.WrappedArray.empty) { + override protected def createIR(): IRContext = new CompiletimeIRContext + } } diff --git a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala index 0c64ccbdf1..2db655f1ae 100644 --- a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala @@ -3,6 +3,7 @@ package sigmastate import org.ergoplatform._ import org.ergoplatform.validation.ValidationRules.{CheckValidOpCode, trySoftForkable, CheckCostFuncOperation, CheckTupleType, CheckCostFunc, CheckDeserializedScriptIsSigmaProp, _} import org.ergoplatform.validation._ +import org.scalatest.BeforeAndAfterAll import sigmastate.SPrimType.MaxPrimTypeCode import sigmastate.Values.ErgoTree.EmptyConstants import sigmastate.Values.{UnparsedErgoTree, NotReadyValueInt, ByteArrayConstant, Tuple, IntConstant, ErgoTree, ValueCompanion} @@ -10,16 +11,16 @@ import sigmastate.eval.Colls import sigmastate.helpers.{ErgoLikeContextTesting, ErgoLikeTestProvingInterpreter, ErgoLikeTestInterpreter} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv} -import sigmastate.interpreter.{ProverResult, ContextExtension} +import sigmastate.interpreter.{ProverResult, ContextExtension, PrecompiledScriptProcessor} import sigmastate.lang.Terms._ -import sigmastate.lang.exceptions.{SerializerException, SigmaException, CosterException, InterpreterException} +import sigmastate.lang.exceptions.{SerializerException, SigmaException, InterpreterException, CosterException} import sigmastate.serialization.OpCodes.{OpCodeExtra, LastConstantCode, OpCode} import sigmastate.serialization._ import sigmastate.utxo.{DeserializeContext, SelectField} import special.sigma.SigmaTestingData import sigmastate.utils.Helpers._ -class SoftForkabilitySpecification extends SigmaTestingData { +class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterAll { implicit lazy val IR = new TestingIRContext lazy val prover = new ErgoLikeTestProvingInterpreter() @@ -371,4 +372,9 @@ class SoftForkabilitySpecification extends SigmaTestingData { CheckCostFunc(tIR)(asRep[Any => Int](costF)) }) } + + override protected def afterAll(): Unit = { + println(PrecompiledScriptProcessor.WithCompiletimeIRContext.cache.stats()) + } + } diff --git a/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala b/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala index 73b435738a..415a105d7a 100644 --- a/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala +++ b/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala @@ -1,19 +1,17 @@ package sigmastate.helpers import org.ergoplatform._ -import sigmastate.eval.{IRContext, CompiletimeIRContext} +import sigmastate.eval.IRContext import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv} import sigmastate.interpreter.PrecompiledScriptProcessor -import scala.collection.mutable import scala.util.{Success, Failure} +/** Base class for interpreters used in tests. + * @see derived classes */ class ErgoLikeTestInterpreter(implicit override val IR: IRContext) extends ErgoLikeInterpreter { override type CTX = ErgoLikeContext - override val precompiledScriptProcessor: PrecompiledScriptProcessor = - new PrecompiledScriptProcessor(mutable.WrappedArray.empty) { - override protected def createIR(): IRContext = new CompiletimeIRContext - } + override val precompiledScriptProcessor = PrecompiledScriptProcessor.WithCompiletimeIRContext } class ErgoTransactionValidator(activatedVersion: Byte)(implicit IR: IRContext) { diff --git a/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala b/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala index 116538000b..1872077fe7 100644 --- a/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala @@ -25,7 +25,7 @@ class PrecompiledScriptProcessorSpecification extends SigmaDslTesting { val scripts = trees.map { t => CacheKey(t.bytes, ValidationRules.currentSettings) } val processor = new PrecompiledScriptProcessor(scripts) trees.foreach { t => - processor.getReducer(t, fakeContext).isDefined shouldBe true + processor.getReducer(t, ValidationRules.currentSettings) should not be null } } diff --git a/sigmastate/src/test/scala/sigmastate/utxo/examples/IcoExample.scala b/sigmastate/src/test/scala/sigmastate/utxo/examples/IcoExample.scala index 3774fce497..5132b8aa68 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/examples/IcoExample.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/examples/IcoExample.scala @@ -16,8 +16,9 @@ import sigmastate.interpreter.Interpreter.ScriptNameProp import sigmastate.lang.Terms._ import sigmastate.serialization.ErgoTreeSerializer import ErgoTreeSerializer.DefaultSerializer +import org.scalatest.BeforeAndAfterAll import sigmastate.eval.{CompiletimeCosting, IRContext} -import sigmastate.interpreter.CryptoConstants +import sigmastate.interpreter.{CryptoConstants, PrecompiledScriptProcessor} import scala.util.Random import sigmastate.eval._ @@ -233,7 +234,7 @@ reasonable to have an additional input from the project with the value equal to */ class IcoExample extends SigmaTestingCommons - with CrossVersionProps { suite => + with CrossVersionProps with BeforeAndAfterAll { suite => // Not mixed with TestContext since it is not possible to call commpiler.compile outside tests if mixed implicit lazy val IR: IRContext = new IRContext with CompiletimeCosting @@ -553,4 +554,10 @@ class IcoExample extends SigmaTestingCommons println(ComplexityTableStat.complexityTableString) } + /** This is the last executed test suite, so this method is executed after all tests. + * We output statistics of how PrecompiledScriptProcessor cache was used. */ + override protected def afterAll(): Unit = { + println(PrecompiledScriptProcessor.WithCompiletimeIRContext.cache.stats()) + } + } From 3ea32e41ff1dd6814d301b98ac433fec308009d2 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 2 Mar 2021 17:08:46 +0300 Subject: [PATCH 17/26] i694-box-opt: missing ScalaDocs --- .../ergoplatform/ErgoLikeInterpreter.scala | 4 +++ .../ergoplatform/ErgoLikeTransaction.scala | 2 +- .../src/main/scala/sigmastate/Values.scala | 14 ++++++++--- .../sigmastate/interpreter/Interpreter.scala | 12 +++++++-- .../PrecompiledScriptProcessor.scala | 25 +++++++++++++++---- .../serialization/ErgoTreeSerializer.scala | 2 +- .../sigmastate/utils/SigmaByteReader.scala | 2 ++ .../helpers/ErgoTransactionValidator.scala | 2 +- .../scala/special/sigma/SigmaDslTesting.scala | 4 --- .../special/sigma/SigmaTestingData.scala | 1 + 10 files changed, 51 insertions(+), 17 deletions(-) diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala index ad3410ee08..1ce5bede46 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeInterpreter.scala @@ -7,6 +7,10 @@ import sigmastate.interpreter.{Interpreter, PrecompiledScriptProcessor} import sigmastate.utxo._ +/** Base class of verifying interpreter which expects ErgoLikeContext as input of + * verify method. + * It implements deserialization of register of SELF box. + */ class ErgoLikeInterpreter(implicit val IR: IRContext) extends Interpreter { override type CTX <: ErgoLikeContext diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala index 9ea01394c0..c89d076f18 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala @@ -3,7 +3,7 @@ package org.ergoplatform import org.ergoplatform.ErgoBox.TokenId import scalan.Nullable import scorex.crypto.authds.ADKey -import scorex.crypto.hash.{Digest32, Blake2b256} +import scorex.crypto.hash.{Blake2b256, Digest32} import scorex.util._ import sigmastate.SType._ import sigmastate.eval.Extensions._ diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index 027e2ffa4a..d2c220f3eb 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -121,6 +121,8 @@ object Values { /** Immutable empty Seq of values. Can be used to avoid allocation. */ val EmptySeq: IndexedSeq[SValue] = EmptyArray + /** Traverses the given expression tree and counts the number of deserialization + * operations. If it it non zero, returns true. */ def hasDeserialize(exp: SValue): Boolean = { val deserializeNode: PartialFunction[Any, Int] = { case _: DeserializeContext[_] => 1 @@ -984,7 +986,13 @@ object Values { * These bytes are obtained in two ways: * 1) in the ErgoTreeSerializer from Reader * 2) in the alternative constructor using ErgoTreeSerializer.serializeErgoTree - * + * @param givenDeserialize optional flag, which contains information about presence of + * deserialization operations in the tree. If it is None, the information is not + * available. If Some(true) then there are deserialization operations, otherwise + * the tree doesn't contain deserialization and is eligible + * for optimized execution. + * ErgoTreeSerializer parsing method computes the value of + * this flag and provides it to the constructor. */ case class ErgoTree private[sigmastate]( header: Byte, @@ -1044,8 +1052,8 @@ object Values { private[sigmastate] var _hasDeserialize: Option[Boolean] = givenDeserialize - /** Structural complexity estimation of this tree. - * @see ComplexityTable + /** Returns true if the tree contains at least one deserialization operation, + * false otherwise. */ lazy val hasDeserialize: Boolean = { if (_hasDeserialize.isEmpty) { diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index f05843911c..00de22fe7c 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -377,10 +377,10 @@ object Interpreter { * The second component is the estimated cost of contract execution. */ type VerificationResult = (Boolean, Long) - /** Result of ErgoTree reduction procedure (see `reduceToCrypto`). + /** Result of ErgoTree reduction procedure (see `reduceToCrypto` and friends). * The first component is the value of SigmaProp type which represents a statement * verifiable via sigma protocol. - * The second component is the estimated cost of contract execution */ + * The second component is the estimated cost of consumed by the contract execution. */ type ReductionResult = (SigmaBoolean, Long) type ScriptEnv = Map[String, Any] @@ -398,6 +398,12 @@ object Interpreter { */ val MaxSupportedScriptVersion: Byte = 1 // supported versions 0 and 1 + /** Executes the given `calcF` graph in the given context. + * @param IR containier of the graph (see [[IRContext]]) + * @param context script execution context (built from [[org.ergoplatform.ErgoLikeContext]]) + * @param calcF graph which represents a reduction function from Context to SigmaProp. + * @return a reduction result + */ def calcResult(IR: IRContext) (context: special.sigma.Context, calcF: IR.Ref[IR.Context => Any]): special.sigma.SigmaProp = { @@ -422,6 +428,8 @@ object Interpreter { res } + /** Special helper function which converts the given expression to expression returning + * boolean or throws an exception if the conversion is not defined. */ def toValidScriptType(exp: SValue): BoolValue = exp match { case v: Value[SBoolean.type]@unchecked if v.tpe == SBoolean => v case p: SValue if p.tpe == SSigmaProp => p.asSigmaProp.isProven diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala index 000d198921..f60e82c855 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala @@ -16,6 +16,11 @@ import sigmastate.utils.Helpers._ import scala.collection.mutable +/** A reducer which represents precompiled script reduction function. + * The function takes script execution context and produces the [[ReductionResult]], + * which contains both sigma proposition and the approximation of the cost taken by the + * reduction. + */ trait ScriptReducer { /** Reduce this pre-compiled script in the given context. * This is equivalent to reduceToCrypto, except that graph construction is @@ -24,6 +29,7 @@ trait ScriptReducer { def reduce(context: InterpreterContext): ReductionResult } +/** Used as a fallback reducer when precompilation failed due to soft-fork condition. */ case object WhenSoftForkReducer extends ScriptReducer { override def reduce(context: InterpreterContext): (Values.SigmaBoolean, Long) = { TrivialProp.TrueProp -> 0 @@ -84,7 +90,14 @@ case class PrecompiledScriptReducer(scriptBytes: Seq[Byte])(implicit val IR: IRC } } -case class CacheKey(scriptBytes: Seq[Byte], vs: SigmaValidationSettings) +/** Represents keys in the cache of precompiled ErgoTrees for repeated evaluation. + * Note, [[SigmaValidationSettings]] are part of the key, which is important, because + * the output of compilation depends on validation settings. + * + * @param ergoTreeBytes serialized bytes of ErgoTree instance (returned by ErgoTreeSerializer) + * @param vs validation settings which where used for soft-forkable compilation. + */ +case class CacheKey(ergoTreeBytes: Seq[Byte], vs: SigmaValidationSettings) /** Script processor which holds pre-compiled reducers for the given scripts. @@ -102,22 +115,24 @@ class PrecompiledScriptProcessor(val predefScripts: Seq[CacheKey]) { implicit val IR: IRContext = createIR() val res = AVHashMap[CacheKey, ScriptReducer](predefScripts.length) predefScripts.foreach { s => - val r = PrecompiledScriptReducer(s.scriptBytes) + val r = PrecompiledScriptReducer(s.ergoTreeBytes) val old = res.put(s, r) - require(old == null, s"duplicate predefined script: '${ErgoAlgos.encode(s.scriptBytes.toArray)}'") + require(old == null, s"duplicate predefined script: '${ErgoAlgos.encode(s.ergoTreeBytes.toArray)}'") } res } + /** Called when a cache item is removed. */ private val CacheListener = new RemovalListener[CacheKey, ScriptReducer]() { override def onRemoval(notification: RemovalNotification[CacheKey, ScriptReducer]): Unit = { if (notification.wasEvicted()) { - val scriptHex = ErgoAlgos.encode(notification.getKey.scriptBytes.toArray) + val scriptHex = ErgoAlgos.encode(notification.getKey.ergoTreeBytes.toArray) println(s"Evicted: ${scriptHex}") } } } + /** The cache which stores MRU set of pre-compiled reducers. */ val cache = { CacheBuilder.newBuilder .maximumSize(1000) @@ -126,7 +141,7 @@ class PrecompiledScriptProcessor(val predefScripts: Seq[CacheKey]) { .build(new CacheLoader[CacheKey, ScriptReducer]() { override def load(key: CacheKey): ScriptReducer = { trySoftForkable[ScriptReducer](whenSoftFork = WhenSoftForkReducer) { - PrecompiledScriptReducer(key.scriptBytes)(createIR()) + PrecompiledScriptReducer(key.ergoTreeBytes)(createIR()) }(key.vs) } }) diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala index c562160537..1b17c767e4 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala @@ -156,7 +156,7 @@ class ErgoTreeSerializer { r.wasDeserialize = false val root = ValueSerializer.deserialize(r) - val hasDeserialize = r.wasDeserialize + val hasDeserialize = r.wasDeserialize // == true if there was deserialization node r.wasDeserialize = wasDeserialize_saved if (checkType) { diff --git a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala index 0260597dc2..d44f038f1f 100644 --- a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala +++ b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala @@ -154,6 +154,7 @@ class SigmaByteReader(val r: Reader, } private var _complexity: Int = 0 + /** Helper property which is used to accumulate complexity during parsing. */ @inline final def complexity: Int = _complexity @inline final def complexity_=(v: Int): Unit = { _complexity = v @@ -164,6 +165,7 @@ class SigmaByteReader(val r: Reader, } private var _wasDeserialize: Boolean = false + /** Helper property which is used to track deserialization operations during parsing. */ @inline final def wasDeserialize: Boolean = _wasDeserialize @inline final def wasDeserialize_=(v: Boolean): Unit = { _wasDeserialize = v diff --git a/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala b/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala index 415a105d7a..4f1b87480a 100644 --- a/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala +++ b/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala @@ -5,7 +5,7 @@ import sigmastate.eval.IRContext import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv} import sigmastate.interpreter.PrecompiledScriptProcessor -import scala.util.{Success, Failure} +import scala.util.{Failure, Success} /** Base class for interpreters used in tests. * @see derived classes */ diff --git a/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala b/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala index a24c690694..2bec5196c8 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaDslTesting.scala @@ -269,10 +269,6 @@ class SigmaDslTesting extends PropSpec val prover = new FeatureProvingInterpreter() val verifier = new ErgoLikeInterpreter()(createIR()) { type CTX = ErgoLikeContext - override val precompiledScriptProcessor = - new PrecompiledScriptProcessor( - predefScripts.map { hex => CacheKey(parseTree(hex).bytes, ValidationRules.currentSettings) } - ) } // Create synthetic ErgoTree which uses all main capabilities of evaluation machinery. diff --git a/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala b/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala index 96f9fdc296..1ce73cdbe2 100644 --- a/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala +++ b/sigmastate/src/test/scala/special/sigma/SigmaTestingData.scala @@ -102,6 +102,7 @@ trait SigmaTestingData extends SigmaTestingCommons with ObjectGenerators { votes = Colls.emptyColl[Byte] ) + /** A list of ErgoTree hexes which are the most often used in Ergo mainnet. */ val predefScriptHexes = Seq( "0008cd03a40b2249d6a9cc7eedf21188842acef44f3df110a58a687ba3e28cbc2e97ead2", "1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304", From 838658dbff390c717ddb0f4f76f7e8f9d43756f3 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 2 Mar 2021 17:29:42 +0300 Subject: [PATCH 18/26] i694-box-opt: fixed ErgoLikeTransaction parsing under Scala 2.11 --- .../org/ergoplatform/ErgoBoxCandidate.scala | 43 ++++++++++--------- .../ergoplatform/ErgoLikeTransaction.scala | 12 ++++-- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala index 679d9f6670..964ad63b62 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala @@ -181,32 +181,31 @@ object ErgoBoxCandidate { /** Helper method to parse [[ErgoBoxCandidate]] previously serialized by * [[serializeBodyWithIndexedDigests()]]. */ - def parseBodyWithIndexedDigests(digestsInTx: Nullable[Array[TokenId]], r: SigmaByteReader): ErgoBoxCandidate = { + def parseBodyWithIndexedDigests(digestsInTx: Array[Array[Byte]], r: SigmaByteReader): ErgoBoxCandidate = { val previousPositionLimit = r.positionLimit r.positionLimit = r.position + ErgoBox.MaxBoxSize val value = r.getULong() // READ val tree = DefaultSerializer.deserializeErgoTree(r, SigmaSerializer.MaxPropositionSize) // READ val creationHeight = r.getUInt().toInt // READ val nTokens = r.getUByte() // READ - val tokenIds = new Array[Digest32](nTokens) + val tokenIds = new Array[Array[Byte]](nTokens) val tokenAmounts = new Array[Long](nTokens) - digestsInTx match { - case Nullable(digests) => - val nDigests = digests.length - cfor(0)(_ < nTokens, _ + 1) { i => - val digestIndex = r.getUInt().toInt // READ - if (digestIndex < 0 || digestIndex >= nDigests) - sys.error(s"failed to find token id with index $digestIndex") - val amount = r.getULong() // READ - tokenIds(i) = digests(digestIndex) - tokenAmounts(i) = amount - } - case _ => - val tokenIdSize = TokenId.size // optimization: access the value once - cfor(0)(_ < nTokens, _ + 1) { i => - tokenIds(i) = r.getBytes(tokenIdSize).asInstanceOf[Digest32] // READ - tokenAmounts(i) = r.getULong() // READ - } + if (digestsInTx != null) { + val nDigests = digestsInTx.length + cfor(0)(_ < nTokens, _ + 1) { i => + val digestIndex = r.getUInt().toInt // READ + if (digestIndex < 0 || digestIndex >= nDigests) + sys.error(s"failed to find token id with index $digestIndex") + val amount = r.getULong() // READ + tokenIds(i) = digestsInTx(digestIndex) + tokenAmounts(i) = amount + } + } else { + val tokenIdSize = TokenId.size // optimization: access the value once + cfor(0)(_ < nTokens, _ + 1) { i => + tokenIds(i) = r.getBytes(tokenIdSize) // READ + tokenAmounts(i) = r.getULong() // READ + } } val tokens = Colls.pairCollFromArrays(tokenIds, tokenAmounts) @@ -220,11 +219,13 @@ object ErgoBoxCandidate { b += ((reg, v)) // don't use `->` since it incur additional wrapper overhead } r.positionLimit = previousPositionLimit - new ErgoBoxCandidate(value, tree, creationHeight, tokens, b.result()) + new ErgoBoxCandidate( + value, tree, creationHeight, + tokens.asInstanceOf[Coll[(TokenId, Long)]], b.result()) } override def parse(r: SigmaByteReader): ErgoBoxCandidate = { - parseBodyWithIndexedDigests(Nullable.None, r) + parseBodyWithIndexedDigests(digestsInTx = null, r) } } diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala index c89d076f18..ec50263315 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala @@ -143,6 +143,7 @@ object ErgoLikeTransactionSerializer extends SigmaSerializer[ErgoLikeTransaction } } + /** @hotspot don't beautify the code */ override def parse(r: SigmaByteReader): ErgoLikeTransaction = { // parse transaction inputs val inputsCount = r.getUShort() @@ -150,25 +151,28 @@ object ErgoLikeTransactionSerializer extends SigmaSerializer[ErgoLikeTransaction cfor(0)(_ < inputsCount, _ + 1) { i => inputs(i) = Input.serializer.parse(r) } + // parse transaction data inputs val dataInputsCount = r.getUShort() val dataInputs = new Array[DataInput](dataInputsCount) cfor(0)(_ < dataInputsCount, _ + 1) { i => dataInputs(i) = DataInput(ADKey @@ r.getBytes(ErgoBox.BoxId.size)) } + // parse distinct ids of tokens in transaction outputs val tokensCount = r.getUInt().toInt - val tokens = new Array[TokenId](tokensCount) + val tokens = new Array[Array[Byte]](tokensCount) cfor(0)(_ < tokensCount, _ + 1) { i => - tokens(i) = Digest32 @@ r.getBytes(TokenId.size) + tokens(i) = r.getBytes(TokenId.size) } - // parse outputs + // parse outputs val outsCount = r.getUShort() val outputCandidates = new Array[ErgoBoxCandidate](outsCount) cfor(0)(_ < outsCount, _ + 1) { i => - outputCandidates(i) = ErgoBoxCandidate.serializer.parseBodyWithIndexedDigests(Nullable(tokens), r) + outputCandidates(i) = ErgoBoxCandidate.serializer.parseBodyWithIndexedDigests(tokens, r) } + new ErgoLikeTransaction(inputs, dataInputs, outputCandidates) } From b2e57da197c7209529dbe1ab57dafc7a541f51c3 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 3 Mar 2021 11:30:43 +0300 Subject: [PATCH 19/26] i694-box-opt: more ScalaDocs --- .../ergoplatform/ErgoLikeTransaction.scala | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala index ec50263315..0698537eec 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala @@ -1,9 +1,8 @@ package org.ergoplatform import org.ergoplatform.ErgoBox.TokenId -import scalan.Nullable import scorex.crypto.authds.ADKey -import scorex.crypto.hash.{Blake2b256, Digest32} +import scorex.crypto.hash.Blake2b256 import scorex.util._ import sigmastate.SType._ import sigmastate.eval.Extensions._ @@ -20,27 +19,28 @@ trait ErgoBoxReader { def byId(boxId: ADKey): Try[ErgoBox] } -/** - * Base trait of a real transaction to be used in Ergo network. +/** Base trait of a real transaction to be used in Ergo network. * May be in unsigned (`UnsignedErgoLikeTransaction`) or in signed (`ErgoLikeTransaction`) version. - * - * Consists of: - * - * @param inputs - inputs, that will be spent by this transaction. - * @param dataInputs - inputs, that are not going to be spent by transaction, but will be - * reachable from inputs scripts. `dataInputs` scripts will not be executed, - * thus their scripts costs are not included in transaction cost and - * they do not contain spending proofs. - * @param outputCandidates - box candidates to be created by this transaction. - * Differ from ordinary ones in that they do not include transaction id and index */ trait ErgoLikeTransactionTemplate[IT <: UnsignedInput] { + /** Inputs, that are not going to be spent by transaction, but will be + * reachable from inputs scripts. `dataInputs` scripts will not be executed, + * thus their scripts costs are not included in transaction cost and + * they do not contain spending proofs. + */ val dataInputs: IndexedSeq[DataInput] + + /** Inputs, that will be spent by this transaction. */ val inputs: IndexedSeq[IT] + + /** Box candidates to be created by this transaction. + * Differ from ordinary ones in that they do not include transaction id and index. + */ val outputCandidates: IndexedSeq[ErgoBoxCandidate] require(outputCandidates.size <= Short.MaxValue) + /** Indentifier of this transaction as state Modifier. */ val id: ModifierId lazy val outputs: IndexedSeq[ErgoBox] = From da884ff7f2d2b754f2fe979c1058bb3f15a9e858 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 3 Mar 2021 19:12:41 +0300 Subject: [PATCH 20/26] i694-box-opt: added ScriptProcessorSettings --- .../PrecompiledScriptProcessor.scala | 102 ++++++++++++++---- .../TestingInterpreterSpecification.scala | 3 +- ...compiledScriptProcessorSpecification.scala | 4 +- 3 files changed, 84 insertions(+), 25 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala index f60e82c855..0e62800946 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala @@ -1,8 +1,9 @@ package sigmastate.interpreter import java.util.concurrent.ExecutionException +import java.util.concurrent.atomic.AtomicInteger -import com.google.common.cache.{RemovalListener, CacheBuilder, RemovalNotification, CacheLoader} +import com.google.common.cache.{CacheBuilder, RemovalNotification, RemovalListener, LoadingCache, CacheLoader, CacheStats} import org.ergoplatform.settings.ErgoAlgos import org.ergoplatform.validation.{ValidationRules, SigmaValidationSettings} import org.ergoplatform.validation.ValidationRules.{CheckCostFunc, CheckCalcFunc, trySoftForkable} @@ -99,11 +100,23 @@ case class PrecompiledScriptReducer(scriptBytes: Seq[Byte])(implicit val IR: IRC */ case class CacheKey(ergoTreeBytes: Seq[Byte], vs: SigmaValidationSettings) +/** Settings to configure script processor. + * @param predefScripts collection of scripts to ALWAYS pre-compile (each given by ErgoTree bytes) + * @param maxCacheSize maximum number of entries in the cache + * @param recordCacheStats if true, then cache statistics is recorded + * @param reportingInterval number of cache load operations between two reporting events + */ +case class ScriptProcessorSettings( + predefScripts: Seq[CacheKey], + maxCacheSize: Int = 1000, + recordCacheStats: Boolean = false, + reportingInterval: Int = 100 +) /** Script processor which holds pre-compiled reducers for the given scripts. - * @param predefScripts collection of scripts to ALWAYS pre-compile (each given by ErgoTree bytes) + * This class is thread-safe. */ -class PrecompiledScriptProcessor(val predefScripts: Seq[CacheKey]) { +class PrecompiledScriptProcessor(val settings: ScriptProcessorSettings) { /** Creates a new instance of IRContex to be used in reducers. * The default implementation can be overriden in derived classes. @@ -111,8 +124,9 @@ class PrecompiledScriptProcessor(val predefScripts: Seq[CacheKey]) { protected def createIR(): IRContext = new RuntimeIRContext /** Holds for each ErgoTree bytes the corresponding pre-compiled reducer. */ - val reducers = { + protected val predefReducers = { implicit val IR: IRContext = createIR() + val predefScripts = settings.predefScripts val res = AVHashMap[CacheKey, ScriptReducer](predefScripts.length) predefScripts.foreach { s => val r = PrecompiledScriptReducer(s.ergoTreeBytes) @@ -122,39 +136,69 @@ class PrecompiledScriptProcessor(val predefScripts: Seq[CacheKey]) { res } + protected def onEvictedCacheEntry(key: CacheKey): Unit = { + } + /** Called when a cache item is removed. */ - private val CacheListener = new RemovalListener[CacheKey, ScriptReducer]() { + protected val cacheListener = new RemovalListener[CacheKey, ScriptReducer]() { override def onRemoval(notification: RemovalNotification[CacheKey, ScriptReducer]): Unit = { if (notification.wasEvicted()) { - val scriptHex = ErgoAlgos.encode(notification.getKey.ergoTreeBytes.toArray) - println(s"Evicted: ${scriptHex}") + onEvictedCacheEntry(notification.getKey) } } } - /** The cache which stores MRU set of pre-compiled reducers. */ - val cache = { - CacheBuilder.newBuilder - .maximumSize(1000) - .removalListener(CacheListener) - .recordStats() - .build(new CacheLoader[CacheKey, ScriptReducer]() { - override def load(key: CacheKey): ScriptReducer = { - trySoftForkable[ScriptReducer](whenSoftFork = WhenSoftForkReducer) { - PrecompiledScriptReducer(key.ergoTreeBytes)(createIR()) - }(key.vs) + protected def onReportStats(stats: CacheStats) = { + } + + /** Loader to be used on a cache miss. The loader creates a new [[ScriptReducer]] for + * the given [[CacheKey]]. The default loader creates an instance of + * [[PrecompiledScriptReducer]] which stores its own IRContext and compiles costF, + * calcF graphs. */ + protected val cacheLoader = new CacheLoader[CacheKey, ScriptReducer]() { + /** Internal counter of all load operations happening an different threads. */ + private val loadCounder = new AtomicInteger(1) + + override def load(key: CacheKey): ScriptReducer = { + val r = trySoftForkable[ScriptReducer](whenSoftFork = WhenSoftForkReducer) { + PrecompiledScriptReducer(key.ergoTreeBytes)(createIR()) + }(key.vs) + + val c = loadCounder.incrementAndGet() + if (c > settings.reportingInterval) { + if (loadCounder.compareAndSet(c, 1)) { + // call reporting only if we was able to reset the counter + // avoid double reporting + onReportStats(cache.stats()) } - }) + } + + r + } + } + + /** The cache which stores MRU set of pre-compiled reducers. */ + val cache: LoadingCache[CacheKey, ScriptReducer] = { + var b = CacheBuilder.newBuilder + .maximumSize(settings.maxCacheSize) + .removalListener(cacheListener) + if (settings.recordCacheStats) { + b = b.recordStats() + } + b.build(cacheLoader) } /** Looks up verifier for the given ErgoTree using its 'bytes' property. + * It first looks up for predefReducers, if not found it looks up in the cache. + * If there is no cache entry, the `cacheLoader` is used to load a new `ScriptReducer`. + * * @param ergoTree a tree to lookup pre-compiled verifier. * @return non-empty Nullable instance with verifier for the given tree, otherwise * Nullable.None */ def getReducer(ergoTree: ErgoTree, vs: SigmaValidationSettings): ScriptReducer = { val key = CacheKey(ergoTree.bytes, vs) - reducers.get(key) match { + predefReducers.get(key) match { case Nullable(r) => r case _ => val r = try { @@ -170,10 +214,24 @@ class PrecompiledScriptProcessor(val predefScripts: Seq[CacheKey]) { object PrecompiledScriptProcessor { /** Default script processor which uses [[RuntimeIRContext]] to process graphs. */ - val Default = new PrecompiledScriptProcessor(mutable.WrappedArray.empty[CacheKey]) + val Default = new PrecompiledScriptProcessor( + ScriptProcessorSettings(mutable.WrappedArray.empty[CacheKey])) /** Script processor which uses [[CompiletimeIRContext]] to process graphs. */ - val WithCompiletimeIRContext = new PrecompiledScriptProcessor(mutable.WrappedArray.empty) { + val WithCompiletimeIRContext = new PrecompiledScriptProcessor( + ScriptProcessorSettings( + predefScripts = mutable.WrappedArray.empty, + recordCacheStats = true, + reportingInterval = 10)) { override protected def createIR(): IRContext = new CompiletimeIRContext + + override protected def onReportStats(stats: CacheStats): Unit = { + println(s"Cache Stats: $stats") + } + + override protected def onEvictedCacheEntry(key: CacheKey): Unit = { + val scriptHex = ErgoAlgos.encode(key.ergoTreeBytes.toArray) + println(s"Evicted: ${scriptHex}") + } } } diff --git a/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala b/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala index 2f53dea87f..e0c40c3fec 100644 --- a/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala @@ -23,7 +23,8 @@ class TestingInterpreterSpecification extends SigmaTestingCommons with CrossVersionProps with BeforeAndAfterAll { implicit lazy val IR = new TestingIRContext - lazy val processor = new PrecompiledScriptProcessor(mutable.WrappedArray.empty) { + lazy val processor = new PrecompiledScriptProcessor( + ScriptProcessorSettings(mutable.WrappedArray.empty)) { override protected def createIR(): IRContext = new TestingIRContext } diff --git a/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala b/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala index 1872077fe7..e0f1fec347 100644 --- a/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala @@ -23,7 +23,7 @@ class PrecompiledScriptProcessorSpecification extends SigmaDslTesting { val extraTrees = Seq(TrueTree, FalseTree) val trees = extraTrees ++ predefTrees val scripts = trees.map { t => CacheKey(t.bytes, ValidationRules.currentSettings) } - val processor = new PrecompiledScriptProcessor(scripts) + val processor = new PrecompiledScriptProcessor(ScriptProcessorSettings(scripts)) trees.foreach { t => processor.getReducer(t, ValidationRules.currentSettings) should not be null } @@ -33,7 +33,7 @@ class PrecompiledScriptProcessorSpecification extends SigmaDslTesting { val trees = Seq(TrueTree, TrueTree) val scripts = trees.map { t => CacheKey(t.bytes, ValidationRules.currentSettings) } assertExceptionThrown( - new PrecompiledScriptProcessor(scripts), + new PrecompiledScriptProcessor(ScriptProcessorSettings(scripts)), { case _: IllegalArgumentException => true case _ => false } ) From 7c579387d7ba237cb93dc531510e881eb2dfc5ba Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 3 Mar 2021 19:19:33 +0300 Subject: [PATCH 21/26] i694-box-opt: remove @hotspot attribute (use HOTSPOT: instead) --- common/src/main/scala/scalan/util/Extensions.scala | 2 +- core/src/main/scala/scalan/Base.scala | 6 +++--- core/src/main/scala/scalan/staged/AstGraphs.scala | 8 ++++---- core/src/main/scala/scalan/staged/Transforming.scala | 6 +++--- sigmastate/src/main/scala/org/ergoplatform/ErgoBox.scala | 6 +++--- .../main/scala/org/ergoplatform/ErgoBoxCandidate.scala | 2 +- .../main/scala/org/ergoplatform/ErgoLikeTransaction.scala | 2 +- .../ergoplatform/validation/SigmaValidationSettings.scala | 2 +- .../org/ergoplatform/validation/ValidationRules.scala | 2 +- sigmastate/src/main/scala/sigmastate/Values.scala | 6 ++++-- .../main/scala/sigmastate/eval/CostingDataContext.scala | 2 +- .../src/main/scala/sigmastate/eval/RuntimeCosting.scala | 4 ++-- .../src/main/scala/sigmastate/lang/SigmaBuilder.scala | 2 +- .../sigmastate/serialization/BlockValueSerializer.scala | 2 +- .../ConcreteCollectionBooleanConstantSerializer.scala | 2 +- .../serialization/ConcreteCollectionSerializer.scala | 2 +- .../scala/sigmastate/serialization/ConstantStore.scala | 2 +- .../sigmastate/serialization/ErgoTreeSerializer.scala | 4 ++-- .../sigmastate/serialization/MethodCallSerializer.scala | 2 +- .../serialization/trees/Relation2Serializer.scala | 2 +- sigmastate/src/main/scala/sigmastate/trees.scala | 2 +- sigmastate/src/main/scala/sigmastate/types.scala | 2 +- .../src/main/scala/sigmastate/utils/SigmaByteReader.scala | 2 +- .../src/main/scala/sigmastate/utxo/transformers.scala | 2 +- 24 files changed, 38 insertions(+), 36 deletions(-) diff --git a/common/src/main/scala/scalan/util/Extensions.scala b/common/src/main/scala/scalan/util/Extensions.scala index a1bf61f03b..7a820585fe 100644 --- a/common/src/main/scala/scalan/util/Extensions.scala +++ b/common/src/main/scala/scalan/util/Extensions.scala @@ -13,7 +13,7 @@ object Extensions { def toByte: Byte = if (b) 1 else 0 } - /** @hotspot it is used in deserialization so we avoid allocation by any means. */ + /** HOTSPOT: it is used in deserialization so we avoid allocation by any means. */ @inline final def toUByte(b: Byte) = b & 0xFF implicit class ByteOps(val b: Byte) extends AnyVal { diff --git a/core/src/main/scala/scalan/Base.scala b/core/src/main/scala/scalan/Base.scala index 47cae5da08..84c1314ac5 100644 --- a/core/src/main/scala/scalan/Base.scala +++ b/core/src/main/scala/scalan/Base.scala @@ -538,7 +538,7 @@ abstract class Base { scalan: Scalan => /** Transforms this object into new one by applying `t` to every Ref inside * its structure. The structure is build out of Seq, Array, Option and Def values. * Other structure items remain unchanged and copied to the new instance. - * @hotspot don't beautify the code */ + * HOTSPOT: don't beautify the code */ protected def transformProductParam(x: Any, t: Transformer): Any = x match { case (_: UnOp[_, _]) | (_: BinOp[_, _]) => // allows use of context bounds in classes extending UnOp/BinOp. @@ -687,7 +687,7 @@ abstract class Base { scalan: Scalan => /** Create or find symbol (node Ref) which refers to the given node in the table of all created symbols. * The d.nodeId is the index in the _symbolTable which is DBuffer (backed by Array) * @return new of existing symbol - * @hotspot the method should be allocation-free (make it sure by examining the generated Java code) + * HOTSPOT: the method should be allocation-free (make it sure by examining the generated Java code) */ final def updateSymbolTable[T](s: Ref[T], d: Def[T]): Ref[T] = { val id = d.nodeId @@ -769,7 +769,7 @@ abstract class Base { scalan: Scalan => * @param d node to be added to the head of nodes * @param newSym producer of the reference to be used as the reference to `d` node. * @return return a reference to `d` node in the heap - * @hotspot */ + * HOTSPOT: */ def findOrCreateDefinition[T](d: Def[T], newSym: => Ref[T]): Ref[T] = { val optScope = thunkStack.top var sym = optScope match { diff --git a/core/src/main/scala/scalan/staged/AstGraphs.scala b/core/src/main/scala/scalan/staged/AstGraphs.scala index dd801e25e3..8723da5633 100644 --- a/core/src/main/scala/scalan/staged/AstGraphs.scala +++ b/core/src/main/scala/scalan/staged/AstGraphs.scala @@ -49,7 +49,7 @@ trait AstGraphs extends Transforming { self: Scalan => * If the graph represents a compound definition (Lambda, Thunk etc), * then each item in `freeVars` is used in the body, but not part of it. * Intersection of free vars with bound vars is empty. - * @hotspot don't beautify the code + * HOTSPOT: don't beautify the code */ def freeVars: Seq[Sym] = { val sch = schedule.toArray @@ -89,7 +89,7 @@ trait AstGraphs extends Transforming { self: Scalan => def scheduleIds: DBuffer[Int] /** Sequence of node references forming a schedule. - * @hotspot don't beautify the code */ + * HOTSPOT: don't beautify the code */ lazy val schedule: Schedule = { val ids = scheduleIds val len = ids.length @@ -155,7 +155,7 @@ trait AstGraphs extends Transforming { self: Scalan => /** Build usage information induced by the given schedule. * For each symbol of the schedule a GraphNode is created and usages are collected. - * @hotspot don't beautify the code + * HOTSPOT: don't beautify the code */ def buildUsageMap(schedule: Schedule, usingDeps: Boolean): DMap[Int, GraphNode] = { val len = schedule.length @@ -203,7 +203,7 @@ trait AstGraphs extends Transforming { self: Scalan => def hasManyUsagesGlobal(s: Sym): Boolean = globalUsagesOf(s).length > 1 - /** @hotspot for performance we return mutable structure, but it should never be changed. */ + /** HOTSPOT: for performance we return mutable structure, but it should never be changed. */ def usagesOf(id: Int): DBuffer[Int] = { val node = usageMap.getOrElse(id, null) if (node == null) return emptyDBufferOfInt diff --git a/core/src/main/scala/scalan/staged/Transforming.scala b/core/src/main/scala/scalan/staged/Transforming.scala index 22f021b8cb..c7760f8899 100644 --- a/core/src/main/scala/scalan/staged/Transforming.scala +++ b/core/src/main/scala/scalan/staged/Transforming.scala @@ -71,7 +71,7 @@ trait Transforming { self: Scalan => } /** Concrete and default implementation of Transformer using underlying HashMap. - * @hotspot don't beatify the code */ + * HOTSPOT: don't beatify the code */ case class MapTransformer(private val subst: util.HashMap[Sym, Sym]) extends Transformer { def this(substPairs: (Sym, Sym)*) { this({ @@ -145,7 +145,7 @@ trait Transforming { self: Scalan => /** Base class for mirrors of graph nodes. Provides default implementations which can be * overriden if special logic is required. - * @hotspot don't beautify the code */ + * HOTSPOT: don't beautify the code */ abstract class Mirror { def apply[A](t: Transformer, rewriter: Rewriter, node: Ref[A], d: Def[A]): Sym = d.mirror(t) @@ -246,7 +246,7 @@ trait Transforming { self: Scalan => } } - /** @hotspot */ + /** HOTSPOT: */ def mirrorSymbols(t0: Transformer, rewriter: Rewriter, g: AstGraph, nodes: DBuffer[Int]) = { var t: Transformer = t0 cfor(0)(_ < nodes.length, _ + 1) { i => diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoBox.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoBox.scala index 90fb04a8fd..79fbb8c436 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoBox.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoBox.scala @@ -46,7 +46,7 @@ import scala.runtime.ScalaRunTime * @param creationHeight - height when a transaction containing the box was created. * This height is declared by user and should not exceed height of the block, * containing the transaction with this box. - * @hotspot don't beautify the code of this class + * HOTSPOT: don't beautify the code of this class */ class ErgoBox( override val value: Long, @@ -149,7 +149,7 @@ object ErgoBox { val maxRegisters: Int = SigmaConstants.MaxRegisters.value - /** @hotspot don't beautify the code in this companion */ + /** HOTSPOT: don't beautify the code in this companion */ private val _mandatoryRegisters: Array[MandatoryRegisterId] = Array(R0, R1, R2, R3) val mandatoryRegisters: Seq[MandatoryRegisterId] = _mandatoryRegisters @@ -169,7 +169,7 @@ object ErgoBox { val registerByName: Map[String, RegisterId] = allRegisters.map(r => s"R${r.number}" -> r).toMap - /** @hotspot called from ErgoBox serializer */ + /** HOTSPOT: called from ErgoBox serializer */ @inline final def registerByIndex(index: Int): RegisterId = allRegisters(index) def findRegisterByIndex(i: Int): Option[RegisterId] = diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala index 964ad63b62..04b9a46d8b 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala @@ -116,7 +116,7 @@ object ErgoBoxCandidate { */ val UndefinedBoxRef: Coll[Byte] = Array.fill(34)(0: Byte).toColl - /** @hotspot don't beautify the code */ + /** HOTSPOT: don't beautify the code */ object serializer extends SigmaSerializer[ErgoBoxCandidate, ErgoBoxCandidate] { /** Helper method for [[ErgoBoxCandidate]] serialization. diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala index 0698537eec..3d8a6dbc57 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala @@ -143,7 +143,7 @@ object ErgoLikeTransactionSerializer extends SigmaSerializer[ErgoLikeTransaction } } - /** @hotspot don't beautify the code */ + /** HOTSPOT: don't beautify the code */ override def parse(r: SigmaByteReader): ErgoLikeTransaction = { // parse transaction inputs val inputsCount = r.getUShort() diff --git a/sigmastate/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala b/sigmastate/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala index e74e41c8c1..c269c0346f 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/validation/SigmaValidationSettings.scala @@ -60,7 +60,7 @@ sealed class MapSigmaValidationSettings(private val map: Map[Short, (ValidationR override def iterator: Iterator[(Short, (ValidationRule, RuleStatus))] = map.iterator override def get(id: Short): Option[(ValidationRule, RuleStatus)] = map.get(id) - /** @hotspot don't beautify this code */ + /** HOTSPOT: don't beautify this code */ override def getStatus(id: Short): Option[RuleStatus] = { val statusOpt = map.get(id) val res = if (statusOpt.isDefined) Some(statusOpt.get._2) else None diff --git a/sigmastate/src/main/scala/org/ergoplatform/validation/ValidationRules.scala b/sigmastate/src/main/scala/org/ergoplatform/validation/ValidationRules.scala index 87aa64fde1..86e2e38675 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/validation/ValidationRules.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/validation/ValidationRules.scala @@ -33,7 +33,7 @@ case class ValidationRule( /** Check the rule is registered and enabled. * Since it is easy to forget to register new rule, we need to do this check. * But because it is hotspot, we do this check only once for each rule. - * @hotspot executed on every typeCode and opCode during script deserialization + * HOTSPOT: executed on every typeCode and opCode during script deserialization */ @inline protected final def checkRule(): Unit = { if (!_checked) { diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index d2c220f3eb..63c6deda93 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -122,7 +122,7 @@ object Values { val EmptySeq: IndexedSeq[SValue] = EmptyArray /** Traverses the given expression tree and counts the number of deserialization - * operations. If it it non zero, returns true. */ + * operations. If it is non-zero, returns true. */ def hasDeserialize(exp: SValue): Boolean = { val deserializeNode: PartialFunction[Any, Int] = { case _: DeserializeContext[_] => 1 @@ -578,6 +578,8 @@ object Values { * * @param sb sigma proposition to estimate * @return the value of estimated cost + * + * HOTSPOT: don't beautify the code */ def estimateCost(sb: SigmaBoolean): Int = sb match { case _: ProveDlog => CostTable.proveDlogEvalCost @@ -613,7 +615,7 @@ object Values { CostTable.MinimalCost } - /** @hotspot don't beautify this code */ + /** HOTSPOT: don't beautify this code */ object serializer extends SigmaSerializer[SigmaBoolean, SigmaBoolean] { val dhtSerializer = ProveDHTupleSerializer(ProveDHTuple.apply) val dlogSerializer = ProveDlogSerializer(ProveDlog.apply) diff --git a/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala b/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala index 2ff25a714c..256290ef60 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/CostingDataContext.scala @@ -598,7 +598,7 @@ class CostingSigmaDslBuilder extends TestSigmaDslBuilder { dsl => /** Extracts [[ErgoBox]] from the given [[Box]] instance. This is inverse to the Box method. */ def toErgoBox(b: Box): ErgoBox = b.asInstanceOf[CostingBox].ebox - /** @hotspot don't beautify this code */ + /** HOTSPOT: don't beautify this code */ private def toSigmaTrees(props: Array[SigmaProp]): Array[SigmaBoolean] = { val len = props.length val res = new Array[SigmaBoolean](len) diff --git a/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala b/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala index 86ebf9eb89..d6457d0fe5 100644 --- a/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala +++ b/sigmastate/src/main/scala/sigmastate/eval/RuntimeCosting.scala @@ -511,7 +511,7 @@ trait RuntimeCosting extends CostingRules { IR: IRContext => * Unfortunately, this is less readable, but gives significant performance boost * Look at comments to understand the logic of the rules. * - * @hotspot executed for each node of the graph, don't beautify. + * HOTSPOT: executed for each node of the graph, don't beautify. */ override def rewriteDef[T](d: Def[T]): Ref[_] = { // First we match on node type, and then depending on it, we have further branching logic. @@ -1027,7 +1027,7 @@ trait RuntimeCosting extends CostingRules { IR: IRContext => _contextDependantNodes(sym.node.nodeId) } - /** @hotspot don't beautify the code */ + /** HOTSPOT: don't beautify the code */ @inline final def allContextDependant(syms: Array[Sym]): Boolean = { val len = syms.length cfor(0)(_ < len, _ + 1) { i => diff --git a/sigmastate/src/main/scala/sigmastate/lang/SigmaBuilder.scala b/sigmastate/src/main/scala/sigmastate/lang/SigmaBuilder.scala index 5ec5145474..a322718a17 100644 --- a/sigmastate/src/main/scala/sigmastate/lang/SigmaBuilder.scala +++ b/sigmastate/src/main/scala/sigmastate/lang/SigmaBuilder.scala @@ -651,7 +651,7 @@ class StdSigmaBuilder extends SigmaBuilder { trait TypeConstraintCheck { - /** @hotspot called during script deserialization (don't beautify this code) + /** HOTSPOT: called during script deserialization (don't beautify this code) * @consensus */ def check2[T <: SType](left: Value[T], diff --git a/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala index 04b05ea70d..c0461b8148 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala @@ -28,7 +28,7 @@ case class BlockValueSerializer(cons: (IndexedSeq[BlockItem], Value[SType]) => V val values: IndexedSeq[BlockItem] = if (itemsSize == 0) BlockItem.EmptySeq else { - // @hotspot: allocate new array only if it is not empty + // HOTSPOT:: allocate new array only if it is not empty val buf = new Array[BlockItem](itemsSize) cfor(0)(_ < itemsSize, _ + 1) { i => buf(i) = r.getValue().asInstanceOf[BlockItem] diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala index e2d5895e9b..36a0e22801 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionBooleanConstantSerializer.scala @@ -28,7 +28,7 @@ case class ConcreteCollectionBooleanConstantSerializer(cons: (IndexedSeq[Value[S w.putBits(bits, bitsInfo) } - /** @hotspot don't beautify this code */ + /** HOTSPOT: don't beautify this code */ override def parse(r: SigmaByteReader): Value[SCollection[SBoolean.type]] = { val size = r.getUShort() // READ val bits = r.getBits(size) // READ diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionSerializer.scala index 82c3e24f22..d44f3adf2a 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ConcreteCollectionSerializer.scala @@ -21,7 +21,7 @@ case class ConcreteCollectionSerializer(cons: (IndexedSeq[Value[SType]], SType) foreach(numItemsInfo.info.name, cc.items)(w.putValue(_, itemInfo)) } - /** @hotspot don't beautify this code */ + /** HOTSPOT: don't beautify this code */ override def parse(r: SigmaByteReader): Value[SCollection[SType]] = { val size = r.getUShort() // READ val tItem = r.getType() // READ diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ConstantStore.scala b/sigmastate/src/main/scala/sigmastate/serialization/ConstantStore.scala index 0bd4588828..dfa7dbb30a 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ConstantStore.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ConstantStore.scala @@ -5,7 +5,7 @@ import sigmastate.Values.{Constant, ConstantNode, ConstantPlaceholder} import sigmastate.lang.SigmaBuilder import debox.Buffer -/** @hotspot used in deserialization (don't beautify this code) */ +/** HOTSPOT: used in deserialization (don't beautify this code) */ class ConstantStore(private val constants: IndexedSeq[Constant[SType]] = Constant.EmptySeq) { private val store: Buffer[Constant[SType]] = Buffer.fromIterable(constants) diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala index 1b17c767e4..a4aa1b33b7 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala @@ -216,14 +216,14 @@ class ErgoTreeSerializer { private val constantSerializer = ConstantSerializer(DeserializationSigmaBuilder) /** Deserialize constants section only. - * @hotspot don't beautify this code + * HOTSPOT: don't beautify this code */ private def deserializeConstants(header: Byte, r: SigmaByteReader): IndexedSeq[Constant[SType]] = { val constants: IndexedSeq[Constant[SType]] = if (ErgoTree.isConstantSegregation(header)) { val nConsts = r.getUInt().toInt if (nConsts > 0) { - // @hotspot: allocate new array only if it is not empty + // HOTSPOT:: allocate new array only if it is not empty val res = new Array[Constant[SType]](nConsts) cfor(0)(_ < nConsts, _ + 1) { i => res(i) = constantSerializer.deserialize(r) diff --git a/sigmastate/src/main/scala/sigmastate/serialization/MethodCallSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/MethodCallSerializer.scala index 99f4dc29e8..9e609a8763 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/MethodCallSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/MethodCallSerializer.scala @@ -38,7 +38,7 @@ case class MethodCallSerializer(cons: (Value[SType], SMethod, IndexedSeq[Value[S * This is limitation of MethodCall, because we cannot use it to represent for example * def Box.getReg[T](id: Int): Option[T], which require serialization of expected type `T` * However it can be implemented using separate node type (new type code) and can be added via soft-fork. - * @hotspot don't beautify this code + * HOTSPOT: don't beautify this code */ override def parse(r: SigmaByteReader): Value[SType] = { val typeId = r.getByte() diff --git a/sigmastate/src/main/scala/sigmastate/serialization/trees/Relation2Serializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/trees/Relation2Serializer.scala index 58345a08cd..23f6fcb6d4 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/trees/Relation2Serializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/trees/Relation2Serializer.scala @@ -35,7 +35,7 @@ case class Relation2Serializer[S1 <: SType, S2 <: SType, R <: Value[SBoolean.typ } } - /** @hotspot don't beautify this code */ + /** HOTSPOT: don't beautify this code */ override def parse(r: SigmaByteReader): R = { if (r.peekByte() == ConcreteCollectionBooleanConstantCode) { val _ = r.getByte() // skip collection op code diff --git a/sigmastate/src/main/scala/sigmastate/trees.scala b/sigmastate/src/main/scala/sigmastate/trees.scala index 2600ab89a1..d2c32cb158 100644 --- a/sigmastate/src/main/scala/sigmastate/trees.scala +++ b/sigmastate/src/main/scala/sigmastate/trees.scala @@ -317,7 +317,7 @@ object AtLeast extends ValueCompanion { def apply(bound: Value[SInt.type], head: SigmaPropValue, tail: SigmaPropValue*): AtLeast = apply(bound, head +: tail) - /** @hotspot don't beautify this code */ + /** HOTSPOT: don't beautify this code */ def reduce(bound: Int, children: Seq[SigmaBoolean]): SigmaBoolean = { import sigmastate.TrivialProp._ if (bound <= 0) return TrueProp diff --git a/sigmastate/src/main/scala/sigmastate/types.scala b/sigmastate/src/main/scala/sigmastate/types.scala index 0b453d2067..a65cab2988 100644 --- a/sigmastate/src/main/scala/sigmastate/types.scala +++ b/sigmastate/src/main/scala/sigmastate/types.scala @@ -1436,7 +1436,7 @@ case class SFunc(tDom: IndexedSeq[SType], tRange: SType, tpeParams: Seq[STypePa object SFunc { final val FuncTypeCode: TypeCode = OpCodes.FirstFuncType - def apply(tDom: SType, tRange: SType): SFunc = SFunc(Array(tDom), tRange) // @hotspot + def apply(tDom: SType, tRange: SType): SFunc = SFunc(Array(tDom), tRange) // HOTSPOT: val identity = { x: Any => x } } diff --git a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala index d44f038f1f..ee55690b3a 100644 --- a/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala +++ b/sigmastate/src/main/scala/sigmastate/utils/SigmaByteReader.scala @@ -33,7 +33,7 @@ class SigmaByteReader(val r: Reader, /** The reader should be lightweight to create. In most cases ErgoTrees don't have * ValDef nodes hence the store is not necessary and it's initialization dominates the * reader instantiation time. Hence it's lazy. - * @hotspot + * HOTSPOT: */ lazy val valDefTypeStore: ValDefTypeStore = new ValDefTypeStore() diff --git a/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala b/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala index 2c79e001e6..9deb31c0e1 100644 --- a/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala +++ b/sigmastate/src/main/scala/sigmastate/utxo/transformers.scala @@ -236,7 +236,7 @@ case class ExtractRegisterAs[V <: SType]( input: Value[SBox.type], object ExtractRegisterAs extends ValueCompanion { override def opCode: OpCode = OpCodes.ExtractRegisterAs - //@hotspot: avoids thousands of allocations per second + //HOTSPOT:: avoids thousands of allocations per second private val BoxAndByte: IndexedSeq[SType] = Array(SBox, SByte) def apply[V <: SType](input: Value[SBox.type], From cdbb2d82faece6bba7619271885fbd885e817a06 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Thu, 4 Mar 2021 21:43:25 +0300 Subject: [PATCH 22/26] i694-box-opt: collect predef hit statistics --- .../PrecompiledScriptProcessor.scala | 78 +++++++++++-------- .../SoftForkabilitySpecification.scala | 2 +- .../helpers/ErgoTransactionValidator.scala | 48 +++++++++++- ...compiledScriptProcessorSpecification.scala | 16 ++++ .../examples/CoinEmissionSpecification.scala | 14 +++- .../sigmastate/utxo/examples/IcoExample.scala | 5 +- 6 files changed, 121 insertions(+), 42 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala index 0e62800946..23462f0fd3 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala @@ -14,7 +14,7 @@ import sigmastate.eval.{RuntimeIRContext, IRContext, CompiletimeIRContext} import sigmastate.interpreter.Interpreter.ReductionResult import sigmastate.serialization.ErgoTreeSerializer import sigmastate.utils.Helpers._ - +import spire.syntax.all.cfor import scala.collection.mutable /** A reducer which represents precompiled script reduction function. @@ -113,6 +113,12 @@ case class ScriptProcessorSettings( reportingInterval: Int = 100 ) +/** Statistics of ScriptProcessor operations. + * @param cacheStats cache statistics such as hits and misses + * @param predefHits one hit counter for each predefined script + */ +case class ProcessorStats(cacheStats: CacheStats, predefHits: Seq[Int]) + /** Script processor which holds pre-compiled reducers for the given scripts. * This class is thread-safe. */ @@ -123,19 +129,41 @@ class PrecompiledScriptProcessor(val settings: ScriptProcessorSettings) { */ protected def createIR(): IRContext = new RuntimeIRContext - /** Holds for each ErgoTree bytes the corresponding pre-compiled reducer. */ - protected val predefReducers = { + /** Holds for each ErgoTree bytes the corresponding pre-compiled reducer. + * Since [[AVHashMap]] is not thread-safe it should be immutable + * after it is constructed. However the counters will be updated on each increase. + */ + protected val predefReducers: AVHashMap[CacheKey, (ScriptReducer, AtomicInteger)] = { implicit val IR: IRContext = createIR() val predefScripts = settings.predefScripts - val res = AVHashMap[CacheKey, ScriptReducer](predefScripts.length) + val res = AVHashMap[CacheKey, (ScriptReducer, AtomicInteger)](predefScripts.length) predefScripts.foreach { s => val r = PrecompiledScriptReducer(s.ergoTreeBytes) - val old = res.put(s, r) + val old = res.put(s, (r, new AtomicInteger(0))) require(old == null, s"duplicate predefined script: '${ErgoAlgos.encode(s.ergoTreeBytes.toArray)}'") } res } + /** Obtain hit counter for each predefined script. The order of counters corresponds to + * the order of the scripts in `settings`. + * */ + def getPredefStats(): Seq[Int] = { + val scriptKeys = settings.predefScripts.toArray + val nScripts = scriptKeys.length + val res = new Array[Int](nScripts) + cfor(0)(_ < nScripts, _ + 1) { i => + val key = scriptKeys(i) + val counter = predefReducers.get(key).get._2 + res(i) = counter.get() + } + res + } + + /** Returns accumulated statistics info. */ + def getStats(): ProcessorStats = ProcessorStats(cache.stats(), getPredefStats()) + + /** Called when the cache entry is evicted. */ protected def onEvictedCacheEntry(key: CacheKey): Unit = { } @@ -148,7 +176,8 @@ class PrecompiledScriptProcessor(val settings: ScriptProcessorSettings) { } } - protected def onReportStats(stats: CacheStats) = { + /** Called to report processor stats. */ + protected def onReportStats(stats: ProcessorStats) = { } /** Loader to be used on a cache miss. The loader creates a new [[ScriptReducer]] for @@ -157,19 +186,19 @@ class PrecompiledScriptProcessor(val settings: ScriptProcessorSettings) { * calcF graphs. */ protected val cacheLoader = new CacheLoader[CacheKey, ScriptReducer]() { /** Internal counter of all load operations happening an different threads. */ - private val loadCounder = new AtomicInteger(1) + private val loadCounter = new AtomicInteger(1) override def load(key: CacheKey): ScriptReducer = { val r = trySoftForkable[ScriptReducer](whenSoftFork = WhenSoftForkReducer) { PrecompiledScriptReducer(key.ergoTreeBytes)(createIR()) }(key.vs) - val c = loadCounder.incrementAndGet() + val c = loadCounter.incrementAndGet() if (c > settings.reportingInterval) { - if (loadCounder.compareAndSet(c, 1)) { + if (loadCounter.compareAndSet(c, 1)) { // call reporting only if we was able to reset the counter // avoid double reporting - onReportStats(cache.stats()) + onReportStats(ProcessorStats(cache.stats(), getPredefStats())) } } @@ -192,14 +221,18 @@ class PrecompiledScriptProcessor(val settings: ScriptProcessorSettings) { * It first looks up for predefReducers, if not found it looks up in the cache. * If there is no cache entry, the `cacheLoader` is used to load a new `ScriptReducer`. * - * @param ergoTree a tree to lookup pre-compiled verifier. - * @return non-empty Nullable instance with verifier for the given tree, otherwise - * Nullable.None + * @param ergoTree a tree to lookup pre-compiled reducer. + * @return a reducer for the given tree + * May throw an exception if error happens and no soft-fork condition detected in `vs`. */ def getReducer(ergoTree: ErgoTree, vs: SigmaValidationSettings): ScriptReducer = { val key = CacheKey(ergoTree.bytes, vs) predefReducers.get(key) match { - case Nullable(r) => r + case Nullable(r) => + if (settings.recordCacheStats) { + r._2.incrementAndGet() // update hit counter + } + r._1 case _ => val r = try { cache.get(key) @@ -217,21 +250,4 @@ object PrecompiledScriptProcessor { val Default = new PrecompiledScriptProcessor( ScriptProcessorSettings(mutable.WrappedArray.empty[CacheKey])) - /** Script processor which uses [[CompiletimeIRContext]] to process graphs. */ - val WithCompiletimeIRContext = new PrecompiledScriptProcessor( - ScriptProcessorSettings( - predefScripts = mutable.WrappedArray.empty, - recordCacheStats = true, - reportingInterval = 10)) { - override protected def createIR(): IRContext = new CompiletimeIRContext - - override protected def onReportStats(stats: CacheStats): Unit = { - println(s"Cache Stats: $stats") - } - - override protected def onEvictedCacheEntry(key: CacheKey): Unit = { - val scriptHex = ErgoAlgos.encode(key.ergoTreeBytes.toArray) - println(s"Evicted: ${scriptHex}") - } - } } diff --git a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala index 2db655f1ae..97cf2eab3f 100644 --- a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala @@ -374,7 +374,7 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA } override protected def afterAll(): Unit = { - println(PrecompiledScriptProcessor.WithCompiletimeIRContext.cache.stats()) + println(ErgoLikeTestInterpreter.DefaultProcessorInTests.cache.stats()) } } diff --git a/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala b/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala index 4f1b87480a..46dbfd47ce 100644 --- a/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala +++ b/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala @@ -1,17 +1,57 @@ package sigmastate.helpers import org.ergoplatform._ -import sigmastate.eval.IRContext +import org.ergoplatform.settings.ErgoAlgos +import org.ergoplatform.validation.{ValidationRules, SigmaValidationSettings} +import sigmastate.eval.{IRContext, CompiletimeIRContext} import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv} -import sigmastate.interpreter.PrecompiledScriptProcessor +import sigmastate.interpreter.{CacheKey, PrecompiledScriptProcessor, ProcessorStats, ScriptProcessorSettings} -import scala.util.{Failure, Success} +import scala.util.{Success, Failure} /** Base class for interpreters used in tests. * @see derived classes */ class ErgoLikeTestInterpreter(implicit override val IR: IRContext) extends ErgoLikeInterpreter { override type CTX = ErgoLikeContext - override val precompiledScriptProcessor = PrecompiledScriptProcessor.WithCompiletimeIRContext + override val precompiledScriptProcessor = ErgoLikeTestInterpreter.DefaultProcessorInTests +} +object ErgoLikeTestInterpreter { + val predefScriptsInTests = Seq( + // for property("emission specification") + "100b0400040004e04e0580bcc1960b0580bcc1960b05808c8d9e02040204e04e04a00b040005808c8d9e02d1ec9683040193e4c6b2a57300000404a391a3e4c6a7040493c2a7c2b2a573010093958fa3730273039973049c73057e9a73069d99a3730773080599c1a7c1b2a5730900ed91a3e4c6a7040490c1a7730a" + ) + + /** A list of settings, one for each soft-fork. */ + val validationSettings: Seq[SigmaValidationSettings] = Array( + ValidationRules.currentSettings // for v4.0 + // add v5.0 settings here... + ) + + /** Script processor which uses [[CompiletimeIRContext]] to process graphs. */ + val DefaultProcessorInTests = { + val scriptKeys = predefScriptsInTests.flatMap { s => + val bytes = ErgoAlgos.decodeUnsafe(s) + validationSettings.map(vs => CacheKey(bytes, vs)) + } + + new PrecompiledScriptProcessor( + ScriptProcessorSettings( + predefScripts = scriptKeys, + maxCacheSize = 10, + recordCacheStats = true, + reportingInterval = 10)) { + override protected def createIR(): IRContext = new CompiletimeIRContext + + override protected def onReportStats(stats: ProcessorStats): Unit = { + println(s"Processor Stats: $stats") + } + + override protected def onEvictedCacheEntry(key: CacheKey): Unit = { + val scriptHex = ErgoAlgos.encode(key.ergoTreeBytes.toArray) + println(s"Evicted: ${scriptHex}") + } + } + } } class ErgoTransactionValidator(activatedVersion: Byte)(implicit IR: IRContext) { diff --git a/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala b/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala index e0f1fec347..d5ccce7931 100644 --- a/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/interpreter/PrecompiledScriptProcessorSpecification.scala @@ -38,4 +38,20 @@ class PrecompiledScriptProcessorSpecification extends SigmaDslTesting { case _ => false } ) } + + property("collects predef hit statistics") { + val predefTrees = predefScriptHexes.map { h => parseTree(h) } + val scriptKeys = predefTrees.map { t => CacheKey(t.bytes, ValidationRules.currentSettings) } + Array(false, true).foreach { recordStats => + val processor = new PrecompiledScriptProcessor( + ScriptProcessorSettings( + predefScripts = scriptKeys, + recordCacheStats = recordStats)) + predefTrees.foreach { t => + processor.getReducer(t, ValidationRules.currentSettings) should not be null + } + val expectedHits = if (recordStats) 1 else 0 + processor.getPredefStats() shouldBe Array.fill(scriptKeys.length)(expectedHits).toSeq + } + } } diff --git a/sigmastate/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala index abbf7a883c..99f9bb8a26 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala @@ -1,8 +1,10 @@ package sigmastate.utxo.examples import org.ergoplatform._ +import org.ergoplatform.settings.ErgoAlgos +import org.ergoplatform.validation.ValidationRules import scorex.util.ScorexLogging -import sigmastate.Values.{IntConstant, ErgoTree} +import sigmastate.Values.{ErgoTree, IntConstant} import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, SigmaTestingCommons} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.ContextExtension @@ -112,6 +114,7 @@ block 1600 in 1622 ms, 30000000000 coins remain, defs: 61661 AND(heightCorrect, heightIncreased, sameScriptRule, correctCoinsConsumed), BinAnd(heightIncreased, lastCoins) ).toSigmaProp + val tree = mkTestErgoTree(prop) val env = Map("fixedRatePeriod" -> s.fixedRatePeriod, "epochLength" -> s.epochLength, @@ -155,7 +158,7 @@ block 1600 in 1622 ms, 30000000000 coins remain, defs: 61661 val ut = if (emissionBox.value > s.oneEpochReduction) { val minerBox = new ErgoBoxCandidate(emissionAtHeight(height), minerProp, height, Colls.emptyColl, Map()) val newEmissionBox: ErgoBoxCandidate = - new ErgoBoxCandidate(emissionBox.value - minerBox.value, mkTestErgoTree(prop), height, Colls.emptyColl, Map(register -> IntConstant(height))) + new ErgoBoxCandidate(emissionBox.value - minerBox.value, tree, height, Colls.emptyColl, Map(register -> IntConstant(height))) UnsignedErgoLikeTransaction( IndexedSeq(new UnsignedInput(emissionBox.id)), @@ -178,7 +181,8 @@ block 1600 in 1622 ms, 30000000000 coins remain, defs: 61661 emissionBox, activatedVersionInTests, ContextExtension.empty) - val proverResult = prover.prove(emptyEnv + (ScriptNameProp -> "prove"), mkTestErgoTree(prop), context, ut.messageToSign).get + val proverResult = prover.prove( + emptyEnv + (ScriptNameProp -> "prove"), tree, context, ut.messageToSign).get ut.toSigned(IndexedSeq(proverResult)) } @@ -212,5 +216,9 @@ block 1600 in 1622 ms, 30000000000 coins remain, defs: 61661 } chainGen(genesisState, initialBox, 0, 1000000) + + println(s"Emission Tree: ${ErgoAlgos.encode(tree.bytes)}") + println(prover.precompiledScriptProcessor.getStats()) +// prover.precompiledScriptProcessor.getReducer(tree, ValidationRules.currentSettings) } } diff --git a/sigmastate/src/test/scala/sigmastate/utxo/examples/IcoExample.scala b/sigmastate/src/test/scala/sigmastate/utxo/examples/IcoExample.scala index 5132b8aa68..0cea1d9aae 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/examples/IcoExample.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/examples/IcoExample.scala @@ -9,8 +9,7 @@ import scorex.crypto.authds.avltree.batch._ import scorex.crypto.hash.{Digest32, Blake2b256} import sigmastate.Values._ import sigmastate._ -import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeTestProvingInterpreter, SigmaTestingCommons} -import sigmastate.helpers.ErgoLikeContextTesting +import sigmastate.helpers.{ErgoLikeContextTesting, ErgoLikeTestInterpreter, ErgoLikeTestProvingInterpreter, SigmaTestingCommons, ContextEnrichingTestProvingInterpreter} import sigmastate.helpers.TestingHelpers._ import sigmastate.interpreter.Interpreter.ScriptNameProp import sigmastate.lang.Terms._ @@ -557,7 +556,7 @@ class IcoExample extends SigmaTestingCommons /** This is the last executed test suite, so this method is executed after all tests. * We output statistics of how PrecompiledScriptProcessor cache was used. */ override protected def afterAll(): Unit = { - println(PrecompiledScriptProcessor.WithCompiletimeIRContext.cache.stats()) + println(ErgoLikeTestInterpreter.DefaultProcessorInTests.cache.stats()) } } From e16065977f171bfc1e987270846b679676b87e90 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 5 Mar 2021 14:08:35 +0300 Subject: [PATCH 23/26] i694-box-opt: soft-forkability test for PrecompiledScriptProcessor + cleanups --- .../sigmastate/interpreter/Interpreter.scala | 9 +++- .../PrecompiledScriptProcessor.scala | 31 +++++++---- .../SoftForkabilitySpecification.scala | 53 +++++++++++++------ .../helpers/ErgoTransactionValidator.scala | 2 +- .../examples/CoinEmissionSpecification.scala | 2 - 5 files changed, 67 insertions(+), 30 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index 00de22fe7c..b47138a1dd 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -13,7 +13,7 @@ import sigmastate.Values._ import sigmastate.eval.{IRContext, Evaluation} import sigmastate.lang.Terms.ValueOps import sigmastate.basics._ -import sigmastate.interpreter.Interpreter.{VerificationResult, ScriptEnv} +import sigmastate.interpreter.Interpreter.{VerificationResult, ScriptEnv, WhenSoftForkReductionResult} import sigmastate.lang.exceptions.{InterpreterException, CostLimitException} import sigmastate.serialization.{ValueSerializer, SigmaSerializer} import sigmastate.utxo.DeserializeContext @@ -133,7 +133,7 @@ trait Interpreter extends ScorexLogging { implicit val vs = context.validationSettings val maxCost = context.costLimit val initCost = context.initCost - trySoftForkable[ReductionResult](whenSoftFork = TrivialProp.TrueProp -> 0) { + trySoftForkable[ReductionResult](whenSoftFork = WhenSoftForkReductionResult) { val costingRes = doCostingEx(env, exp, true) val costF = costingRes.costF IR.onCostingResult(env, exp, costingRes) @@ -398,6 +398,11 @@ object Interpreter { */ val MaxSupportedScriptVersion: Byte = 1 // supported versions 0 and 1 + /** The result of script reduction when soft-fork condition is detected by the old node. + * This which case the script is reduced to trivial true proposition and takes up 0 cost. + */ + val WhenSoftForkReductionResult: ReductionResult = TrivialProp.TrueProp -> 0 + /** Executes the given `calcF` graph in the given context. * @param IR containier of the graph (see [[IRContext]]) * @param context script execution context (built from [[org.ergoplatform.ErgoLikeContext]]) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala index 23462f0fd3..9d7dce2b95 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala @@ -5,16 +5,17 @@ import java.util.concurrent.atomic.AtomicInteger import com.google.common.cache.{CacheBuilder, RemovalNotification, RemovalListener, LoadingCache, CacheLoader, CacheStats} import org.ergoplatform.settings.ErgoAlgos -import org.ergoplatform.validation.{ValidationRules, SigmaValidationSettings} +import org.ergoplatform.validation.SigmaValidationSettings import org.ergoplatform.validation.ValidationRules.{CheckCostFunc, CheckCalcFunc, trySoftForkable} import scalan.{AVHashMap, Nullable} -import sigmastate.{Values, TrivialProp} +import sigmastate.Values import sigmastate.Values.ErgoTree -import sigmastate.eval.{RuntimeIRContext, IRContext, CompiletimeIRContext} -import sigmastate.interpreter.Interpreter.ReductionResult +import sigmastate.eval.{RuntimeIRContext, IRContext} +import sigmastate.interpreter.Interpreter.{ReductionResult, WhenSoftForkReductionResult} import sigmastate.serialization.ErgoTreeSerializer import sigmastate.utils.Helpers._ import spire.syntax.all.cfor + import scala.collection.mutable /** A reducer which represents precompiled script reduction function. @@ -33,7 +34,7 @@ trait ScriptReducer { /** Used as a fallback reducer when precompilation failed due to soft-fork condition. */ case object WhenSoftForkReducer extends ScriptReducer { override def reduce(context: InterpreterContext): (Values.SigmaBoolean, Long) = { - TrivialProp.TrueProp -> 0 + WhenSoftForkReductionResult } } @@ -77,7 +78,7 @@ case class PrecompiledScriptReducer(scriptBytes: Seq[Byte])(implicit val IR: IRC implicit val vs = context.validationSettings val maxCost = context.costLimit val initCost = context.initCost - trySoftForkable[ReductionResult](whenSoftFork = TrivialProp.TrueProp -> 0) { + trySoftForkable[ReductionResult](whenSoftFork = WhenSoftForkReductionResult) { val costF = costingRes.costF val costingCtx = context.toSigmaContext(isCost = true) val estimatedCost = IR.checkCostWithContext(costingCtx, costF, maxCost, initCost).getOrThrow @@ -217,16 +218,28 @@ class PrecompiledScriptProcessor(val settings: ScriptProcessorSettings) { b.build(cacheLoader) } - /** Looks up verifier for the given ErgoTree using its 'bytes' property. - * It first looks up for predefReducers, if not found it looks up in the cache. - * If there is no cache entry, the `cacheLoader` is used to load a new `ScriptReducer`. + /** An overload version of the method which looks up reducer for the given ErgoTree + * using its 'bytes' property. See also `getReducer(key)`. * * @param ergoTree a tree to lookup pre-compiled reducer. + * @param vs validation settings which are used to detect soft-fork condition * @return a reducer for the given tree * May throw an exception if error happens and no soft-fork condition detected in `vs`. */ def getReducer(ergoTree: ErgoTree, vs: SigmaValidationSettings): ScriptReducer = { val key = CacheKey(ergoTree.bytes, vs) + getReducer(key) + } + + /** Looks up reducer for the given key using its 'ergoTreeBytes' property. + * It first looks up for predefReducers, if not found it looks up in the cache. + * If there is no cache entry, the `cacheLoader` is used to load a new `ScriptReducer`. + * + * @param key a script key to lookup pre-compiled reducer. + * @return a reducer for the given script + * May throw an exception if error happens and no soft-fork condition detected in `key.vs`. + */ + def getReducer(key: CacheKey): ScriptReducer = { predefReducers.get(key) match { case Nullable(r) => if (settings.recordCacheStats) { diff --git a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala index 97cf2eab3f..7f191682d7 100644 --- a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala @@ -1,7 +1,7 @@ package sigmastate import org.ergoplatform._ -import org.ergoplatform.validation.ValidationRules.{CheckValidOpCode, trySoftForkable, CheckCostFuncOperation, CheckTupleType, CheckCostFunc, CheckDeserializedScriptIsSigmaProp, _} +import org.ergoplatform.validation.ValidationRules._ import org.ergoplatform.validation._ import org.scalatest.BeforeAndAfterAll import sigmastate.SPrimType.MaxPrimTypeCode @@ -10,11 +10,12 @@ import sigmastate.Values.{UnparsedErgoTree, NotReadyValueInt, ByteArrayConstant, import sigmastate.eval.Colls import sigmastate.helpers.{ErgoLikeContextTesting, ErgoLikeTestProvingInterpreter, ErgoLikeTestInterpreter} import sigmastate.helpers.TestingHelpers._ -import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv} -import sigmastate.interpreter.{ProverResult, ContextExtension, PrecompiledScriptProcessor} +import sigmastate.interpreter.Interpreter.{ScriptNameProp, emptyEnv, WhenSoftForkReductionResult} +import sigmastate.interpreter.{ProverResult, Interpreter, WhenSoftForkReducer, ContextExtension, CacheKey} import sigmastate.lang.Terms._ import sigmastate.lang.exceptions.{SerializerException, SigmaException, InterpreterException, CosterException} import sigmastate.serialization.OpCodes.{OpCodeExtra, LastConstantCode, OpCode} +import sigmastate.serialization.SigmaSerializer.startReader import sigmastate.serialization._ import sigmastate.utxo.{DeserializeContext, SelectField} import special.sigma.SigmaTestingData @@ -83,7 +84,7 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA property("node v1, received tx with script v1, incorrect script") { assertExceptionThrown({ // CheckDeserializedScriptIsSigmaProp rule violated - ErgoLikeTransaction.serializer.parse(SigmaSerializer.startReader(invalidTxV1bytes)) + ErgoLikeTransaction.serializer.parse(startReader(invalidTxV1bytes)) }, { case se: SerializerException if se.cause.isDefined => val ve = se.cause.get.asInstanceOf[ValidationException] @@ -94,7 +95,7 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA property("node v1, received tx with script v1, correct script") { // able to parse - val tx = ErgoLikeTransaction.serializer.parse(SigmaSerializer.startReader(txV1bytes)) + val tx = ErgoLikeTransaction.serializer.parse(startReader(txV1bytes)) // validating script proveAndVerifyTx("propV1", tx, vs) @@ -105,7 +106,7 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA case object Height2 extends NotReadyValueInt with ValueCompanion { override def companion = this override val opCode: OpCode = Height2Code // use reserved code - def opType = SFunc(SContext, SInt) + override val opType = SFunc(SContext, SInt) } val Height2Ser = CaseObjectSerialization(Height2, Height2) @@ -166,7 +167,7 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA // v1 node should fail assertExceptionThrown( { - val r = SigmaSerializer.startReader(v2tree_withoutSize_bytes) + val r = startReader(v2tree_withoutSize_bytes) ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(r, SigmaSerializer.MaxPropositionSize) }, { @@ -179,7 +180,7 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA runOnV2Node { assertExceptionThrown( { - val r = SigmaSerializer.startReader(v2tree_withoutSize_bytes) + val r = startReader(v2tree_withoutSize_bytes) ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(r, SigmaSerializer.MaxPropositionSize) }, { @@ -194,7 +195,7 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA val txV2bytes = txV2messageToSign // parse and validate tx with v2 settings - val tx = ErgoLikeTransaction.serializer.parse(SigmaSerializer.startReader(txV2bytes)) + val tx = ErgoLikeTransaction.serializer.parse(startReader(txV2bytes)) proveAndVerifyTx("propV2", tx, v2vs) // and with v1 settings @@ -222,7 +223,7 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA property("node v1, no soft-fork, received script v2, raise error") { assertExceptionThrown({ val invalidTxV2bytes = runOnV2Node { invalidTxV2.messageToSign } - ErgoLikeTransaction.serializer.parse(SigmaSerializer.startReader(invalidTxV2bytes)) + ErgoLikeTransaction.serializer.parse(startReader(invalidTxV2bytes)) },{ case se: SerializerException if se.cause.isDefined => val ve = se.cause.get.asInstanceOf[ValidationException] @@ -238,7 +239,7 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA runOnV2Node { // parse and validate tx with v2 script (since serializers were extended to v2) - val tx = ErgoLikeTransaction.serializer.parse(SigmaSerializer.startReader(txV2bytes)) + val tx = ErgoLikeTransaction.serializer.parse(startReader(txV2bytes)) tx shouldBe txV2 // fails evaluation of v2 script (due to the rest of the implementation is still v1) @@ -304,7 +305,7 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA val typeBytes = Array[Byte](MaxPrimTypeCode) val v2vs = vs.updated(CheckPrimitiveTypeCode.id, ChangedRule(Array[Byte](MaxPrimTypeCode))) checkRule(CheckPrimitiveTypeCode, v2vs, { - val r = SigmaSerializer.startReader(typeBytes) + val r = startReader(typeBytes) TypeSerializer.deserialize(r) }) } @@ -313,7 +314,7 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA val typeBytes = Array[Byte](newTypeCode) val v2vs = vs.updated(CheckTypeCode.id, ChangedRule(Array[Byte](newTypeCode))) checkRule(CheckTypeCode, v2vs, { - val r = SigmaSerializer.startReader(typeBytes) + val r = startReader(typeBytes) TypeSerializer.deserialize(r) }) } @@ -323,7 +324,7 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA val dataBytes = Array[Byte](1, 2, 3) // any random bytes will work val v2vs = vs.updated(CheckSerializableTypeCode.id, ReplacedRule(0)) checkRule(CheckSerializableTypeCode, v2vs, { - val r = SigmaSerializer.startReader(dataBytes) + val r = startReader(dataBytes) DataSerializer.deserialize(newType, r) }) } @@ -348,11 +349,16 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA property("CheckCostFuncOperation rule") { val exp = Height - val v2vs = vs.updated(CheckCostFuncOperation.id, + val v2vs = vs.updated( + CheckCostFuncOperation.id, ChangedRule(CheckCostFuncOperation.encodeVLQUShort(Seq(OpCodes.toExtra(Height.opCode))))) checkRule(CheckCostFuncOperation, v2vs, { val costingRes = IR.doCostingEx(emptyEnv, exp, okRemoveIsProven = false) - // use calcF as costing function to have forbidden (not allowed) op (Height) in the costing function + // We need to exercise CheckCostFunc rule. + // The calcF graph have Height operation in it, which is not allowed to be in cost graph. + // This leads to a ValidationException to be thrown with the CheckCostFunc rule in it. + // And the rule is changed in v2vs, so Height is allowed, which is interpreted as + // soft-fork condition CheckCostFunc(IR)(IR.asRep[Any => Int](costingRes.calcF)) }) } @@ -373,6 +379,21 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA }) } + property("PrecompiledScriptProcessor is soft-forkable") { + val p = ErgoLikeTestInterpreter.DefaultProcessorInTests + val v1key = CacheKey(propV2treeBytes, vs) + checkRule(CheckValidOpCode, v2vs, { + p.getReducer(v1key) + }) + + val v2key = CacheKey(propV2treeBytes, v2vs) + val r = p.getReducer(v2key) + r shouldBe WhenSoftForkReducer + + val ctx = createContext(blockHeight, txV2, v2vs) + r.reduce(ctx) shouldBe WhenSoftForkReductionResult + } + override protected def afterAll(): Unit = { println(ErgoLikeTestInterpreter.DefaultProcessorInTests.cache.stats()) } diff --git a/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala b/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala index 46dbfd47ce..74cdbcf440 100644 --- a/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala +++ b/sigmastate/src/test/scala/sigmastate/helpers/ErgoTransactionValidator.scala @@ -28,7 +28,7 @@ object ErgoLikeTestInterpreter { ) /** Script processor which uses [[CompiletimeIRContext]] to process graphs. */ - val DefaultProcessorInTests = { + val DefaultProcessorInTests: PrecompiledScriptProcessor = { val scriptKeys = predefScriptsInTests.flatMap { s => val bytes = ErgoAlgos.decodeUnsafe(s) validationSettings.map(vs => CacheKey(bytes, vs)) diff --git a/sigmastate/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala b/sigmastate/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala index 99f9bb8a26..c3cff93dd8 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/examples/CoinEmissionSpecification.scala @@ -2,7 +2,6 @@ package sigmastate.utxo.examples import org.ergoplatform._ import org.ergoplatform.settings.ErgoAlgos -import org.ergoplatform.validation.ValidationRules import scorex.util.ScorexLogging import sigmastate.Values.{ErgoTree, IntConstant} import sigmastate.helpers.{ContextEnrichingTestProvingInterpreter, ErgoLikeContextTesting, SigmaTestingCommons} @@ -219,6 +218,5 @@ block 1600 in 1622 ms, 30000000000 coins remain, defs: 61661 println(s"Emission Tree: ${ErgoAlgos.encode(tree.bytes)}") println(prover.precompiledScriptProcessor.getStats()) -// prover.precompiledScriptProcessor.getReducer(tree, ValidationRules.currentSettings) } } From d36bf2d1e42f18f45de8a2e2f2f86c22f67d87ff Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 5 Mar 2021 14:25:28 +0300 Subject: [PATCH 24/26] i694-box-opt: fixed typos --- .../src/main/scala/sigmastate/interpreter/Interpreter.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index b47138a1dd..7817701661 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -398,8 +398,8 @@ object Interpreter { */ val MaxSupportedScriptVersion: Byte = 1 // supported versions 0 and 1 - /** The result of script reduction when soft-fork condition is detected by the old node. - * This which case the script is reduced to trivial true proposition and takes up 0 cost. + /** The result of script reduction when soft-fork condition is detected by the old node, + * in which case the script is reduced to the trivial true proposition and takes up 0 cost. */ val WhenSoftForkReductionResult: ReductionResult = TrivialProp.TrueProp -> 0 From 822778878beb5658f258f83677985ef42a9a632d Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 10 Mar 2021 13:13:11 +0300 Subject: [PATCH 25/26] i694-box-opt: fixed typos and cleanups --- .../src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala | 2 -- .../src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala | 2 +- .../src/main/scala/sigmastate/interpreter/Interpreter.scala | 4 ++-- .../sigmastate/interpreter/PrecompiledScriptProcessor.scala | 4 ++-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala index 04b9a46d8b..82f6f5f877 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala @@ -4,8 +4,6 @@ import java.util import org.ergoplatform.ErgoBox._ import org.ergoplatform.settings.ErgoAlgos -import scalan.Nullable -import scorex.crypto.hash.Digest32 import scorex.util.{bytesToId, ModifierId} import sigmastate.Values._ import sigmastate._ diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala index 3d8a6dbc57..c747ff9684 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala @@ -40,7 +40,7 @@ trait ErgoLikeTransactionTemplate[IT <: UnsignedInput] { require(outputCandidates.size <= Short.MaxValue) - /** Indentifier of this transaction as state Modifier. */ + /** Identifier of this transaction as state Modifier. */ val id: ModifierId lazy val outputs: IndexedSeq[ErgoBox] = diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala index 7817701661..0f213abcbb 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala @@ -190,7 +190,7 @@ trait Interpreter extends ScorexLogging { } } - /** Perfroms reduction of proposition which contains deserialization operations. */ + /** Performs reduction of proposition which contains deserialization operations. */ private def reductionWithDeserialize(prop: SigmaPropValue, context: CTX, env: ScriptEnv) = { @@ -404,7 +404,7 @@ object Interpreter { val WhenSoftForkReductionResult: ReductionResult = TrivialProp.TrueProp -> 0 /** Executes the given `calcF` graph in the given context. - * @param IR containier of the graph (see [[IRContext]]) + * @param IR container of the graph (see [[IRContext]]) * @param context script execution context (built from [[org.ergoplatform.ErgoLikeContext]]) * @param calcF graph which represents a reduction function from Context to SigmaProp. * @return a reduction result diff --git a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala index 9d7dce2b95..d5124b5e43 100644 --- a/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala +++ b/sigmastate/src/main/scala/sigmastate/interpreter/PrecompiledScriptProcessor.scala @@ -52,12 +52,12 @@ case object WhenSoftForkReducer extends ScriptReducer { case class PrecompiledScriptReducer(scriptBytes: Seq[Byte])(implicit val IR: IRContext) extends ScriptReducer { - /** The following operations create [[RCostingResultEx]] structure for the given + /** The following operations create [[IR.RCostingResultEx]] structure for the given * `scriptBytes` and they should be the same as in `reduceToCrypto` method. * This can be viewed as ahead of time pre-compilation of the cost and calc graphs * which are reused over many invocations of the `reduce` method. */ - val costingRes = { + val costingRes: IR.RCostingResultEx[Any] = { val tree = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(scriptBytes.toArray) val prop = tree.toProposition(tree.isConstantSegregation) val validProp = Interpreter.toValidScriptType(prop) From 252071eda9e7e76621363622c0e750de6026a14e Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Wed, 10 Mar 2021 13:42:10 +0300 Subject: [PATCH 26/26] i694-box-opt: use getStats() method --- .../test/scala/sigmastate/SoftForkabilitySpecification.scala | 2 +- .../test/scala/sigmastate/TestingInterpreterSpecification.scala | 2 +- .../src/test/scala/sigmastate/utxo/examples/IcoExample.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala index 7f191682d7..2574a009be 100644 --- a/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/SoftForkabilitySpecification.scala @@ -395,7 +395,7 @@ class SoftForkabilitySpecification extends SigmaTestingData with BeforeAndAfterA } override protected def afterAll(): Unit = { - println(ErgoLikeTestInterpreter.DefaultProcessorInTests.cache.stats()) + println(ErgoLikeTestInterpreter.DefaultProcessorInTests.getStats()) } } diff --git a/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala b/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala index e0c40c3fec..47f9fc457d 100644 --- a/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala +++ b/sigmastate/src/test/scala/sigmastate/TestingInterpreterSpecification.scala @@ -380,7 +380,7 @@ class TestingInterpreterSpecification extends SigmaTestingCommons } override protected def afterAll(): Unit = { - println(processor.cache.stats()) + println(processor.getStats()) } } diff --git a/sigmastate/src/test/scala/sigmastate/utxo/examples/IcoExample.scala b/sigmastate/src/test/scala/sigmastate/utxo/examples/IcoExample.scala index 0cea1d9aae..039ec873af 100644 --- a/sigmastate/src/test/scala/sigmastate/utxo/examples/IcoExample.scala +++ b/sigmastate/src/test/scala/sigmastate/utxo/examples/IcoExample.scala @@ -556,7 +556,7 @@ class IcoExample extends SigmaTestingCommons /** This is the last executed test suite, so this method is executed after all tests. * We output statistics of how PrecompiledScriptProcessor cache was used. */ override protected def afterAll(): Unit = { - println(ErgoLikeTestInterpreter.DefaultProcessorInTests.cache.stats()) + println(ErgoLikeTestInterpreter.DefaultProcessorInTests.getStats()) } }