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

Add flag isUsingBlockchainContext to ErgoTree #929

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,12 @@ class CoreByteReader(val r: Reader, val maxTreeDepth: Int = CoreSerializer.MaxTr
@inline final def wasDeserialize_=(v: Boolean): Unit = {
_wasDeserialize = v
}

private var _wasUsingBlockchainContext: Boolean = false
/** Helper property which is used to track operations using the blockchain context during parsing. */
@inline final def wasUsingBlockchainContext: Boolean = _wasUsingBlockchainContext

@inline final def wasUsingBlockchainContext_=(v: Boolean): Unit = {
_wasUsingBlockchainContext = v
}
}
22 changes: 19 additions & 3 deletions data/shared/src/main/scala/sigma/ast/ErgoTree.scala
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ case class ErgoTree private[sigma](
root: Either[UnparsedErgoTree, SigmaPropValue],
private val givenComplexity: Int,
private val propositionBytes: Array[Byte],
private val givenDeserialize: Option[Boolean]
private val givenDeserialize: Option[Boolean],
private val givenIsUsingBlockchainContext: Option[Boolean]
) {
def this(
header: HeaderType,
Expand All @@ -99,9 +100,10 @@ case class ErgoTree private[sigma](
this(
header, constants, root, 0,
propositionBytes = DefaultSerializer.serializeErgoTree(
ErgoTree(header, constants, root, 0, null, None)
ErgoTree(header, constants, root, 0, null, None, None)
),
givenDeserialize = None
givenDeserialize = None,
givenIsUsingBlockchainContext = None
)

require(isConstantSegregation || constants.isEmpty)
Expand Down Expand Up @@ -163,6 +165,20 @@ case class ErgoTree private[sigma](
_hasDeserialize.get
}

private[sigma] var _isUsingBlockchainContext: Option[Boolean] = givenIsUsingBlockchainContext

/** Returns true if the tree depends on the blockchain context.
*/
lazy val isUsingBlockchainContext: Boolean = {
if (_isUsingBlockchainContext.isEmpty) {
_isUsingBlockchainContext = Some(root match {
case Right(p) => Value.isUsingBlockchainContext(p)
case _ => false
})
}
_isUsingBlockchainContext.get
}

/** Serialized proposition expression of SigmaProp type with
* ConstantPlaceholder nodes not replaced by Constant nodes.
*/
Expand Down
7 changes: 7 additions & 0 deletions data/shared/src/main/scala/sigma/ast/methods.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1408,6 +1408,13 @@ case object SContextMethods extends MonoTypeMethods {
dataInputsMethod, headersMethod, preHeaderMethod, inputsMethod, outputsMethod, heightMethod, selfMethod,
selfBoxIndexMethod, lastBlockUtxoRootHashMethod, minerPubKeyMethod, getVarMethod
)

/** Names of methods which provide blockchain context.
* This value can be reused where necessary to avoid allocations. */
val BlockchainContextMethodNames: IndexedSeq[String] = Array(
headersMethod.name, preHeaderMethod.name, heightMethod.name,
lastBlockUtxoRootHashMethod.name, minerPubKeyMethod.name
)
}

/** Type descriptor of `Header` type of ErgoTree. */
Expand Down
19 changes: 19 additions & 0 deletions data/shared/src/main/scala/sigma/ast/values.scala
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,25 @@ object Value {
c > 0
}

/** Traverses the given expression tree and counts the number of operations
* which read the blockchain context. If it is non-zero, returns true. */
def isUsingBlockchainContext(exp: SValue): Boolean = {
val blockchainContextNode: PartialFunction[Any, Int] = {
case Height => 1
case LastBlockUtxoRootHash => 1
case MinerPubkey => 1
case MethodCall(_, method, _, _) =>
method.objType match {
case SContextMethods =>
if (SContextMethods.BlockchainContextMethodNames.contains(method.name)) 1 else 0
case _ => 0
}
case _ => 0
}
val c = count(blockchainContextNode)(exp)
c > 0
}

def typeError(node: SValue, evalResult: Any) = {
val tpe = node.tpe
throw new InterpreterException(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
package sigma.serialization

import sigma.ast.SType
import sigma.ast.{Value, ValueCompanion}
import sigma.ast.{Height, LastBlockUtxoRootHash, MinerPubkey, SType, Value, ValueCompanion}

case class CaseObjectSerialization[V <: Value[SType]](override val opDesc: ValueCompanion, obj: V)
extends ValueSerializer[V] {

override def serialize(obj: V, w: SigmaByteWriter): Unit = ()

override def parse(r: SigmaByteReader): V = obj
override def parse(r: SigmaByteReader): V = {
opDesc match {
case Height => r.wasUsingBlockchainContext = true
case LastBlockUtxoRootHash => r.wasUsingBlockchainContext = true
case MinerPubkey => r.wasUsingBlockchainContext = true
case _ =>
}

obj
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,16 @@ class ErgoTreeSerializer {
val wasDeserialize_saved = r.wasDeserialize
r.wasDeserialize = false

val wasUsingBlockchainContext_saved = r.wasUsingBlockchainContext
r.wasUsingBlockchainContext = false

val root = ValueSerializer.deserialize(r)
val hasDeserialize = r.wasDeserialize // == true if there was deserialization node
r.wasDeserialize = wasDeserialize_saved

val isUsingBlockchainContext = r.wasUsingBlockchainContext // == true if there was a node using the blockchain context
r.wasUsingBlockchainContext = wasUsingBlockchainContext_saved

if (checkType) {
CheckDeserializedScriptIsSigmaProp(root)
}
Expand All @@ -168,7 +174,7 @@ class ErgoTreeSerializer {

new ErgoTree(
h, cs, Right(root.asSigmaProp), complexity,
propositionBytes, Some(hasDeserialize))
propositionBytes, Some(hasDeserialize), Some(isUsingBlockchainContext))
}
catch {
case e: ReaderPositionLimitExceeded =>
Expand All @@ -183,7 +189,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, None)
new ErgoTree(ErgoTree.DefaultHeader, EmptyConstants, Left(UnparsedErgoTree(bytes, ve)), complexity, bytes, None, None)
case None =>
throw new SerializerException(
s"Cannot handle ValidationException, ErgoTree serialized without size bit.", Some(ve))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package sigma.serialization

import sigma.ast.syntax._
import sigma.ast.MethodCall
import sigma.ast.{ComplexityTable, MethodCall, SContextMethods, SMethod, SType, STypeSubst, Value, ValueCompanion}
import sigma.util.safeNewArray
import SigmaByteWriter._
import debox.cfor
import sigma.ast.{ComplexityTable, SMethod, SType, STypeSubst, Value, ValueCompanion}
import sigma.ast.SContextMethods.BlockchainContextMethodNames
import sigma.serialization.CoreByteWriter.{ArgInfo, DataInfo}

case class MethodCallSerializer(cons: (Value[SType], SMethod, IndexedSeq[Value[SType]], STypeSubst) => Value[SType])
Expand Down Expand Up @@ -60,6 +60,11 @@ case class MethodCallSerializer(cons: (Value[SType], SMethod, IndexedSeq[Value[S
}

val specMethod = method.specializeFor(obj.tpe, types)

var isUsingBlockchainContext = specMethod.objType == SContextMethods &&
BlockchainContextMethodNames.contains(method.name)
r.wasUsingBlockchainContext ||= isUsingBlockchainContext

cons(obj, specMethod, args, Map.empty)
}
}
104 changes: 101 additions & 3 deletions sc/shared/src/test/scala/sigmastate/ErgoTreeSpecification.scala
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,104 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit {
}
}

property("ErgoTree.isUsingBlockchainContext") {
{
val t = new ErgoTree(
HeaderType @@ 0.toByte,
Array[Constant[SType]](),
Right(TrueSigmaProp))
t._isUsingBlockchainContext shouldBe None
t.isUsingBlockchainContext shouldBe false
}

{
val t = new ErgoTree(
HeaderType @@ 16.toByte,
Array(IntConstant(1)),
Right(BoolToSigmaProp(GT(Height, ConstantPlaceholder(0, SInt))))
)
t._isUsingBlockchainContext shouldBe None
t.isUsingBlockchainContext shouldBe true
}

{
val t = new ErgoTree(
HeaderType @@ 16.toByte,
Vector(),
Right(OptionIsDefined(IR.builder.mkMethodCall(
LastBlockUtxoRootHash, SAvlTreeMethods.getMethod,
IndexedSeq(ExtractId(GetVarBox(22: Byte).get), GetVarByteArray(23: Byte).get)).asOption[SByteArray]))
)
t._isUsingBlockchainContext shouldBe None
t.isUsingBlockchainContext shouldBe true
}

{
val t = new ErgoTree(
HeaderType @@ 16.toByte,
Vector(),
Right(BoolToSigmaProp(EQ(MinerPubkey, ErgoLikeContextTesting.dummyPubkey)))
)
t._isUsingBlockchainContext shouldBe None
t.isUsingBlockchainContext shouldBe true
}

{
val t = new ErgoTree(
HeaderType @@ 16.toByte,
Vector(),
Right(OptionIsDefined(IR.builder.mkMethodCall(
Context, SContextMethods.headersMethod, Vector()).asOption[SByteArray]))
)
t._isUsingBlockchainContext shouldBe None
t.isUsingBlockchainContext shouldBe true
}

{
val t = new ErgoTree(
HeaderType @@ 16.toByte,
Vector(),
Right(OptionIsDefined(IR.builder.mkMethodCall(
Context, SContextMethods.preHeaderMethod, Vector()).asOption[SByteArray]))
)
t._isUsingBlockchainContext shouldBe None
t.isUsingBlockchainContext shouldBe true
}

{
val t = new ErgoTree(
HeaderType @@ 16.toByte,
Vector(),
Right(OptionIsDefined(IR.builder.mkMethodCall(
Context, SContextMethods.heightMethod, Vector()).asOption[SByteArray]))
)
t._isUsingBlockchainContext shouldBe None
t.isUsingBlockchainContext shouldBe true
}

{
val t = new ErgoTree(
HeaderType @@ 16.toByte,
Vector(),
Right(OptionIsDefined(IR.builder.mkMethodCall(
Context, SContextMethods.lastBlockUtxoRootHashMethod, Vector()).asOption[SByteArray]))
)
t._isUsingBlockchainContext shouldBe None
t.isUsingBlockchainContext shouldBe true
}

{
val t = new ErgoTree(
HeaderType @@ 16.toByte,
Vector(),
Right(OptionIsDefined(IR.builder.mkMethodCall(
Context, SContextMethods.minerPubKeyMethod, Vector()).asOption[SByteArray]))
)
t._isUsingBlockchainContext shouldBe None
t.isUsingBlockchainContext shouldBe true
}
}

property("ErgoTree equality") {
val t1 = new ErgoTree(
HeaderType @@ 16.toByte,
Expand Down Expand Up @@ -728,7 +826,7 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit {
val addr = ErgoAddressEncoder.Mainnet.fromString("3ehvg9kjzVt2ep6VkYbsNZBXVoFjQA3b26LTmCwvE9vTUEU2TG1uKdqh7sb1a23G1HLkXa6rv4NUcR5Ucghp8CTuBnUxJB4qYq61GYNDFmCHdoZJq32vUVJ7Jsq4cFE7zp9ddFnchb8EN2Qkaa9rqzmruj4iKjLu8MMJ3V8ns1tpRF8eSp2KSjPuMYt6ZHysFNGoMt4dQ2P45YoCXbgiJtzcADgjMnr5bkpqKMx2ZEaAUWoZfHN8DUwvNawSCr2yHieHKbWujxeGuPUuGPAdJHQRcC47xpBj7rKExxGE6T117vAAzSwc98UG3CC8Lb8UeoE7WWi9LCTdXqJpJFrwb8Zqc9HnqSVRvAxeaKgcueX36absXAxpqpAGUcH8YwYoeVmSYLsQKQbUAVrFe73eJyRtgxpcVEqrs4rBZ3KeDJUe5J2NJTNYKoUFcruqqu4N1XUFCECWXANsE9TLoQNyqDgNRcnHE4t8nw6THPJXQWCTBHK6mHvkVcj6SvGinvVGfMpeuA8MF1FFtZJTMnM31cuMBexK3m5mDxsbamJngQiPrcyVqK4smDpdiqhds7APGJbwKTHgst2u1P6").getOrThrow
val tree = addr.script
tree match {
case ErgoTree(_, _, Right(BlockValueWithInvalidBoolToSigmaProp(_)), _, _, _) =>
case ErgoTree(_, _, Right(BlockValueWithInvalidBoolToSigmaProp(_)), _, _, _, _) =>
}
}

Expand All @@ -741,7 +839,7 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit {
val addr = ErgoAddressEncoder.Mainnet.fromString("bpn7HXccD8wzY3x8XbymfBCh71aeci1FyM7JJgVx6mVY3vxznf2Pom7gtEgQBxr9UZb8cn9Z6GiXEjV7mB7ypRfH9Qpf3eCsd8qoEGmvsFqW2GyEvxgYWnJGd8QYTYZLBgGkSFcAeerLLDZwS1bos5oV4atMLnPDUJ6bH2EFFwVRdTAQKxaVMAhNtNYZxAcYtWhaYBJqYZM8ne3hGFrkWNTgNy7NxEDQ5LpBEM3rq6EqUENAGhH6THDL7RyU8AMkpr2vhosqEeqp4yXJmK887vU4qbnGGrMLX4w5GrgL9zLk41Vm6vzEUVLvp8XQJ8CULwkHNiKkNHRLeTk6BG4hwJsFoUSJJNWgsfputm8pEbaTuKfNG5u4NFmZ3YLfGsrqpr62c95QuNuD3g3FaoBUhxigkfNhKwFFtpKKsYerJvvp2Suh2eVRptFsCN15pjdkveUakhFsAo9j9YwhYVjjPpCWZgB8Wme8iZLRVfopSuDVdiqGsnmpJcuccmPsebgXPz3XvyjQazYE7WnR3Jrr1mRWHVEnJb3UU89JVPDpJPP1jUaSqmkKCkrPj3WMMQTDg17WW2DkhGKsb").getOrThrow
val tree = addr.script
tree match {
case ErgoTree(_, _, Right(BlockValueWithInvalidBoolToSigmaProp(_)), _, _, _) =>
case ErgoTree(_, _, Right(BlockValueWithInvalidBoolToSigmaProp(_)), _, _, _, _) =>
}
}

Expand All @@ -765,7 +863,7 @@ class ErgoTreeSpecification extends SigmaDslTesting with ContractsTestkit {
val addr = ErgoAddressEncoder.Mainnet.fromString("9drgw9DeDxHwdwYWT11yrYD3qCN3fzgnjA3eS5ZXEkR2Rrpd2x58R3HFViGAs93hYFqroBGShW5DG6h2tU7PqDURegny88hEhNKfLpWKBHpLMmfDwZwTM75MuYKXkmtyUmiMRAfGAVVjaSqThddra5ykZ4UrG8kgqkHSBTxtibVPmNff8Fx7Fy5q82xsZ95RFpjGkAX1QgtirHc3JP6QDZfHUFPx2gnyT9pDpYrKWEhjK8Ake779PoYqGzrL5gtuHyLqsC3WtLFMvP3QNfVtwPaqeCLEmjFmWYU6MV1A8fTJxGWDByiCMnva9wDgZgLh8wrSwttEGE9XHiZaWxWNWqVM8Ypn8aW8x8mKSjxQjF1BNxV41JTeGEMFDvY2HaNYgGbQhBbrTXFq9cvxuzPgXHtfwcbb2kiytr3YBCz8eNmbzp73LSKzRk4AFaTiTdFSSWJe72uLAurQTQBTzAzVgPptGWfrhMWmHCkN5qXQMpUQWNsCzwqRHeZzpSMVtWTyrBCGsbyuPitbukdLHZ8Wee7DtCy8j4Gkhewrwn23jVQu1ApN4uGAFEa29AL26bsMGD7tdu1StE9CKRVzbfEknaReqv6").getOrThrow
val tree = addr.script
tree match {
case ErgoTree(_, _, Right(BlockValueWithInvalidBoolToSigmaProp(_)), _, _, _) =>
case ErgoTree(_, _, Right(BlockValueWithInvalidBoolToSigmaProp(_)), _, _, _, _) =>
}
val lines = SigmaPPrint.tokenize(tree, 150, 300)
if (printDebugInfo) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ class ErgoTreeSerializerSpecification extends SerializationSpecification
property("SigmaProp.propBytes vs ErgoTree.serializer equivalence") {
forAll(MinSuccessful(100)) { sp: SigmaProp =>
val propBytes = sp.propBytes
val ergoTree = new ErgoTree(ErgoTree.DefaultHeader, EmptyConstants, Right(sp.toSigmaBoolean.toSigmaPropValue), 0, null, None)
val ergoTree = new ErgoTree(ErgoTree.DefaultHeader, EmptyConstants, Right(sp.toSigmaBoolean.toSigmaPropValue), 0, null, None, None)
val treeBytes = DefaultSerializer.serializeErgoTree(ergoTree)
treeBytes shouldBe propBytes.toArray
}
Expand Down
Loading