Skip to content

Commit

Permalink
Merge pull request #704 from ScorexFoundation/v4.0
Browse files Browse the repository at this point in the history
Release v4.0
  • Loading branch information
aslesarenko authored Jan 5, 2021
2 parents 973f850 + dfce8f8 commit d38fe09
Show file tree
Hide file tree
Showing 66 changed files with 980 additions and 398 deletions.
2 changes: 1 addition & 1 deletion ci/ci.jvmopts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
-Xmx4g
-Xmx6g
-Xss2m
-XX:+CMSClassUnloadingEnabled
-XX:+UseConcMarkSweepGC
Expand Down
106 changes: 70 additions & 36 deletions sigmastate/src/main/scala/org/ergoplatform/ErgoLikeContext.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,40 @@ import sigmastate.serialization.OpCodes
import sigmastate.serialization.OpCodes.OpCode
import special.collection.Coll
import special.sigma
import special.sigma.{AnyValue, Header, PreHeader}
import special.sigma.{AnyValue, PreHeader, Header}
import spire.syntax.all.cfor

/**
* TODO lastBlockUtxoRoot should be calculated from headers if it is nonEmpty
/** Represents a script evaluation context to be passed to a prover and a verifier to execute and
* validate guarding proposition of input boxes of a transaction.
*
* @param selfIndex - index of the box in `boxesToSpend` that contains the script we're evaluating
* @param lastBlockUtxoRoot - state root before current block application
* @param headers - fixed number of last block headers in descending order (first header is the newest one)
* @param preHeader - fields of block header with the current `spendingTransaction`, that can be predicted
* by a miner before it's formation
* @param dataBoxes - boxes, that corresponds to id's of `spendingTransaction.dataInputs`
* @param boxesToSpend - boxes, that corresponds to id's of `spendingTransaction.inputs`
* @param spendingTransaction - transaction that contains `self` box
* @param extension - prover-defined key-value pairs, that may be used inside a script
* @param validationSettings validataion parameters passed to Interpreter.verify to detect soft-fork conditions
* @param costLimit hard limit on accumulated execution cost, if exceeded lead to CostLimitException to be thrown
* @param initCost initial value of execution cost already accumulated before Interpreter.verify is called
* @param selfIndex - index of the box in `boxesToSpend` that contains the script we're evaluating
* @param lastBlockUtxoRoot - state root before current block application
* @param headers - fixed number of last block headers in descending order (first header is the newest one)
* @param preHeader - fields of block header with the current `spendingTransaction`, that can be predicted
* by a miner before it's formation
* @param dataBoxes - boxes, that corresponds to id's of `spendingTransaction.dataInputs`
* @param boxesToSpend - boxes, that corresponds to id's of `spendingTransaction.inputs`
* @param spendingTransaction - transaction that contains `self` box
* @param extension - prover-defined key-value pairs, that may be used inside a script
* @param validationSettings validation parameters passed to Interpreter.verify to detect soft-fork conditions
* @param costLimit hard limit on accumulated execution cost, if exceeded lead to CostLimitException to be thrown
* @param initCost initial value of execution cost already accumulated before Interpreter.verify is called
* @param activatedScriptVersion Maximum version of ErgoTree currently activated on the network.
* The activation is performed via miners voting.
* For verification of *mined* blocks this parameter should be passed according
* to the latest voted (activated) script version on the network.
* However this is not the case for *candidate* blocks.
* When `activatedScriptVersion > Interpreter.MaxSupportedScriptVersion`
* then the interpreter accept script without verification which is not
* what should happen for *candidate* blocks.
* This means Ergo node should always pass Interpreter.MaxSupportedScriptVersion
* as a value of ErgoLikeContext.activatedScriptVersion during
* verification of candidate blocks (which is a default).
* The following values are used for current and upcoming forks:
* - version 3.x this value must be 0
* - in v4.0 must be 1
* - in v5.x must be 2
* etc.
*/
class ErgoLikeContext(val lastBlockUtxoRoot: AvlTreeData,
val headers: Coll[Header],
Expand All @@ -42,8 +58,10 @@ class ErgoLikeContext(val lastBlockUtxoRoot: AvlTreeData,
val extension: ContextExtension,
val validationSettings: SigmaValidationSettings,
val costLimit: Long,
val initCost: Long
val initCost: Long,
val activatedScriptVersion: Byte
) extends InterpreterContext {
// TODO lastBlockUtxoRoot should be calculated from headers if it is nonEmpty

/* NOHF PROOF:
Added: assert(preHeader != null)
Expand Down Expand Up @@ -93,30 +111,19 @@ class ErgoLikeContext(val lastBlockUtxoRoot: AvlTreeData,
val self: ErgoBox = boxesToSpend(selfIndex)

override def withCostLimit(newCostLimit: Long): ErgoLikeContext =
new ErgoLikeContext(
lastBlockUtxoRoot, headers, preHeader,
dataBoxes, boxesToSpend, spendingTransaction, selfIndex, extension, validationSettings, newCostLimit, initCost)
ErgoLikeContext.copy(this)(costLimit = newCostLimit)

override def withInitCost(newCost: Long): ErgoLikeContext =
new ErgoLikeContext(
lastBlockUtxoRoot, headers, preHeader,
dataBoxes, boxesToSpend, spendingTransaction, selfIndex, extension, validationSettings, costLimit, newCost)
override def withInitCost(newInitCost: Long): ErgoLikeContext =
ErgoLikeContext.copy(this)(initCost = newInitCost)

override def withValidationSettings(newVs: SigmaValidationSettings): ErgoLikeContext =
new ErgoLikeContext(
lastBlockUtxoRoot, headers, preHeader,
dataBoxes, boxesToSpend, spendingTransaction, selfIndex, extension, newVs, costLimit, initCost)
ErgoLikeContext.copy(this)(validationSettings = newVs)

override def withExtension(newExtension: ContextExtension): ErgoLikeContext =
new ErgoLikeContext(
lastBlockUtxoRoot, headers, preHeader,
dataBoxes, boxesToSpend, spendingTransaction, selfIndex, newExtension, validationSettings, costLimit, initCost)
ErgoLikeContext.copy(this)(extension = newExtension)

def withTransaction(newSpendingTransaction: ErgoLikeTransactionTemplate[_ <: UnsignedInput]): ErgoLikeContext =
new ErgoLikeContext(
lastBlockUtxoRoot, headers, preHeader,
dataBoxes, boxesToSpend, newSpendingTransaction, selfIndex, extension, validationSettings, costLimit, initCost)

ErgoLikeContext.copy(this)(spendingTransaction = newSpendingTransaction)

override def toSigmaContext(isCost: Boolean, extensions: Map[Byte, AnyValue] = Map()): sigma.Context = {
import Evaluation._
Expand Down Expand Up @@ -164,22 +171,26 @@ class ErgoLikeContext(val lastBlockUtxoRoot: AvlTreeData,
extension == that.extension &&
validationSettings == that.validationSettings &&
costLimit == that.costLimit &&
initCost == that.initCost
initCost == that.initCost &&
activatedScriptVersion == that.activatedScriptVersion
case _ => false
}

def canEqual(other: Any): Boolean = other.isInstanceOf[ErgoLikeContext]

override def hashCode(): Int = {
val state = Array(lastBlockUtxoRoot, headers, preHeader, dataBoxes, boxesToSpend, spendingTransaction, selfIndex, extension, validationSettings, costLimit, initCost)
val state = Array(
lastBlockUtxoRoot, headers, preHeader, dataBoxes, boxesToSpend, spendingTransaction,
selfIndex, extension, validationSettings, costLimit, initCost,
activatedScriptVersion)
var hashCode = 0
cfor(0)(_ < state.length, _ + 1) { i =>
hashCode = 31 * hashCode + state(i).hashCode
}
hashCode
}

override def toString = s"ErgoLikeContext(lastBlockUtxoRoot=$lastBlockUtxoRoot, headers=$headers, preHeader=$preHeader, dataBoxes=$dataBoxes, boxesToSpend=$boxesToSpend, spendingTransaction=$spendingTransaction, selfIndex=$selfIndex, extension=$extension, validationSettings=$validationSettings, costLimit=$costLimit, initCost=$initCost)"
override def toString = s"ErgoLikeContext(lastBlockUtxoRoot=$lastBlockUtxoRoot, headers=$headers, preHeader=$preHeader, dataBoxes=$dataBoxes, boxesToSpend=$boxesToSpend, spendingTransaction=$spendingTransaction, selfIndex=$selfIndex, extension=$extension, validationSettings=$validationSettings, costLimit=$costLimit, initCost=$initCost, activatedScriptVersion=$activatedScriptVersion)"
}

object ErgoLikeContext {
Expand All @@ -188,6 +199,29 @@ object ErgoLikeContext {

/** Maximimum number of headers in `headers` collection of the context. */
val MaxHeaders = SigmaConstants.MaxHeaders.value

/** Copies the given context allowing also to update fields.
* NOTE: it can be used ONLY for instances of ErgoLikeContext.
* @tparam T used here to limit use of this method to only ErgoLikeContext instances
* @return a new instance of [[ErgoLikeContext]]. */
@inline def copy[T >: ErgoLikeContext <: ErgoLikeContext](ctx: T)(
lastBlockUtxoRoot: AvlTreeData = ctx.lastBlockUtxoRoot,
headers: Coll[Header] = ctx.headers,
preHeader: PreHeader = ctx.preHeader,
dataBoxes: IndexedSeq[ErgoBox] = ctx.dataBoxes,
boxesToSpend: IndexedSeq[ErgoBox] = ctx.boxesToSpend,
spendingTransaction: ErgoLikeTransactionTemplate[_ <: UnsignedInput] = ctx.spendingTransaction,
selfIndex: Int = ctx.selfIndex,
extension: ContextExtension = ctx.extension,
validationSettings: SigmaValidationSettings = ctx.validationSettings,
costLimit: Long = ctx.costLimit,
initCost: Long = ctx.initCost,
activatedScriptVersion: Byte = ctx.activatedScriptVersion): ErgoLikeContext = {
new ErgoLikeContext(
lastBlockUtxoRoot, headers, preHeader, dataBoxes, boxesToSpend,
spendingTransaction, selfIndex, extension, validationSettings, costLimit, initCost,
activatedScriptVersion)
}
}

/** When interpreted evaluates to a ByteArrayConstant built from Context.minerPubkey */
Expand Down
11 changes: 8 additions & 3 deletions sigmastate/src/main/scala/org/ergoplatform/JsonCodecs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,8 @@ trait JsonCodecs {
"extension" -> ctx.extension.asJson,
"validationSettings" -> ctx.validationSettings.asJson,
"costLimit" -> ctx.costLimit.asJson,
"initCost" -> ctx.initCost.asJson
"initCost" -> ctx.initCost.asJson,
"scriptVersion" -> ctx.activatedScriptVersion.asJson
)
})

Expand All @@ -417,7 +418,11 @@ trait JsonCodecs {
validationSettings <- cursor.downField("validationSettings").as[SigmaValidationSettings]
costLimit <- cursor.downField("costLimit").as[Long]
initCost <- cursor.downField("initCost").as[Long]
} yield new ErgoLikeContext(lastBlockUtxoRoot, Colls.fromArray(headers.toArray), preHeader,
dataBoxes, boxesToSpend, spendingTransaction, selfIndex, extension, validationSettings, costLimit, initCost)
version <- cursor.downField("scriptVersion").as[Byte]
} yield new ErgoLikeContext(
lastBlockUtxoRoot, Colls.fromArray(headers.toArray), preHeader,
dataBoxes, boxesToSpend, spendingTransaction, selfIndex, extension,
validationSettings, costLimit, initCost, version
)
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ case class ValidationException(message: String, rule: ValidationRule, args: Seq[
override def fillInStackTrace(): Throwable = this // to avoid spending time on recording stack trace
}

/** All validation rules which are used to check soft-forkable conditions. Each validation
* rule throws a [[org.ergoplatform.validation.ValidationException]]. Each
* ValidationException can be caught and handled with respect to
* [[SigmaValidationSettings]], which can be changed by miners via voting.
* Thus, the behavior of the rules can be overridden without breaking consensus.
*/
object ValidationRules {
/** The id of the first validation rule. Can be used as the beginning of the rules id range. */
val FirstRuleId = 1000.toShort
Expand Down
31 changes: 30 additions & 1 deletion sigmastate/src/main/scala/sigmastate/Values.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import sigmastate.lang.SourceContext
import special.collection.Coll

import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer

object Values {

Expand Down Expand Up @@ -1043,10 +1042,33 @@ object Values {
/** Default header with constant segregation enabled. */
val ConstantSegregationHeader: Byte = (DefaultHeader | ConstantSegregationFlag).toByte

/** @return true if the constant segregation flag is set to 1 in the given header byte. */
@inline final def isConstantSegregation(header: Byte): Boolean = (header & ConstantSegregationFlag) != 0

/** @return true if the size flag is set to 1 in the given header byte. */
@inline final def hasSize(header: Byte): Boolean = (header & SizeFlag) != 0

/** @return a value of the version bits from the given header byte. */
@inline final def getVersion(header: Byte): Byte = (header & VersionMask).toByte

/** Update the version bits of the given header byte with the given version value. */
@inline final def updateVersionBits(header: Byte, version: Byte): Byte = {
require(version < 8, s"ErgoTree.version should be < 8: $version")
(header | version).toByte
}

/** Creates valid header byte with the given version.
* The SizeFlag is set if version > 0 */
@inline def headerWithVersion(version: Byte): Byte = {
// take default header and embedd the given version in it
var h = updateVersionBits(DefaultHeader, version)
if (version > 0) {
// set SizeFlag if version is greater then 0 (see require() in ErgoTree constructor)
h = (h | ErgoTree.SizeFlag).toByte
}
h
}

def substConstants(root: SValue, constants: IndexedSeq[Constant[SType]]): SValue = {
val store = new ConstantStore(constants)
val substRule = strategy[Any] {
Expand All @@ -1066,6 +1088,9 @@ object Values {
def withoutSegregation(root: SigmaPropValue): ErgoTree =
ErgoTree(ErgoTree.DefaultHeader, EmptyConstants, root)

def withoutSegregation(headerFlags: Byte, root: SigmaPropValue): ErgoTree =
ErgoTree((ErgoTree.DefaultHeader | headerFlags).toByte, EmptyConstants, root)

implicit def fromProposition(prop: SigmaPropValue): ErgoTree = {
prop match {
case SigmaPropConstant(_) => withoutSegregation(prop)
Expand All @@ -1077,6 +1102,10 @@ object Values {
withoutSegregation(pk.toSigmaProp)
}

def fromSigmaBoolean(headerFlags: Byte, pk: SigmaBoolean): ErgoTree = {
withoutSegregation(headerFlags, pk.toSigmaProp)
}

/** Build ErgoTree via serialization of the value with ConstantSegregationHeader, constants segregated
* from the tree and ConstantPlaceholders referring to the segregated constants.
*
Expand Down
35 changes: 33 additions & 2 deletions sigmastate/src/main/scala/sigmastate/interpreter/Interpreter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import sigmastate.basics.DLogProtocol.{DLogInteractiveProver, FirstDLogProverMes
import scorex.util.ScorexLogging
import sigmastate.SCollection.SByteArray
import sigmastate.Values._
import sigmastate.eval.{IRContext, Sized, Evaluation}
import sigmastate.eval.{IRContext, Evaluation}
import sigmastate.lang.Terms.ValueOps
import sigmastate.basics._
import sigmastate.interpreter.Interpreter.{ScriptEnv, VerificationResult}
Expand All @@ -22,7 +22,7 @@ import org.ergoplatform.validation.ValidationRules._
import scalan.util.BenchmarkUtil
import sigmastate.utils.Helpers._

import scala.util.Try
import scala.util.{Try, Success}

trait Interpreter extends ScorexLogging {

Expand Down Expand Up @@ -231,6 +231,26 @@ trait Interpreter extends ScorexLogging {
proof: Array[Byte],
message: Array[Byte]): Try[VerificationResult] = {
val (res, t) = BenchmarkUtil.measureTime(Try {
// TODO v5.0: the condition below should be revised if necessary
// The following conditions define behavior which depend on the version of ergoTree
// This works in addition to more fine-grained soft-forkability mechanism implemented
// using ValidationRules (see trySoftForkable method call here and in reduceToCrypto).
if (context.activatedScriptVersion > Interpreter.MaxSupportedScriptVersion) {
// > 90% has already switched to higher version, accept without verification
// NOTE: this path should never be taken for validation of candidate blocks
// in which case Ergo node should always pass Interpreter.MaxSupportedScriptVersion
// as the value of ErgoLikeContext.activatedScriptVersion.
// see also ErgoLikeContext ScalaDoc.
return Success(true -> context.initCost)
} else {
// activated version is within the supported range [0..MaxSupportedScriptVersion]
// however
if (ergoTree.version > context.activatedScriptVersion) {
throw new InterpreterException(
s"ErgoTree version ${ergoTree.version} is higher than activated ${context.activatedScriptVersion}")
}
// else proceed normally
}

val initCost = JMath.addExact(ergoTree.complexity.toLong, context.initCost)
val remainingLimit = context.costLimit - initCost
Expand Down Expand Up @@ -346,6 +366,17 @@ object Interpreter {
val emptyEnv: ScriptEnv = Map.empty[String, Any]
val ScriptNameProp = "ScriptName"

/** Maximum version of ErgoTree supported by this interpreter release.
* See version bits in `ErgoTree.header` for more details.
* This value should be increased with each new protocol update via soft-fork.
* The following values are used for current and upcoming forks:
* - version 3.x this value must be 0
* - in v4.0 must be 1
* - in v5.x must be 2
* etc.
*/
val MaxSupportedScriptVersion: Byte = 1 // supported versions 0 and 1

def error(msg: String) = throw new InterpreterException(msg)

}
Loading

0 comments on commit d38fe09

Please sign in to comment.