Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finalize v5.0: fix XorOf operation #760

Merged
merged 6 commits into from
Jan 28, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions common/src/main/scala/sigmastate/VersionContext.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package sigmastate

import scala.util.DynamicVariable

/** Represent currently activated protocol version and currently executed ErgoTree version.
*
* This parameters, once set in DynamicVariable can be accessed everywhere on the current
* thread.
*
* @param activatedVersion Currently activated script version == Block.headerVersion - 1
* @param ergoTreeVersion version of the currently executed ErgoTree
*
* @see
*/
case class VersionContext(activatedVersion: Byte, ergoTreeVersion: Byte)

object VersionContext {
/** 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 = 2 // supported versions 0, 1, 2

/** The first version of ErgoTree starting from which the JIT costing interpreter must be used.
* It must also be used for all subsequent versions (3, 4, etc).
*/
val JitActivationVersion: Byte = 2

/** Universally accessible version context which is used to version the code
* across the whole repository.
*
* The default value represent activated Ergo protocol and highest ErgoTree version.
*/
private val _versionContext: DynamicVariable[VersionContext] =
new DynamicVariable[VersionContext](VersionContext(
activatedVersion = 1/* v4.x */,
ergoTreeVersion = 1
))

/** Returns the current VersionContext attached to the current thread.
* Each thread can have only one current version context at any time, which can be
* changed using `withVersions` method.
*
* @see withVersions()
*/
def current: VersionContext = {
val ctx = _versionContext.value
if (ctx == null)
throw new IllegalStateException(
s"VersionContext is not specified on thread ${Thread.currentThread().getId}")
ctx
}

/** Executes the given block under the given version context attached to the current thread.
*
* The typical usage is to use `VersionContext.withVersions(activatedVersion,
* treeVersion) {...}` when the block of code needs to be executed with the given
* versions.
*
* For example, sigmastate.Interpreter uses it to execute operations according to the
* necessary versions of Ergo protocol and ErgoTree.
*
* @param activatedVersion Currently activated script version == Block.headerVersion - 1
* @param ergoTreeVersion ErgoTree version to be set on the current thread
* @param block block of code to execute
* @return result of block execution
*/
def withVersions[T](activatedVersion: Byte, ergoTreeVersion: Byte)(block: => T): T =
_versionContext.withValue(VersionContext(activatedVersion, ergoTreeVersion))(block)
}
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ class CollOverArrayBuilder extends CollBuilder {
override def Monoids: MonoidBuilder = new MonoidBuilderInst

@inline override def pairColl[@specialized A, @specialized B](as: Coll[A], bs: Coll[B]): PairColl[A, B] = {
// TODO v6.0 (2h): use minimal length and slice longer collection
// TODO v5.0 (2h): use minimal length and slice longer collection
// The current implementation doesn't check the case when `as` and `bs` have different lengths.
// in which case the implementation of `PairOfCols` has inconsistent semantics of `map`, `exists` etc methods.
// To fix the problem, the longer collection have to be truncated (which is consistent
Expand Down
74 changes: 64 additions & 10 deletions library/src/test/scala/special/collections/CollsTests.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package special.collections

import scala.language.{existentials,implicitConversions}
import special.collection.{Coll, PairOfCols, CollOverArray, CReplColl}
import scala.language.{existentials, implicitConversions}
import special.collection.{CReplColl, Coll, CollOverArray, PairOfCols}
import org.scalacheck.Gen
import org.scalatest.{PropSpec, Matchers}
import org.scalatest.exceptions.TestFailedException
import org.scalatest.{Matchers, PropSpec}
import org.scalatest.prop.PropertyChecks
import scalan.RType
import sigmastate.VersionContext

class CollsTests extends PropSpec with PropertyChecks with Matchers with CollGens { testSuite =>
import Gen._
Expand All @@ -23,8 +25,6 @@ class CollsTests extends PropSpec with PropertyChecks with Matchers with CollGen
}
}

// TODO v6.0: make sure forall: T, col: Coll[T] => col.length shouldBe col.toArray.length
// The above equality should hold for all possible collection instances
property("Coll.length") {
def equalLength[A: RType](xs: Coll[A]) = {
val arr = xs.toArray
Expand All @@ -45,6 +45,9 @@ class CollsTests extends PropSpec with PropertyChecks with Matchers with CollGen
equalLength(ys.append(xs))
}

// make sure forall: T, col: Coll[T] => col.length shouldBe col.toArray.length
// The above equality should hold for all possible collection instances

forAll(MinSuccessful(100)) { xs: Coll[Int] =>
val arr = xs.toArray
equalLength(xs)
Expand All @@ -55,13 +58,27 @@ class CollsTests extends PropSpec with PropertyChecks with Matchers with CollGen

val pairs = xs.zip(xs)
equalLength(pairs)
an[ClassCastException] should be thrownBy {
equalLengthMapped(pairs, squared(inc)) // due to problem with append

if (!xs.isInstanceOf[CReplColl[_]]) {
an[ClassCastException] should be thrownBy {
equalLengthMapped(pairs, squared(inc)) // due to problem with append
}
}
VersionContext.withVersions(VersionContext.JitActivationVersion, VersionContext.JitActivationVersion) {
// TODO v5.0: make it work
// equalLengthMapped(pairs, squared(inc)) // problem fixed in v5.0
}

equalLength(pairs.append(pairs))
an[ClassCastException] should be thrownBy {
equalLengthMapped(pairs.append(pairs), squared(inc)) // due to problem with append

if (!xs.isInstanceOf[CReplColl[_]]) {
an[ClassCastException] should be thrownBy {
equalLengthMapped(pairs.append(pairs), squared(inc)) // due to problem with append
}
}
VersionContext.withVersions(VersionContext.JitActivationVersion, VersionContext.JitActivationVersion) {
// TODO v5.0: make it work
// equalLengthMapped(pairs.append(pairs), squared(inc)) // problem fixed in v5.0
}
}
}
Expand Down Expand Up @@ -245,10 +262,37 @@ class CollsTests extends PropSpec with PropertyChecks with Matchers with CollGen
val xs = builder.fromItems(1, 2)
val pairs = xs.zip(xs)
val ys = pairs.map(squared(inc)) // this map transforms PairOfCols to CollOverArray

// due to the problem with concatArrays
an[ClassCastException] should be thrownBy (ys.append(ys))

VersionContext.withVersions(VersionContext.JitActivationVersion, VersionContext.JitActivationVersion) {
// TODO v5.0: make it work
// ys.append(ys).toArray shouldBe ys.toArray ++ ys.toArray // problem fixed in v5.0
}
}


{
val col1 = builder.fromItems(1, 2, 3)
val col2 = builder.fromItems(10, 20, 30, 40)
val pairs = col1.zip(col2)
assert(pairs.isInstanceOf[PairOfCols[_,_]])

val pairsArr = pairs.toArray
pairsArr shouldBe Array((1, 10), (2, 20), (3, 30))

// here is the problem with append
val appended = pairs.append(pairs)

an[TestFailedException] should be thrownBy(
appended.toArray shouldBe (pairsArr ++ pairsArr)
)
// Note, the last element of col2 (40) is zipped with 1
// this is because PairOfCols keeps ls and rs separated and zip doesn't do truncation
// the they are of different length
appended.toArray shouldBe Array((1, 10), (2, 20), (3, 30), (1, 40), (2, 10), (3, 20))
}

forAll(collGen, collGen, valGen, MinSuccessful(50)) { (col1, col2, v) =>

{
Expand Down Expand Up @@ -288,6 +332,16 @@ class CollsTests extends PropSpec with PropertyChecks with Matchers with CollGen
}
}

property("Coll.zip") {
val col1 = builder.fromItems(1, 2, 3)
val col2 = builder.fromItems(10, 20, 30, 40)
val pairs = col1.zip(col2)
assert(pairs.isInstanceOf[PairOfCols[Int, Int]])
pairs.length shouldBe col1.length
pairs.length shouldNot be(col2.length)
pairs.length shouldBe col2.zip(col1).length
}

property("Coll.mapReduce") {
import scalan.util.CollectionUtil.TraversableOps
def m(x: Int) = (math.abs(x) % 10, x)
Expand Down
21 changes: 18 additions & 3 deletions sigma-impl/src/main/scala/special/sigma/SigmaDslOverArrays.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package special.sigma

import java.math.BigInteger

import com.google.common.primitives.Longs
import org.bouncycastle.crypto.ec.CustomNamedCurves
import org.bouncycastle.math.ec.ECPoint
Expand All @@ -12,6 +11,9 @@ import scorex.crypto.hash.{Sha256, Blake2b256}
import special.SpecialPredef
import special.collection._
import scalan.util.Extensions.BigIntegerOps
import sigmastate.VersionContext
import sigmastate.VersionContext.JitActivationVersion
import spire.syntax.all.cfor

// TODO refactor: this class is not necessary and can be removed
class TestSigmaDslBuilder extends SigmaDslBuilder {
Expand Down Expand Up @@ -40,8 +42,21 @@ class TestSigmaDslBuilder extends SigmaDslBuilder {

@NeverInline
override def xorOf(conditions: Coll[Boolean]): Boolean = {
// This is buggy version used in v4.x interpreter (for ErgoTrees v0, v1)
conditions.toArray.distinct.length == 2
if (VersionContext.current.ergoTreeVersion >= JitActivationVersion) {
val len = conditions.length
if (len == 0) false
else if (len == 1) conditions(0)
else {
var res = conditions(0)
cfor(1)(_ < len, _ + 1) { i =>
res ^= conditions(i)
}
res
}
} else {
// This is buggy version used in v4.x interpreter (for ErgoTrees v0, v1)
conditions.toArray.distinct.length == 2
}
}

@NeverInline
Expand Down
Loading