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

[6.0] Global.serialize method #989

Merged
merged 19 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from 18 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
3 changes: 3 additions & 0 deletions core/shared/src/main/scala/sigma/SigmaDsl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,9 @@ trait SigmaDslBuilder {
/** Construct a new authenticated dictionary with given parameters and tree root digest. */
def avlTree(operationFlags: Byte, digest: Coll[Byte], keyLength: Int, valueLengthOpt: Option[Int]): AvlTree

/** Serializes the given `value` into bytes using the default serialization format. */
def serialize[T](value: T)(implicit cT: RType[T]): Coll[Byte]

/** Returns a byte-wise XOR of the two collections of bytes. */
def xor(l: Coll[Byte], r: Coll[Byte]): Coll[Byte]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,10 @@ object ReflectionData {
mkMethod(clazz, "sha256", Array[Class[_]](cColl)) { (obj, args) =>
obj.asInstanceOf[SigmaDslBuilder].sha256(args(0).asInstanceOf[Coll[Byte]])
},
mkMethod(clazz, "serialize", Array[Class[_]](classOf[Object], classOf[RType[_]])) { (obj, args) =>
obj.asInstanceOf[SigmaDslBuilder].serialize[Any](
args(0).asInstanceOf[Any])(args(1).asInstanceOf[RType[Any]])
},
mkMethod(clazz, "decodePoint", Array[Class[_]](cColl)) { (obj, args) =>
obj.asInstanceOf[SigmaDslBuilder].decodePoint(args(0).asInstanceOf[Coll[Byte]])
}
Expand Down
150 changes: 132 additions & 18 deletions core/shared/src/main/scala/sigma/serialization/CoreByteWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package sigma.serialization
import scorex.util.serialization.Writer.Aux
import scorex.util.serialization.{VLQByteBufferWriter, Writer}
import sigma.ast.SType
import sigma.serialization.CoreByteWriter.{Bits, DataInfo, U, Vlq, ZigZag}
import sigma.serialization.CoreByteWriter._

/** Implementation of [[Writer]] provided by `sigma-core` module.
*
* @param w destination [[Writer]] to which all the call got delegated.
*/
class CoreByteWriter(val w: Writer) extends Writer {
Expand All @@ -15,53 +16,131 @@ class CoreByteWriter(val w: Writer) extends Writer {

@inline override def newWriter(): Aux[CH] = w.newWriter()

@inline override def putChunk(chunk: CH): this.type = { w.putChunk(chunk); this }
@inline override def putChunk(chunk: CH): this.type = {
w.putChunk(chunk); this
}

@inline override def result(): CH = w.result()

@inline def put(x: Byte): this.type = { w.put(x); this }
@inline override def put(x: Byte): this.type = {
w.put(x); this
}

/** Put the given byte into the writer.
* @param x the byte to put into the writer
* @param info meta information about the data being put into the writer
*/
@inline def put(x: Byte, info: DataInfo[Byte]): this.type = {
w.put(x); this
}

override def putUByte(x: Int): this.type = {
super.putUByte(x)
}

/** Encode integer as an unsigned byte asserting the range check
* @param x integer value to encode (should be in the range of unsigned byte)
* @param info meta information about the data being put into the writer
* @return
* @throws AssertionError if x is outside of the unsigned byte range
*/
def putUByte(x: Int, info: DataInfo[U[Byte]]): this.type = {
super.putUByte(x)
}

@inline def putBoolean(x: Boolean): this.type = { w.putBoolean(x); this }
@inline override def putBoolean(x: Boolean): this.type = {
w.putBoolean(x); this
}

/** Encode boolean by delegating to the underlying writer.
* @param x boolean value to encode
* @param info meta information about the data being put into the writer
* @return
*/
@inline def putBoolean(x: Boolean, info: DataInfo[Boolean]): this.type = {
w.putBoolean(x); this
}

@inline def putShort(x: Short): this.type = { w.putShort(x); this }
@inline override def putShort(x: Short): this.type = {
w.putShort(x); this
}

/** Encode signed Short by delegating to the underlying writer.
*
* Use [[putUShort]] to encode values that are positive.
* @param x short value to encode
* @param info meta information about the data being put into the writer
*/
@inline def putShort(x: Short, info: DataInfo[Short]): this.type = {
w.putShort(x); this
}

@inline def putUShort(x: Int): this.type = { w.putUShort(x); this }
@inline override def putUShort(x: Int): this.type = {
w.putUShort(x); this
}

/** Encode Short that are positive by delegating to the underlying writer.
*
* Use [[putShort]] to encode values that might be negative.
* @param x unsigned short value (represented as Int) to encode
* @param info meta information about the data being put into the writer
*/
@inline def putUShort(x: Int, info: DataInfo[Vlq[U[Short]]]): this.type = {
w.putUShort(x); this
}

@inline def putInt(x: Int): this.type = { w.putInt(x); this }
@inline override def putInt(x: Int): this.type = {
w.putInt(x); this
}

/** Encode signed Int by delegating to the underlying writer.
* Use [[putUInt]] to encode values that are positive.
*
* @param x integer value to encode
* @param info meta information about the data being put into the writer
*/
@inline def putInt(x: Int, info: DataInfo[Int]): this.type = {
w.putInt(x); this
}

@inline def putUInt(x: Long): this.type = { w.putUInt(x); this }
@inline override def putUInt(x: Long): this.type = {
w.putUInt(x); this
}

/** Encode Int that are positive by delegating to the underlying writer.
* Use [[putInt]] to encode values that might be negative.
*
* @param x unsigned integer value (represented as Long) to encode
* @param info meta information about the data being put into the writer
*/
@inline def putUInt(x: Long, info: DataInfo[Vlq[U[Int]]]): this.type = {
w.putUInt(x); this
}

@inline def putLong(x: Long): this.type = { w.putLong(x); this }
@inline override def putLong(x: Long): this.type = {
w.putLong(x); this
}

/** Encode signed Long by delegating to the underlying writer.
* Use [[putULong]] to encode values that are positive.
*
* @param x long value to encode
* @param info meta information about the data being put into the writer
*/
@inline def putLong(x: Long, info: DataInfo[Vlq[ZigZag[Long]]]): this.type = {
w.putLong(x); this
}

@inline def putULong(x: Long): this.type = { w.putULong(x); this }
@inline override def putULong(x: Long): this.type = {
w.putULong(x); this
}

/** Encode Long that are positive by delegating to the underlying writer.
* Use [[putLong]] to encode values that might be negative.
*
* @param x unsigned long value to encode
* @param info meta information about the data being put into the writer
*/
@inline def putULong(x: Long, info: DataInfo[Vlq[U[Long]]]): this.type = {
w.putULong(x); this
}
Expand All @@ -71,7 +150,15 @@ class CoreByteWriter(val w: Writer) extends Writer {
length: Int): this.type = {
w.putBytes(xs, offset, length); this
}
@inline def putBytes(xs: Array[Byte]): this.type = { w.putBytes(xs); this }

@inline override def putBytes(xs: Array[Byte]): this.type = {
w.putBytes(xs); this
}

/** Encode an array of bytes by delegating to the underlying writer.
* @param xs array of bytes to encode
* @param info meta information about the data being put into the writer
*/
@inline def putBytes(xs: Array[Byte], info: DataInfo[Array[Byte]]): this.type = {
w.putBytes(xs); this
}
Expand All @@ -84,29 +171,51 @@ class CoreByteWriter(val w: Writer) extends Writer {
this
}

@inline def putBits(xs: Array[Boolean]): this.type = { w.putBits(xs); this }
@inline override def putBits(xs: Array[Boolean]): this.type = {
w.putBits(xs); this
}

/** Encode an array of boolean values as a bit array (packing bits into bytes)
*
* @param xs array of boolean values
* @param info meta information about the data being put into the writer
*/
@inline def putBits(xs: Array[Boolean], info: DataInfo[Bits]): this.type = {
w.putBits(xs);
this
w.putBits(xs); this
}

@inline def putOption[T](x: Option[T])(putValueC: (this.type, T) => Unit): this.type = {
@inline override def putOption[T](x: Option[T])(putValueC: (this.type, T) => Unit): this.type = {
w.putOption(x) { (_, v) =>
putValueC(this, v)
}
this
}

@inline def putShortString(s: String): this.type = { w.putShortString(s); this }
@inline override def putShortString(s: String): this.type = {
w.putShortString(s);
this
}

// TODO refactor: move to Writer
@inline def toBytes: Array[Byte] = w match {
case wr: VLQByteBufferWriter => wr.toBytes
}

@inline def putType[T <: SType](x: T): this.type = { TypeSerializer.serialize(x, this); this }
/** Serialize the given type into the writer using [[TypeSerializer]].
* @param x the type to put into the writer
*/
@inline def putType[T <: SType](x: T): this.type = {
TypeSerializer.serialize(x, this)
this
}

/** Serialize the given type into the writer using [[TypeSerializer]].
* @param x the type to put into the writer
* @param info meta information about the data being put into the writer
*/
@inline def putType[T <: SType](x: T, info: DataInfo[SType]): this.type = {
TypeSerializer.serialize(x, this); this
TypeSerializer.serialize(x, this)
this
}

}
Expand Down Expand Up @@ -226,6 +335,11 @@ object CoreByteWriter {
* @param description argument description. */
case class ArgInfo(name: String, description: String)

/** Represents meta information about serialized data.
* Passed as additional argument of serializer methods.
* Can be used to automatically generate format specifications based on
* the actual collected method invocations.
*/
case class DataInfo[T](info: ArgInfo, format: FormatDescriptor[T])

object DataInfo {
Expand Down
13 changes: 13 additions & 0 deletions data/shared/src/main/scala/sigma/SigmaDataReflection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@ object SigmaDataReflection {
)
)

registerClassEntry(classOf[LongToByteArray],
constructors = Array(
mkConstructor(Array(classOf[Value[_]])) { args =>
new LongToByteArray(args(0).asInstanceOf[Value[SLong.type]])
}
)
)

registerClassEntry(classOf[CalcBlake2b256],
constructors = Array(
mkConstructor(Array(classOf[Value[_]])) { args =>
Expand Down Expand Up @@ -322,6 +330,11 @@ object SigmaDataReflection {
args(1).asInstanceOf[SigmaDslBuilder],
args(2).asInstanceOf[Coll[Byte]],
args(3).asInstanceOf[Coll[Byte]])(args(4).asInstanceOf[ErgoTreeEvaluator])
},
mkMethod(clazz, "serialize_eval", Array[Class[_]](classOf[MethodCall], classOf[SigmaDslBuilder], classOf[Object], classOf[ErgoTreeEvaluator])) { (obj, args) =>
obj.asInstanceOf[SGlobalMethods.type].serialize_eval(args(0).asInstanceOf[MethodCall],
args(1).asInstanceOf[SigmaDslBuilder],
args(2).asInstanceOf[SType#WrappedType])(args(3).asInstanceOf[ErgoTreeEvaluator])
}
)
)
Expand Down
2 changes: 1 addition & 1 deletion data/shared/src/main/scala/sigma/ast/ErgoTree.scala
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ object ErgoTree {
* */
def withSegregation(header: HeaderType, prop: SigmaPropValue): ErgoTree = {
val constantStore = new ConstantStore()
val w = SigmaSerializer.startWriter(constantStore)
val w = SigmaSerializer.startWriter(Some(constantStore))
// serialize value and segregate constants into constantStore
ValueSerializer.serialize(prop, w)
val extractedConstants = constantStore.getAll
Expand Down
4 changes: 3 additions & 1 deletion data/shared/src/main/scala/sigma/ast/SMethod.scala
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ case class SMethod(
/** Operation descriptor of this method. */
lazy val opDesc = MethodDesc(this)

/** Return true if this method has runtime type parameters */
/** Return true if this method has explicit type parameters, which need to be serialized
* as part of [[MethodCall]].
*/
def hasExplicitTypeArgs: Boolean = explicitTypeArgs.nonEmpty

/** Finds and keeps the [[RMethod]] instance which corresponds to this method descriptor.
Expand Down
21 changes: 20 additions & 1 deletion data/shared/src/main/scala/sigma/ast/SigmaPredef.scala
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,24 @@ object SigmaPredef {
ArgInfo("default", "optional default value, if register is not available")))
)

val SerializeFunc = PredefinedFunc("serialize",
Lambda(Seq(paramT), Array("value" -> tT), SByteArray, None),
irInfo = PredefFuncInfo(
irBuilder = { case (_, args @ Seq(value)) =>
MethodCall.typed[Value[SCollection[SByte.type]]](
Global,
SGlobalMethods.serializeMethod.withConcreteTypes(Map(tT -> value.tpe)),
args.toIndexedSeq,
Map()
)
}),
docInfo = OperationInfo(MethodCall,
"""Serializes the given `value` into bytes using the default serialization format.
""".stripMargin,
Seq(ArgInfo("value", "value to serialize"))
)
)

val globalFuncs: Map[String, PredefinedFunc] = Seq(
AllOfFunc,
AnyOfFunc,
Expand Down Expand Up @@ -429,7 +447,8 @@ object SigmaPredef {
AvlTreeFunc,
SubstConstantsFunc,
ExecuteFromVarFunc,
ExecuteFromSelfRegFunc
ExecuteFromSelfRegFunc,
SerializeFunc
).map(f => f.name -> f).toMap

def comparisonOp(symbolName: String, opDesc: ValueCompanion, desc: String, args: Seq[ArgInfo]) = {
Expand Down
Loading
Loading