sigma.data.DataValueComparer.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sigma-state_2.12 Show documentation
Show all versions of sigma-state_2.12 Show documentation
Interpreter of a Sigma-State language
The newest version!
package sigma.data
import debox.{cfor, sp}
import sigma._
import sigma.ast.{FixedCost, JitCost, NamedDesc, OperationCostInfo, PerItemCost}
import sigma.crypto.EcPointType
import sigma.eval.{ErgoTreeEvaluator, SigmaDsl}
/** Implementation of data equality for two arbitrary ErgoTree data types.
* @see [[DataValueComparer.equalDataValues]]
*/
object DataValueComparer {
/** NOTE: The cost of most equality operations depends on the position in `match` statement.
* Thus the full cost to compare x and y equals DispatchCost * OperationCost, where
* DispatchCost = CasePosition * CostOf_MatchType,
* OperationCost is the type specific cost.
* For this reason reordering of cases may lead to divergence between an estimated and
* the actual execution cost (time).
* The constants are part of the consensus protocol and cannot be changed without forking.
*/
final val CostOf_MatchType = 1
final val CostKind_MatchType = FixedCost(JitCost(CostOf_MatchType))
final val OpDesc_MatchType = NamedDesc("MatchType")
final val MatchType = OperationCostInfo(CostKind_MatchType, OpDesc_MatchType)
final val CostKind_EQ_Prim = FixedCost(JitCost(3)) // case 1
final val OpDesc_EQ_Prim = NamedDesc("EQ_Prim")
final val EQ_Prim = OperationCostInfo(CostKind_EQ_Prim, OpDesc_EQ_Prim)
/** Equals two Colls of non-primitive (boxed) types.
*/
final val CostKind_EQ_Coll = PerItemCost(
baseCost = JitCost(10), perChunkCost = JitCost(2), chunkSize = 1) // case 2
final val OpDesc_EQ_Coll = NamedDesc("EQ_Coll")
final val EQ_Coll = OperationCostInfo(CostKind_EQ_Coll, OpDesc_EQ_Coll)
final val CostKind_EQ_Tuple = FixedCost(JitCost(4)) // case 3
final val OpDesc_EQ_Tuple = NamedDesc("EQ_Tuple")
final val EQ_Tuple = OperationCostInfo(CostKind_EQ_Tuple, OpDesc_EQ_Tuple)
/** NOTE: the value is set based on benchmarking of SigmaDslSpecification. */
final val CostKind_EQ_GroupElement = FixedCost(JitCost(172)) // case 4
final val OpDesc_EQ_GroupElement = NamedDesc("EQ_GroupElement")
final val EQ_GroupElement = OperationCostInfo(CostKind_EQ_GroupElement, OpDesc_EQ_GroupElement)
final val CostKind_EQ_BigInt = FixedCost(JitCost(5)) // case 5
final val OpDesc_EQ_BigInt = NamedDesc("EQ_BigInt")
final val EQ_BigInt = OperationCostInfo(CostKind_EQ_BigInt, OpDesc_EQ_BigInt)
final val CostKind_EQ_AvlTree = FixedCost(JitCost(3 + (6 * CostOf_MatchType) / 2)) // case 6
final val OpDesc_EQ_AvlTree = NamedDesc("EQ_AvlTree")
final val EQ_AvlTree = OperationCostInfo(CostKind_EQ_AvlTree, OpDesc_EQ_AvlTree)
final val CostKind_EQ_Box = FixedCost(JitCost(6)) // case 7
final val OpDesc_EQ_Box = NamedDesc("EQ_Box")
final val EQ_Box = OperationCostInfo(CostKind_EQ_Box, OpDesc_EQ_Box)
/** NOTE: In the formula `(7 + 1)` the 1 corresponds to the second type match. */
final val CostKind_EQ_Option = FixedCost(JitCost(1 + (7 + 1) * CostOf_MatchType / 2 - 1)) // case 8
final val OpDesc_EQ_Option = NamedDesc("EQ_Option")
final val EQ_Option = OperationCostInfo(CostKind_EQ_Option, OpDesc_EQ_Option)
final val CostKind_EQ_PreHeader = FixedCost(JitCost(4)) // case 9
final val OpDesc_EQ_PreHeader = NamedDesc("EQ_PreHeader")
final val EQ_PreHeader = OperationCostInfo(CostKind_EQ_PreHeader, OpDesc_EQ_PreHeader)
final val CostKind_EQ_Header = FixedCost(JitCost(6)) // case 10
final val OpDesc_EQ_Header = NamedDesc("EQ_Header")
final val EQ_Header = OperationCostInfo(CostKind_EQ_Header, OpDesc_EQ_Header)
/** Equals two CollOverArray of Boolean type. */
final val CostKind_EQ_COA_Boolean = PerItemCost(
baseCost = JitCost(15), perChunkCost = JitCost(2), chunkSize = 128)
final val OpDesc_EQ_COA_Boolean = NamedDesc("EQ_COA_Boolean")
final val EQ_COA_Boolean = OperationCostInfo(CostKind_EQ_COA_Boolean, OpDesc_EQ_COA_Boolean)
/** Equals two CollOverArray of Byte type. */
final val CostKind_EQ_COA_Byte = PerItemCost(
baseCost = JitCost(15), perChunkCost = JitCost(2), chunkSize = 128)
final val OpDesc_EQ_COA_Byte = NamedDesc("EQ_COA_Byte")
final val EQ_COA_Byte = OperationCostInfo(CostKind_EQ_COA_Byte, OpDesc_EQ_COA_Byte)
/** Equals two CollOverArray of Short type. */
final val CostKind_EQ_COA_Short = PerItemCost(
baseCost = JitCost(15), perChunkCost = JitCost(2), chunkSize = 96)
final val OpDesc_EQ_COA_Short = NamedDesc("EQ_COA_Short")
final val EQ_COA_Short = OperationCostInfo(CostKind_EQ_COA_Short, OpDesc_EQ_COA_Short)
/** Equals two CollOverArray of Int type. */
final val CostKind_EQ_COA_Int = PerItemCost(
baseCost = JitCost(15), perChunkCost = JitCost(2), chunkSize = 64)
final val OpDesc_EQ_COA_Int = NamedDesc("EQ_COA_Int")
final val EQ_COA_Int = OperationCostInfo(CostKind_EQ_COA_Int, OpDesc_EQ_COA_Int)
/** Equals two CollOverArray of Long type. */
final val CostKind_EQ_COA_Long = PerItemCost(
baseCost = JitCost(15), perChunkCost = JitCost(2), chunkSize = 48)
final val OpDesc_EQ_COA_Long = NamedDesc("EQ_COA_Long")
final val EQ_COA_Long = OperationCostInfo(CostKind_EQ_COA_Long, OpDesc_EQ_COA_Long)
/** Equals two CollOverArray of GroupElement type. */
final val CostKind_EQ_COA_GroupElement = PerItemCost(
baseCost = JitCost(15), perChunkCost = JitCost(5), chunkSize = 1)
final val OpDesc_EQ_COA_GroupElement = NamedDesc("EQ_COA_GroupElement")
final val EQ_COA_GroupElement = OperationCostInfo(CostKind_EQ_COA_GroupElement, OpDesc_EQ_COA_GroupElement)
/** Equals two CollOverArray of BigInt type. */
final val CostKind_EQ_COA_BigInt = PerItemCost(
baseCost = JitCost(15), perChunkCost = JitCost(7), chunkSize = 5)
final val OpDesc_EQ_COA_BigInt = NamedDesc("EQ_COA_BigInt")
final val EQ_COA_BigInt = OperationCostInfo(CostKind_EQ_COA_BigInt, OpDesc_EQ_COA_BigInt)
/** Equals two CollOverArray of AvlTree type. */
final val CostKind_EQ_COA_AvlTree = PerItemCost(
baseCost = JitCost(15), perChunkCost = JitCost(5), chunkSize = 2)
final val OpDesc_EQ_COA_AvlTree = NamedDesc("EQ_COA_AvlTree")
final val EQ_COA_AvlTree = OperationCostInfo(CostKind_EQ_COA_AvlTree, OpDesc_EQ_COA_AvlTree)
/** Equals two CollOverArray of Box type. */
final val CostKind_EQ_COA_Box = PerItemCost(
baseCost = JitCost(15), perChunkCost = JitCost(5), chunkSize = 1)
final val OpDesc_EQ_COA_Box = NamedDesc("EQ_COA_Box")
final val EQ_COA_Box = OperationCostInfo(CostKind_EQ_COA_Box, OpDesc_EQ_COA_Box)
/** Equals two CollOverArray of PreHeader type. */
final val CostKind_EQ_COA_PreHeader = PerItemCost(
baseCost = JitCost(15), perChunkCost = JitCost(3), chunkSize = 1)
final val OpDesc_EQ_COA_PreHeader = NamedDesc("EQ_COA_PreHeader")
final val EQ_COA_PreHeader = OperationCostInfo(CostKind_EQ_COA_PreHeader, OpDesc_EQ_COA_PreHeader)
/** Equals two CollOverArray of Header type. */
final val CostKind_EQ_COA_Header = PerItemCost(
baseCost = JitCost(15), perChunkCost = JitCost(5), chunkSize = 1)
final val OpDesc_EQ_COA_Header = NamedDesc("EQ_COA_Header")
final val EQ_COA_Header = OperationCostInfo(CostKind_EQ_COA_Header, OpDesc_EQ_COA_Header)
val descriptors: AVHashMap[RType[_], (OperationCostInfo[FixedCost], OperationCostInfo[PerItemCost])] =
AVHashMap.fromSeq(Array[(RType[_], (OperationCostInfo[FixedCost], OperationCostInfo[PerItemCost]))](
(BigIntRType, (EQ_BigInt, EQ_COA_BigInt)),
(GroupElementRType, (EQ_GroupElement, EQ_COA_GroupElement)),
(AvlTreeRType, (EQ_AvlTree, EQ_COA_AvlTree)),
(BoxRType, (EQ_Box, EQ_COA_Box)),
(PreHeaderRType, (EQ_PreHeader, EQ_COA_PreHeader)),
(HeaderRType, (EQ_Header, EQ_COA_Header))
))
type COA[A] = CollOverArray[A]
type POC[A,B] = PairOfCols[A, B]
/** This method is specialized for numeric types and thus Scala generates four
* specialized methods (one for each type) which implement unboxed comparison or arrays
* in a most efficient way. This efficient implementation is reflected in the cost
* parameters, which are part of the protocol. Thus any alternative protocol
* implementation should implement comparison is the same way.
*/
private def equalCOA_Prim[@sp(Boolean, Byte, Short, Int, Long) A]
(c1: COA[A], c2: COA[A], costInfo: OperationCostInfo[PerItemCost])
(implicit E: ErgoTreeEvaluator): Boolean = {
var okEqual = true
E.addSeqCost(costInfo.costKind, costInfo.opDesc) { () =>
// this loop is bounded because MaxArrayLength limit is enforced
val len = c1.length
var i = 0
val a1 = c1.toArray
val a2 = c2.toArray
while (i < len && okEqual) {
okEqual = a1(i) == a2(i)
i += 1
}
i // return the number of actually compared items
}
okEqual
}
/** Compare two collections for equality. Used when the element type A is NOT known
* statically. When the type A is scalar, each collection item is boxed before
* comparison, which have significant performace overhead.
* For this reason, this method is used as a fallback case.
*/
def equalColls[A](c1: Coll[A], c2: Coll[A])(implicit E: ErgoTreeEvaluator): Boolean = {
var okEqual = true
E.addSeqCost(CostKind_EQ_Coll, OpDesc_EQ_Coll) { () =>
// this loop is bounded because MaxArrayLength limit is enforced
val len = c1.length
var i = 0
while(i < len && okEqual) {
okEqual = equalDataValues(c1(i), c2(i))
i += 1
}
i
}
okEqual
}
/** Compares two collections by dispatching to the most efficient implementation
* depending on the actual type A.
* */
def equalColls_Dispatch[A](coll1: Coll[A], coll2: Coll[A])(implicit E: ErgoTreeEvaluator): Boolean = {
coll1.tItem match {
case BooleanType =>
equalCOA_Prim(
coll1.asInstanceOf[COA[Boolean]],
coll2.asInstanceOf[COA[Boolean]], EQ_COA_Boolean)
case ByteType =>
equalCOA_Prim(
coll1.asInstanceOf[COA[Byte]],
coll2.asInstanceOf[COA[Byte]], EQ_COA_Byte)
case ShortType =>
equalCOA_Prim(
coll1.asInstanceOf[COA[Short]],
coll2.asInstanceOf[COA[Short]], EQ_COA_Short)
case IntType =>
equalCOA_Prim(
coll1.asInstanceOf[COA[Int]],
coll2.asInstanceOf[COA[Int]], EQ_COA_Int)
case LongType =>
equalCOA_Prim(
coll1.asInstanceOf[COA[Long]],
coll2.asInstanceOf[COA[Long]], EQ_COA_Long)
case t =>
descriptors.get(t) match {
case Nullable((_, info)) =>
equalCOA_Prim(
coll1.asInstanceOf[COA[A]],
coll2.asInstanceOf[COA[A]], info)
case _ =>
equalColls(coll1, coll2)
}
}
}
/** Compare equality of two sequences of SigmaBoolean trees. */
def equalSigmaBooleans(xs: Seq[SigmaBoolean], ys: Seq[SigmaBoolean])
(implicit E: ErgoTreeEvaluator): Boolean = {
val len = xs.length
if (len != ys.length) return false
var okEqual = true
cfor(0)(_ < len && okEqual, _ + 1) { i =>
okEqual = equalSigmaBoolean(xs(i), ys(i))
}
okEqual
}
/** Compare equality of two SigmaBoolean trees. */
def equalSigmaBoolean(l: SigmaBoolean, r: SigmaBoolean)
(implicit E: ErgoTreeEvaluator): Boolean = {
E.addCost(MatchType) // once for every node of the SigmaBoolean tree
l match {
case ProveDlog(x) => r match {
case ProveDlog(y) => equalECPoint(x, y)
case _ => false
}
case x: ProveDHTuple => r match {
case y: ProveDHTuple =>
equalECPoint(x.gv, y.gv) && equalECPoint(x.hv, y.hv) &&
equalECPoint(x.uv, y.uv) && equalECPoint(x.vv, y.vv)
case _ => false
}
case x: TrivialProp => r match {
case y: TrivialProp => x.condition == y.condition
case _ => false
}
case CAND(children) if r.isInstanceOf[CAND] =>
equalSigmaBooleans(children, r.asInstanceOf[CAND].children)
case COR(children) if r.isInstanceOf[COR] =>
equalSigmaBooleans(children, r.asInstanceOf[COR].children)
case CTHRESHOLD(k, children) if r.isInstanceOf[CTHRESHOLD] =>
val sb2 = r.asInstanceOf[CTHRESHOLD]
k == sb2.k && equalSigmaBooleans(children, sb2.children)
case _ =>
sys.error(
s"Cannot compare SigmaBoolean values $l and $r: unknown type")
}
}
/** Returns true if the given GroupElement is equal to the given object. */
def equalGroupElement(ge1: GroupElement, r: Any)(implicit E: ErgoTreeEvaluator): Boolean = {
var okEqual = true
E.addFixedCost(EQ_GroupElement) {
okEqual = ge1 == r
}
okEqual
}
/** Returns true if the given EcPointType is equal to the given object. */
def equalECPoint(p1: EcPointType, r: Any)(implicit E: ErgoTreeEvaluator): Boolean = {
var okEqual = true
E.addFixedCost(EQ_GroupElement) {
okEqual = p1 == r
}
okEqual
}
/** Generic comparison of any two data values. The method dispatches on a type of the
* left value and then performs the specific comparison.
* The comparison recursively descends on the value structure regardless of the depth.
* However, every step costs are accrued to `E.coster` and the defined limit
* `E.coster.costLimit` is checked. Thus, the execution of this method is limited and
* always finishes at least as fast as the costLimit prescribes.
* No limit is structural depth is necessary.
*/
def equalDataValues(l: Any, r: Any)(implicit E: ErgoTreeEvaluator): Boolean = {
var okEqual: Boolean = false
l match {
case _: java.lang.Number | _: Boolean => /** case 1 (see [[EQ_Prim]]) */
E.addFixedCost(EQ_Prim) {
okEqual = l == r
}
case coll1: Coll[a] => /** case 2 (see [[EQ_Coll]]) */
E.addCost(MatchType) // for second match below
okEqual = r match {
case coll2: Coll[_] =>
val len = coll1.length
if (len != coll2.length || coll1.tItem != coll2.tItem)
return false
equalColls_Dispatch(coll1, coll2.asInstanceOf[Coll[a]])
case _ => false
}
case tup1: Tuple2[_,_] => /** case 3 (see [[EQ_Tuple]]) */
E.addFixedCost(EQ_Tuple) {
okEqual = r match {
case tup2: Tuple2[_,_] =>
equalDataValues(tup1._1, tup2._1) && equalDataValues(tup1._2, tup2._2)
case _ => false
}
}
case ge1: GroupElement => /** case 4 (see [[EQ_GroupElement]]) */
okEqual = equalGroupElement(ge1, r)
case bi: BigInt => /** case 5 (see [[EQ_BigInt]]) */
E.addFixedCost(EQ_BigInt) {
okEqual = bi == r
}
case sp1: SigmaProp =>
E.addCost(MatchType) // for second match below
okEqual = r match {
case sp2: SigmaProp =>
equalSigmaBoolean(
SigmaDsl.toSigmaBoolean(sp1),
SigmaDsl.toSigmaBoolean(sp2))
case _ => false
}
case bi: AvlTree => /** case 6 (see [[EQ_AvlTree]]) */
E.addFixedCost(EQ_AvlTree) {
okEqual = bi == r
}
case opt1: Option[_] => /** case 7 (see [[EQ_Option]]) */
E.addFixedCost(EQ_Option) {
okEqual = r match {
case opt2: Option[_] =>
if (opt1.isDefined) {
if (opt2.isDefined) {
equalDataValues(opt1.get, opt2.get)
} else
false // right is not Some
} else {
// here left in None
opt2.isEmpty // return if the right is also None
}
case _ =>
false // right is not an Option
}
}
case ph: PreHeader => /** case 8 (see [[EQ_PreHeader]]) */
E.addFixedCost(EQ_PreHeader) {
okEqual = ph == r
}
case h: Header => /** case 9 (see [[EQ_Header]]) */
E.addFixedCost(EQ_Header) {
okEqual = h == r
}
case box: Box => /** case 10 (see [[EQ_Box]]) */
E.addFixedCost(EQ_Box) {
okEqual = box == r
}
case s1: String =>
E.addCost(MatchType) // for second match below
okEqual = r match {
case s2: String =>
val len = s1.length
if (len != s2.length)
return false
E.addSeqCost(EQ_COA_Short, len) { () =>
s1 == s2
}
case _ => false
}
case _: Unit =>
okEqual = r.isInstanceOf[Unit]
case _ =>
sys.error(s"Cannot compare $l and $r: unknown type")
}
okEqual
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy