Skip to content

Commit

Permalink
Merge pull request #989 from ScorexFoundation/v6.0-serialize
Browse files Browse the repository at this point in the history
[6.0] Global.serialize method
kushti authored Sep 20, 2024
2 parents 3d88fc2 + 9c310c7 commit f5feee5
Showing 27 changed files with 819 additions and 60 deletions.
3 changes: 3 additions & 0 deletions core/shared/src/main/scala/sigma/SigmaDsl.scala
Original file line number Diff line number Diff line change
@@ -758,6 +758,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]
}
Original file line number Diff line number Diff line change
@@ -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]])
}
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
@@ -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 {
@@ -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
}
@@ -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
}
@@ -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
}

}
@@ -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 {
13 changes: 13 additions & 0 deletions data/shared/src/main/scala/sigma/SigmaDataReflection.scala
Original file line number Diff line number Diff line change
@@ -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 =>
@@ -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])
}
)
)
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
@@ -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
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
@@ -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.
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
@@ -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,
@@ -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]) = {
Loading

0 comments on commit f5feee5

Please sign in to comment.