Skip to content

Commit

Permalink
merging w. v6.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
kushti committed Jan 16, 2025
2 parents aa61236 + f1c1287 commit bff17fe
Show file tree
Hide file tree
Showing 17 changed files with 467 additions and 185 deletions.
18 changes: 12 additions & 6 deletions data/shared/src/main/scala/sigma/SigmaDataReflection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -236,12 +236,6 @@ object SigmaDataReflection {
{ val clazz = SAvlTreeMethods.getClass
registerClassEntry(clazz,
methods = Map(
mkMethod(clazz, "update_eval", Array[Class[_]](classOf[MethodCall], classOf[AvlTree], classOf[Coll[_]], classOf[Coll[_]], classOf[ErgoTreeEvaluator])) { (obj, args) =>
obj.asInstanceOf[SAvlTreeMethods.type].update_eval(args(0).asInstanceOf[MethodCall],
args(1).asInstanceOf[AvlTree],
args(2).asInstanceOf[KeyValueColl],
args(3).asInstanceOf[Coll[Byte]])(args(4).asInstanceOf[ErgoTreeEvaluator])
},
mkMethod(clazz, "contains_eval", Array[Class[_]](classOf[MethodCall], classOf[AvlTree], classOf[Coll[_]], classOf[Coll[_]], classOf[ErgoTreeEvaluator])) { (obj, args) =>
obj.asInstanceOf[SAvlTreeMethods.type].contains_eval(args(0).asInstanceOf[MethodCall],
args(1).asInstanceOf[AvlTree],
Expand Down Expand Up @@ -271,6 +265,18 @@ object SigmaDataReflection {
args(1).asInstanceOf[AvlTree],
args(2).asInstanceOf[KeyValueColl],
args(3).asInstanceOf[Coll[Byte]])(args(4).asInstanceOf[ErgoTreeEvaluator])
},
mkMethod(clazz, "update_eval", Array[Class[_]](classOf[MethodCall], classOf[AvlTree], classOf[Coll[_]], classOf[Coll[_]], classOf[ErgoTreeEvaluator])) { (obj, args) =>
obj.asInstanceOf[SAvlTreeMethods.type].update_eval(args(0).asInstanceOf[MethodCall],
args(1).asInstanceOf[AvlTree],
args(2).asInstanceOf[KeyValueColl],
args(3).asInstanceOf[Coll[Byte]])(args(4).asInstanceOf[ErgoTreeEvaluator])
},
mkMethod(clazz, "insertOrUpdate_eval", Array[Class[_]](classOf[MethodCall], classOf[AvlTree], classOf[Coll[_]], classOf[Coll[_]], classOf[ErgoTreeEvaluator])) { (obj, args) =>
obj.asInstanceOf[SAvlTreeMethods.type].insertOrUpdate_eval(args(0).asInstanceOf[MethodCall],
args(1).asInstanceOf[AvlTree],
args(2).asInstanceOf[KeyValueColl],
args(3).asInstanceOf[Coll[Byte]])(args(4).asInstanceOf[ErgoTreeEvaluator])
}
)
)
Expand Down
74 changes: 57 additions & 17 deletions data/shared/src/main/scala/sigma/ast/methods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1759,23 +1759,63 @@ case object SAvlTreeMethods extends MonoTypeMethods {
OperationCostInfo(m.costKind.asInstanceOf[FixedCost], m.opDesc)
}

protected override def getMethods(): Seq[SMethod] = super.getMethods() ++ Seq(
digestMethod,
enabledOperationsMethod,
keyLengthMethod,
valueLengthOptMethod,
isInsertAllowedMethod,
isUpdateAllowedMethod,
isRemoveAllowedMethod,
updateOperationsMethod,
containsMethod,
getMethod,
getManyMethod,
insertMethod,
updateMethod,
removeMethod,
updateDigestMethod
)
// 6.0 methods below
lazy val insertOrUpdateMethod = SMethod(this, "insertOrUpdate",
SFunc(Array(SAvlTree, CollKeyValue, SByteArray), SAvlTreeOption), 16, DynamicCost)
.withIRInfo(MethodCallIrBuilder)
.withInfo(MethodCall,
"""
| /** Perform insertions or updates of key-value entries into this tree using proof `proof`.
| * Throws exception if proof is incorrect
| * Return Some(newTree) if successful
| * Return None if operations were not performed.
| *
| * @note CAUTION! Pairs must be ordered the same way they were in insert ops before proof was generated.
| * @param operations collection of key-value pairs to insert or update in this authenticated dictionary.
| * @param proof
| */
|
""".stripMargin)

/** Implements evaluation of AvlTree.insertOrUpdate method call ErgoTree node.
* Called via reflection based on naming convention.
* @see SMethod.evalMethod
*/
def insertOrUpdate_eval(mc: MethodCall, tree: AvlTree, entries: KeyValueColl, proof: Coll[Byte])
(implicit E: ErgoTreeEvaluator): Option[AvlTree] = {
E.insertOrUpdate_eval(mc, tree, entries, proof)
}

lazy val v5Methods = {
super.getMethods() ++ Seq(
digestMethod,
enabledOperationsMethod,
keyLengthMethod,
valueLengthOptMethod,
isInsertAllowedMethod,
isUpdateAllowedMethod,
isRemoveAllowedMethod,
updateOperationsMethod,
containsMethod,
getMethod,
getManyMethod,
insertMethod,
updateMethod,
removeMethod,
updateDigestMethod
)
}

lazy val v6Methods = v5Methods ++ Seq(insertOrUpdateMethod)

protected override def getMethods(): Seq[SMethod] = {
if (VersionContext.current.isV6SoftForkActivated) {
v6Methods
} else {
v5Methods
}
}

}

/** Type descriptor of `Context` type of ErgoTree. */
Expand Down
2 changes: 2 additions & 0 deletions data/shared/src/main/scala/sigma/ast/values.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import sigma.ast.syntax._
import sigma.crypto.{CryptoConstants, EcPointType}
import sigma.data.OverloadHack.Overloaded1
import sigma.data.{CSigmaDslBuilder, CSigmaProp, CUnsignedBigInt, Nullable, RType, SigmaBoolean}
import sigma.data.{AvlTreeData, CAvlTree, CSigmaDslBuilder, CSigmaProp, Nullable, RType, SigmaBoolean}
import sigma.eval.ErgoTreeEvaluator.DataEnv
import sigma.eval.{ErgoTreeEvaluator, SigmaDsl}
import sigma.exceptions.InterpreterException
Expand Down Expand Up @@ -550,6 +551,7 @@ object SigmaPropConstant {

object AvlTreeConstant {
def apply(value: AvlTree): Constant[SAvlTree.type] = Constant[SAvlTree.type](value, SAvlTree)
def apply(value: AvlTreeData): Constant[SAvlTree.type] = Constant[SAvlTree.type](CAvlTree(value), SAvlTree)
}

object PreHeaderConstant {
Expand Down
13 changes: 13 additions & 0 deletions data/shared/src/main/scala/sigma/eval/AvlTreeVerifier.scala
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,19 @@ trait AvlTreeVerifier {
*/
def performUpdate(key: Array[Byte], value: Array[Byte]): Try[Option[Array[Byte]]]

/**
* Returns Failure if the proof does not verify.
* Otherwise, successfully modifies tree and so returns Success.
* After one failure, all subsequent operations with this verifier will fail and digest
* is None.
*
* @param key key to look up
* @param value value to check it was inserted or updated
* @return Success(Some(oldValue)) if there was some oldValue associated with the key,
* Success(None) in case of insertion, or Failure if proof invalid
*/
def performInsertOrUpdate(key: Array[Byte], value: Array[Byte]): Try[Option[Array[Byte]]]

/** Check the key has been removed in the tree.
* If `key` exists in the tree and the operation succeeds,
* returns `Success(Some(v))`, where v is old value associated with `key`.
Expand Down
7 changes: 7 additions & 0 deletions data/shared/src/main/scala/sigma/eval/ErgoTreeEvaluator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,13 @@ abstract class ErgoTreeEvaluator {
mc: MethodCall, tree: AvlTree,
operations: KeyValueColl, proof: Coll[Byte]): Option[AvlTree]

/** Implements evaluation of AvlTree.insertOrUpdate method call ErgoTree node. */
def insertOrUpdate_eval(
mc: MethodCall,
tree: AvlTree,
entries: KeyValueColl,
proof: Coll[Byte]): Option[AvlTree]

/** Implements evaluation of AvlTree.remove method call ErgoTree node. */
def remove_eval(
mc: MethodCall, tree: AvlTree,
Expand Down
16 changes: 15 additions & 1 deletion docs/LangSpec.md
Original file line number Diff line number Diff line change
Expand Up @@ -749,10 +749,24 @@ class AvlTree {
* Return None if operations were not performed.
* @param operations collection of key-value pairs to update in this
* authenticated dictionary.
* @param proof data to reconstruct part of the tree
* @param proof subtree which is enough to check operations
*/
def update(operations: Coll[(Coll[Byte], Coll[Byte])], proof: Coll[Byte]): Option[AvlTree]


/** Perform insertions or updates of key-value entries into this tree using proof `proof`.
* Throws exception if proof is incorrect
*
* @note CAUTION! Pairs must be ordered the same way they were in ops
* before proof was generated.
* Return Some(newTree) if successful
* Return None if operations were not performed.
* @param operations collection of key-value pairs to insert or update in this
* authenticated dictionary.
* @param proof subtree which is enough to check operations
*/
def insertOrUpdate(operations: Coll[(Coll[Byte], Coll[Byte])], proof: Coll[Byte]): Option[AvlTree]

/** Perform removal of entries into this tree using proof `proof`.
* Throws exception if proof is incorrect
* Return Some(newTree) if successful
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package sigmastate.eval

import scorex.crypto.authds.avltree.batch.{BatchAVLVerifier, Insert, Lookup, Remove, Update}
import scorex.crypto.authds.avltree.batch.{BatchAVLVerifier, Insert, InsertOrUpdate, Lookup, Remove, Update}
import scorex.crypto.authds.{ADDigest, ADKey, ADValue, SerializedAdProof}
import scorex.crypto.hash.{Blake2b256, Digest32}
import sigma.data.CAvlTree
Expand Down Expand Up @@ -32,6 +32,9 @@ class CAvlTreeVerifier private(
override def performUpdate(key: Array[Byte], value: Array[Byte]): Try[Option[Array[Byte]]] =
performOneOperation(Update(ADKey @@ key, ADValue @@ value))

override def performInsertOrUpdate(key: Array[Byte], value: Array[Byte]): Try[Option[Array[Byte]]] =
performOneOperation(InsertOrUpdate(ADKey @@ key, ADValue @@ value))

override def performRemove(key: Array[Byte]): Try[Option[Array[Byte]]] =
performOneOperation(Remove(ADKey @@ key))

Expand Down
22 changes: 20 additions & 2 deletions interpreter/shared/src/main/scala/sigmastate/eval/Extensions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package sigmastate.eval
import debox.cfor
import org.ergoplatform.ErgoBox
import org.ergoplatform.ErgoBox.TokenId
import scorex.crypto.authds.avltree.batch.{Insert, Lookup, Remove, Update}
import scorex.crypto.authds.avltree.batch.{Insert, InsertOrUpdate, Lookup, Remove, Update}
import scorex.crypto.authds.{ADKey, ADValue}
import scorex.util.encode.Base16
import sigma.ast.SType.AnyOps
Expand Down Expand Up @@ -91,7 +91,7 @@ object Extensions {
val bv = CAvlTreeVerifier(tree, proof)
entries.forall { case (key, value) =>
val insertRes = bv.performOneOperation(Insert(ADKey @@ key.toArray, ADValue @@ value.toArray))
if (insertRes.isFailure) {
if (insertRes.isFailure && !VersionContext.current.isV6SoftForkActivated) {
syntax.error(s"Incorrect insert for $tree (key: $key, value: $value, digest: ${tree.digest}): ${insertRes.failed.get}}")
}
insertRes.isSuccess
Expand Down Expand Up @@ -120,6 +120,24 @@ object Extensions {
}
}

def insertOrUpdate(
entries: Coll[(Coll[Byte], Coll[Byte])],
proof: Coll[Byte]): Option[AvlTree] = {
if (!tree.isInsertAllowed || !tree.isUpdateAllowed) {
None
} else {
val bv = CAvlTreeVerifier(tree, proof)
entries.forall { case (key, value) =>
val insertRes = bv.performOneOperation(InsertOrUpdate(ADKey @@ key.toArray, ADValue @@ value.toArray))
insertRes.isSuccess
}
bv.digest match {
case Some(d) => Some(tree.updateDigest(Colls.fromArray(d)))
case _ => None
}
}
}

def remove(operations: Coll[Coll[Byte]], proof: Coll[Byte]): Option[AvlTree] = {
if (!tree.isRemoveAllowed) {
None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,8 @@ class CErgoTreeEvaluator(
// the cost of tree lookup is O(bv.treeHeight)
addSeqCost(InsertIntoAvlTree_Info, nItems) { () =>
val insertRes = bv.performInsert(key.toArray, value.toArray)
// TODO v6.0: throwing exception is not consistent with update semantics
// however it preserves v4.0 semantics (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/908)
if (insertRes.isFailure) {
// For versioned change details, see see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/908
if (insertRes.isFailure && !VersionContext.current.isV6SoftForkActivated) {
syntax.error(s"Incorrect insert for $tree (key: $key, value: $value, digest: ${tree.digest}): ${insertRes.failed.get}}")
}
res = insertRes.isSuccess
Expand All @@ -173,7 +172,7 @@ class CErgoTreeEvaluator(
// when the tree is empty we still need to add the insert cost
val nItems = Math.max(bv.treeHeight, 1)

// here we use forall as looping with fast break on first failed tree oparation
// here we use forall as looping with fast break on first failed tree operation
operations.forall { case (key, value) =>
var res = true
// the cost of tree update is O(bv.treeHeight)
Expand All @@ -192,6 +191,40 @@ class CErgoTreeEvaluator(
}
}

override def insertOrUpdate_eval(
mc: MethodCall, tree: AvlTree,
operations: KeyValueColl, proof: Coll[Byte]): Option[AvlTree] = {
addCost(isUpdateAllowed_Info)
addCost(isInsertAllowed_Info)
if (!(tree.isUpdateAllowed && tree.isInsertAllowed)) {
None
} else {
val bv = createVerifier(tree, proof)
// when the tree is empty we still need to add the insert cost
val nItems = Math.max(bv.treeHeight, 1)

// here we use forall as looping with fast break on first failed tree operation
operations.forall { case (key, value) =>
var res = true
// the cost of tree update is O(bv.treeHeight)
// Here (and in the previous methods) the cost is not properly approximated.
// When the tree is small (or empty), but there are many `operations`, the treeHeight will grow on every iteration.
// So should the cost on every iteration.
addSeqCost(UpdateAvlTree_Info, nItems) { () =>
val updateRes = bv.performInsertOrUpdate(key.toArray, value.toArray)
res = updateRes.isSuccess
}
res
}
bv.digest match {
case Some(d) =>
addCost(updateDigest_Info)
Some(tree.updateDigest(Colls.fromArray(d)))
case _ => None
}
}
}

override def remove_eval(
mc: MethodCall, tree: AvlTree,
operations: Coll[Coll[Byte]], proof: Coll[Byte]): Option[AvlTree] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,10 @@ trait GraphBuilding extends Base with DefRewriting { IR: IRContext =>
val operations = asRep[Coll[(Coll[Byte], Coll[Byte])]](argsV(0))
val proof = asRep[Coll[Byte]](argsV(1))
tree.update(operations, proof)
case SAvlTreeMethods.insertOrUpdateMethod.name =>
val operations = asRep[Coll[(Coll[Byte], Coll[Byte])]](argsV(0))
val proof = asRep[Coll[Byte]](argsV(1))
tree.insertOrUpdate(operations, proof)
case _ => throwError()
}
case (ph: Ref[PreHeader]@unchecked, SPreHeaderMethods) => method.name match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,10 +290,6 @@ object GraphIRReflection {
obj.asInstanceOf[ctx.AvlTree].getMany(args(0).asInstanceOf[ctx.Ref[ctx.Coll[ctx.Coll[Byte]]]],
args(1).asInstanceOf[ctx.Ref[ctx.Coll[Byte]]])
},
mkMethod(clazz, "update", Array[Class[_]](classOf[Base#Ref[_]], classOf[Base#Ref[_]])) { (obj, args) =>
obj.asInstanceOf[ctx.AvlTree].update(args(0).asInstanceOf[ctx.Ref[ctx.Coll[(ctx.Coll[Byte], ctx.Coll[Byte])]]],
args(1).asInstanceOf[ctx.Ref[ctx.Coll[Byte]]])
},
mkMethod(clazz, "keyLength", Array[Class[_]]()) { (obj, _) =>
obj.asInstanceOf[ctx.AvlTree].keyLength
},
Expand All @@ -310,6 +306,14 @@ object GraphIRReflection {
obj.asInstanceOf[ctx.AvlTree].insert(args(0).asInstanceOf[ctx.Ref[ctx.Coll[(ctx.Coll[Byte], ctx.Coll[Byte])]]],
args(1).asInstanceOf[ctx.Ref[ctx.Coll[Byte]]])
},
mkMethod(clazz, "update", Array[Class[_]](classOf[Base#Ref[_]], classOf[Base#Ref[_]])) { (obj, args) =>
obj.asInstanceOf[ctx.AvlTree].update(args(0).asInstanceOf[ctx.Ref[ctx.Coll[(ctx.Coll[Byte], ctx.Coll[Byte])]]],
args(1).asInstanceOf[ctx.Ref[ctx.Coll[Byte]]])
},
mkMethod(clazz, "insertOrUpdate", Array[Class[_]](classOf[Base#Ref[_]], classOf[Base#Ref[_]])) { (obj, args) =>
obj.asInstanceOf[ctx.AvlTree].insertOrUpdate(args(0).asInstanceOf[ctx.Ref[ctx.Coll[(ctx.Coll[Byte], ctx.Coll[Byte])]]],
args(1).asInstanceOf[ctx.Ref[ctx.Coll[Byte]]])
},
mkMethod(clazz, "isRemoveAllowed", Array[Class[_]]()) { (obj, _) =>
obj.asInstanceOf[ctx.AvlTree].isRemoveAllowed
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import scalan._
def getMany(keys: Ref[Coll[Coll[Byte]]], proof: Ref[Coll[Byte]]): Ref[Coll[WOption[Coll[Byte]]]];
def insert(operations: Ref[Coll[scala.Tuple2[Coll[Byte], Coll[Byte]]]], proof: Ref[Coll[Byte]]): Ref[WOption[AvlTree]];
def update(operations: Ref[Coll[scala.Tuple2[Coll[Byte], Coll[Byte]]]], proof: Ref[Coll[Byte]]): Ref[WOption[AvlTree]];
def insertOrUpdate(operations: Ref[Coll[scala.Tuple2[Coll[Byte], Coll[Byte]]]], proof: Ref[Coll[Byte]]): Ref[WOption[AvlTree]];
def remove(operations: Ref[Coll[Coll[Byte]]], proof: Ref[Coll[Byte]]): Ref[WOption[AvlTree]]
};
trait PreHeader extends Def[PreHeader] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1208,6 +1208,13 @@ object AvlTree extends EntityObject("AvlTree") {
true, false, element[WOption[AvlTree]]))
}

override def insertOrUpdate(operations: Ref[Coll[(Coll[Byte], Coll[Byte])]], proof: Ref[Coll[Byte]]): Ref[WOption[AvlTree]] = {
asRep[WOption[AvlTree]](mkMethodCall(self,
AvlTreeClass.getMethod("insertOrUpdate", classOf[Sym], classOf[Sym]),
Array[AnyRef](operations, proof),
true, false, element[WOption[AvlTree]]))
}

override def remove(operations: Ref[Coll[Coll[Byte]]], proof: Ref[Coll[Byte]]): Ref[WOption[AvlTree]] = {
asRep[WOption[AvlTree]](mkMethodCall(self,
AvlTreeClass.getMethod("remove", classOf[Sym], classOf[Sym]),
Expand Down Expand Up @@ -1332,6 +1339,13 @@ object AvlTree extends EntityObject("AvlTree") {
true, true, element[WOption[AvlTree]]))
}

def insertOrUpdate(operations: Ref[Coll[(Coll[Byte], Coll[Byte])]], proof: Ref[Coll[Byte]]): Ref[WOption[AvlTree]] = {
asRep[WOption[AvlTree]](mkMethodCall(source,
AvlTreeClass.getMethod("insertOrUpdate", classOf[Sym], classOf[Sym]),
Array[AnyRef](operations, proof),
true, true, element[WOption[AvlTree]]))
}

def remove(operations: Ref[Coll[Coll[Byte]]], proof: Ref[Coll[Byte]]): Ref[WOption[AvlTree]] = {
asRep[WOption[AvlTree]](mkMethodCall(source,
AvlTreeClass.getMethod("remove", classOf[Sym], classOf[Sym]),
Expand All @@ -1355,7 +1369,7 @@ object AvlTree extends EntityObject("AvlTree") {
override protected def collectMethods: Map[RMethod, MethodDesc] = {
super.collectMethods ++
Elem.declaredMethods(RClass(classOf[AvlTree]), RClass(classOf[SAvlTree]), Set(
"digest", "enabledOperations", "keyLength", "valueLengthOpt", "isInsertAllowed", "isUpdateAllowed", "isRemoveAllowed", "updateDigest", "updateOperations", "contains", "get", "getMany", "insert", "update", "remove"
"digest", "enabledOperations", "keyLength", "valueLengthOpt", "isInsertAllowed", "isUpdateAllowed", "isRemoveAllowed", "updateDigest", "updateOperations", "contains", "get", "getMany", "insert", "update", "insertOrUpdate", "remove"
))
}
}
Expand Down
Loading

0 comments on commit bff17fe

Please sign in to comment.