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

Assets support #189

Merged
merged 22 commits into from
Jul 5, 2018
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ to use it in your project you first need to:
3. In your own project add library dependency
```
libraryDependencies ++= Seq(
"org.scorexplatform" %% "sigma-state" % "0.9.3"
"org.scorexplatform" %% "sigma-state" % "0.9.4"
)
```
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ name := "sigma-state"

lazy val commonSettings = Seq(
organization := "org.scorexfoundation",
version := "0.9.4-SNAPSHOT",
version := "0.9.4",
scalaVersion := "2.12.4",
licenses := Seq("CC0" -> url("https://creativecommons.org/publicdomain/zero/1.0/legalcode")),
homepage := Some(url("https://github.com/ScorexFoundation/sigmastate-interpreter")),
Expand Down
123 changes: 76 additions & 47 deletions src/main/scala/org/ergoplatform/ErgoBox.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package org.ergoplatform
import java.util.Arrays

import com.google.common.primitives.Shorts
import org.ergoplatform.ErgoBox.NonMandatoryIdentifier
import org.ergoplatform.ErgoBox.{NonMandatoryRegisterId, TokenId}
import scorex.crypto.authds.ADKey
import scorex.crypto.encode.Base16
import scorex.crypto.hash.{Blake2b256, Digest32}
import sigmastate.Values._
import sigmastate._
import sigmastate.serialization.Serializer.{Consumed, Position}
import sigmastate.serialization.{DataSerializer, Serializer}
import sigmastate.utxo.CostTable.Cost

Expand Down Expand Up @@ -38,46 +39,41 @@ import scala.runtime.ScalaRunTime
* @param additionalRegisters
*/
class ErgoBox private(
override val value: Long,
override val proposition: Value[SBoolean.type],
override val additionalRegisters: Map[NonMandatoryIdentifier, _ <: EvaluatedValue[_ <: SType]] = Map(),
val transactionId: Array[Byte],
val index: Short
) extends ErgoBoxCandidate(value, proposition, additionalRegisters) {
override val value: Long,
override val proposition: Value[SBoolean.type],
override val additionalTokens: Seq[(TokenId, Long)] = Seq(),
override val additionalRegisters: Map[NonMandatoryRegisterId, _ <: EvaluatedValue[_ <: SType]] = Map(),
val transactionId: Array[Byte],
val index: Short
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kushti why we need index? Each box's position is uniquely defined by outputCandidates. At least maybe we don't need to serialize it and save 2 bytes for each box?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ergomorphic a box is a first-class citizen in Ergo. It is living in a state, which is basically an id -> box dictionary. Thus we need for an unique id for every box (so two boxes of the same value and proposition should have different ids), thus we store (unique) transaction id and also an output number in the box.

) extends ErgoBoxCandidate(value, proposition, additionalTokens, additionalRegisters) {

import ErgoBox._

lazy val id: BoxId = ADKey @@ Blake2b256.hash(bytes)

override lazy val cost = (bytesWithNoRef.size / 1024 + 1) * Cost.BoxPerKilobyte

override def get(identifier: RegisterIdentifier): Option[Value[SType]] = {
override def get(identifier: RegisterId): Option[Value[SType]] = {
identifier match {
case R2 => Some(ByteArrayConstant(transactionId ++ Shorts.toByteArray(index)))
case ReferenceRegId => Some(ByteArrayConstant(transactionId ++ Shorts.toByteArray(index)))
case _ => super.get(identifier)
}
}

lazy val bytes: Array[Byte] = {
val w = Serializer.startWriter()
DataSerializer.serialize[SBox.type](this, SBox, w)
w.toBytes
}
lazy val bytes: Array[Byte] = ErgoBox.serializer.toBytes(this)

override def equals(arg: Any): Boolean = arg match {
case x: ErgoBox =>
super.equals(x) &&
Arrays.equals(transactionId, x.transactionId) &&
index == x.index
case x: ErgoBox => Arrays.equals(id, x.id)
case _ => false
}

override def hashCode() = ScalaRunTime._hashCode((value, proposition, additionalRegisters, index))
override def hashCode() = ScalaRunTime._hashCode((value, proposition, additionalTokens, additionalRegisters, index))

def toCandidate: ErgoBoxCandidate = new ErgoBoxCandidate(value, proposition, additionalRegisters)
def toCandidate: ErgoBoxCandidate = new ErgoBoxCandidate(value, proposition, additionalTokens, additionalRegisters)

override def toString: Idn = s"ErgoBox(${Base16.encode(id)},$value,$proposition,${Base16.encode(transactionId)}," +
s"$index,$additionalRegisters)"
override def toString: Idn = s"ErgoBox(${Base16.encode(id)},$value,$proposition," +
s"tokens: (${additionalTokens.map(t => Base16.encode(t._1)+":"+t._2)}), ${Base16.encode(transactionId)}, " +
s"$index, $additionalRegisters)"
}

object ErgoBox {
Expand All @@ -86,36 +82,69 @@ object ErgoBox {
val size: Short = 32
}

type TokenId = Digest32
object TokenId {
val size: Short = 32
}

type Amount = Long

trait RegisterId {val number: Byte}
abstract class MandatoryRegisterId(override val number: Byte, purpose: String) extends RegisterId
abstract class NonMandatoryRegisterId(override val number: Byte) extends RegisterId

object R0 extends MandatoryRegisterId(0, "Monetary value, in Ergo tokens")
object R1 extends MandatoryRegisterId(1, "Guarding script")
object R2 extends MandatoryRegisterId(2, "Secondary tokens")
object R3 extends MandatoryRegisterId(3, "Reference to transaction and output id where the box was created")
object R4 extends NonMandatoryRegisterId(4)
object R5 extends NonMandatoryRegisterId(5)
object R6 extends NonMandatoryRegisterId(6)
object R7 extends NonMandatoryRegisterId(7)
object R8 extends NonMandatoryRegisterId(8)
object R9 extends NonMandatoryRegisterId(9)

val ValueRegId = R0
val ScriptRegId = R1
val TokensRegId = R2
val ReferenceRegId = R3

val MaxTokens: Byte = 4

val maxRegisters = 10
val mandatoryRegisters: Vector[MandatoryRegisterId] = Vector(R0, R1, R2, R3)
val nonMandatoryRegisters: Vector[NonMandatoryRegisterId] = Vector(R4, R5, R6, R7, R8, R9)
val startingNonMandatoryIndex = nonMandatoryRegisters.head.number
.ensuring(_ == mandatoryRegisters.last.number + 1)

val allRegisters = (mandatoryRegisters ++ nonMandatoryRegisters).ensuring(_.size == maxRegisters)
val mandatoryRegistersCount = mandatoryRegisters.size.toByte
val nonMandatoryRegistersCount = nonMandatoryRegisters.size.toByte

val registerByName: Map[String, RegisterId] = allRegisters.map(r => s"R${r.number}" -> r).toMap
val registerByIndex: Map[Byte, RegisterId] = allRegisters.map(r => r.number -> r).toMap

def findRegisterByIndex(i: Byte): Option[RegisterId] = registerByIndex.get(i)

def apply(value: Long,
proposition: Value[SBoolean.type],
additionalRegisters: Map[NonMandatoryIdentifier, _ <: EvaluatedValue[_ <: SType]] = Map(),
additionalTokens: Seq[(TokenId, Long)] = Seq(),
additionalRegisters: Map[NonMandatoryRegisterId, _ <: EvaluatedValue[_ <: SType]] = Map(),
transactionId: Array[Byte] = Array.fill(32)(0: Byte),
boxId: Short = 0): ErgoBox =
new ErgoBox(value, proposition, additionalRegisters, transactionId, boxId)

abstract class RegisterIdentifier(val number: Byte)
abstract class NonMandatoryIdentifier(override val number: Byte) extends RegisterIdentifier(number)

object R0 extends RegisterIdentifier(0)
object R1 extends RegisterIdentifier(1)
object R2 extends RegisterIdentifier(2)
object R3 extends NonMandatoryIdentifier(3)
object R4 extends NonMandatoryIdentifier(4)
object R5 extends NonMandatoryIdentifier(5)
object R6 extends NonMandatoryIdentifier(6)
object R7 extends NonMandatoryIdentifier(7)
object R8 extends NonMandatoryIdentifier(8)
object R9 extends NonMandatoryIdentifier(9)

val maxRegisters = 10
val startingNonMandatoryIndex = 3
val nonMandatoryRegisters = Vector(R3, R4, R5, R6, R7, R8, R9).ensuring(_.head.number == startingNonMandatoryIndex)
new ErgoBox(value, proposition, additionalTokens, additionalRegisters, transactionId, boxId)

val allRegisters = (Vector(R0, R1, R2) ++ nonMandatoryRegisters).ensuring(_.size == maxRegisters)
val registerByName: Map[String, RegisterIdentifier] = allRegisters.map(r => s"R${r.number}" -> r).toMap
val registerByIndex: Map[Byte, RegisterIdentifier] = allRegisters.map(r => r.number -> r).toMap
object serializer extends Serializer[ErgoBox, ErgoBox] {
override def toBytes(obj: ErgoBox): Array[Byte] = {
val w = Serializer.startWriter()
DataSerializer.serialize[SBox.type](obj, SBox, w)
w.toBytes
}

def findRegisterByIndex(i: Byte): Option[RegisterIdentifier] = registerByIndex.get(i)
}
override def parseBody(bytes: Array[Byte], pos: Position): (ErgoBox, Consumed) = {
val w = Serializer.startReader(bytes, pos)
val box = DataSerializer.deserialize[SBox.type](SBox, w)
box -> (w.position - pos)
}
}
}
98 changes: 70 additions & 28 deletions src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala
Original file line number Diff line number Diff line change
@@ -1,54 +1,71 @@
package org.ergoplatform

import java.util

import com.google.common.primitives.Longs
import org.ergoplatform.ErgoBox._
import scorex.crypto.encode.Base16
import scorex.crypto.hash.Digest32
import sigmastate.{SBoolean, SType}
import sigmastate.Values.{ByteArrayConstant, Value, EvaluatedValue, LongConstant}
import sigmastate.serialization.Serializer.{Position, Consumed}
import sigmastate.serialization.{ValueSerializer, Serializer}
import sigmastate._
import sigmastate.Values._
import sigmastate.serialization.Serializer.{Consumed, Position}
import sigmastate.serialization.{Serializer, ValueSerializer}
import sigmastate.utxo.CostTable.Cost
import STuple.STokenType

import scala.annotation.tailrec
import scala.runtime.ScalaRunTime
import scala.util.Try

class ErgoBoxCandidate(val value: Long,
val proposition: Value[SBoolean.type],
val additionalRegisters: Map[NonMandatoryIdentifier, _ <: EvaluatedValue[_ <: SType]] = Map()) {
val additionalTokens: Seq[(TokenId, Long)] = Seq(),
val additionalRegisters: Map[NonMandatoryRegisterId, _ <: EvaluatedValue[_ <: SType]] = Map()) {

lazy val cost = (bytesWithNoRef.length / 1024 + 1) * Cost.BoxPerKilobyte

val propositionBytes: Array[Byte] = proposition.bytes

lazy val bytesWithNoRef: Array[Byte] = ErgoBoxCandidate.serializer.toBytes(this)

def toBox(txId: Array[Byte], boxId: Short) = ErgoBox(value, proposition, additionalRegisters, txId, boxId)
def toBox(txId: Array[Byte], boxId: Short) =
ErgoBox(value, proposition, additionalTokens, additionalRegisters, txId, boxId)

def get(identifier: RegisterIdentifier): Option[Value[SType]] = {
def get(identifier: RegisterId): Option[Value[SType]] = {
identifier match {
case R0 => Some(LongConstant(value))
case R1 => Some(ByteArrayConstant(propositionBytes))
case R2 => None
case n: NonMandatoryIdentifier => additionalRegisters.get(n)
case ValueRegId => Some(LongConstant(value))
case ScriptRegId => Some(ByteArrayConstant(propositionBytes))
case TokensRegId =>
val tokenTuples = additionalTokens.map { case (id, amount) =>
Tuple(ByteArrayConstant(id), LongConstant(amount))
}.toIndexedSeq
Some(ConcreteCollection(tokenTuples, STokenType))
case ReferenceRegId => None
case n: NonMandatoryRegisterId => additionalRegisters.get(n)
}
}

override def equals(arg: Any): Boolean = arg match {
case x: ErgoBoxCandidate =>
value == x.value &&
proposition == x.proposition &&
additionalRegisters == x.additionalRegisters
case _ => false
override def equals(arg: Any): Boolean = {
arg match {
case x: ErgoBoxCandidate => util.Arrays.equals(bytesWithNoRef, x.bytesWithNoRef)
case _ => false
}
}

override def hashCode() = ScalaRunTime._hashCode((value, proposition, additionalTokens, additionalRegisters))


override def toString: Idn = s"ErgoBoxCandidate($value, $proposition," +
s"tokens: (${additionalTokens.map(t => Base16.encode(t._1)+":"+t._2).mkString(", ")}), $additionalRegisters)"
}

object ErgoBoxCandidate {

object serializer extends Serializer[ErgoBoxCandidate, ErgoBoxCandidate] {
@tailrec
def collectRegister(obj: ErgoBoxCandidate,
collectedBytes: Array[Byte],
collectedRegisters: Byte): (Array[Byte], Byte) = {
collectedBytes: Array[Byte],
collectedRegisters: Byte): (Array[Byte], Byte) = {
val regIdx = (startingNonMandatoryIndex + collectedRegisters).toByte
val regByIdOpt = registerByIndex.get(regIdx)
regByIdOpt.flatMap(obj.get) match {
Expand All @@ -63,26 +80,51 @@ object ErgoBoxCandidate {
val propBytes = obj.propositionBytes
val (regBytes, regNum) = collectRegister(obj, Array.emptyByteArray, 0: Byte)

Longs.toByteArray(obj.value) ++ propBytes ++ (regNum +: regBytes)
val tokensCount = obj.additionalTokens.size.toByte
val tokenBytes = if (obj.additionalTokens.nonEmpty) {
obj.additionalTokens.map { case (id, amount) =>
id ++ Longs.toByteArray(amount)
}.reduce(_ ++ _)
} else {
Array.emptyByteArray
}

Longs.toByteArray(obj.value) ++ propBytes ++ (tokensCount +: tokenBytes) ++ (regNum +: regBytes)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kushti use Writer to serialize data in a byte array and avoid allocation of intermediate arrays. Also code will be shorter and easier to understand.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ergomorphic @kushti FWIW I've switched the ErgoBoxCandidate serializer to reader/writer API here -

override def serializeBody(obj: ErgoBoxCandidate, w: ByteWriter): Unit = {

}

override def parseBytes(bytes: Array[Byte]): Try[ErgoBoxCandidate] = Try {
parseBody(bytes, 0)._1
}

override def parseBody(bytes: Array[Byte], pos: Position): (ErgoBoxCandidate, Consumed) = {
val value = Longs.fromByteArray(bytes.slice(pos, pos + 8))
val (prop, consumed) = ValueSerializer.deserialize(bytes, pos + 8)
val posAfterProp = pos + 8 + consumed
val regNum = bytes(posAfterProp)
val posAfterRegNum = posAfterProp + 1
val (regs, finalPos) = (0 until regNum).foldLeft(Map[NonMandatoryIdentifier, EvaluatedValue[SType]]() -> posAfterRegNum) { case ((m, p), regIdx) =>
val regId = registerByIndex((regIdx + startingNonMandatoryIndex).toByte).asInstanceOf[NonMandatoryIdentifier]
var curPos = pos

val value = Longs.fromByteArray(bytes.slice(curPos, curPos + 8))
curPos += 8

val (prop, consumed) = ValueSerializer.deserialize(bytes, curPos)
curPos += consumed

val tokensNum = bytes(curPos)
curPos += 1

val additionalTokens = (1 to tokensNum).map{_ =>
val id = Digest32 @@ (bytes.slice(curPos, curPos + 32))
val amount = Longs.fromByteArray(bytes.slice(curPos + 32, curPos + 40))
curPos += 40
id -> amount
}

val regNum = bytes(curPos)
curPos += 1

val (regs, finalPos) = (0 until regNum).foldLeft(Map[NonMandatoryRegisterId, EvaluatedValue[SType]]() -> curPos) { case ((m, p), regIdx) =>
val regId = registerByIndex((regIdx + startingNonMandatoryIndex).toByte).asInstanceOf[NonMandatoryRegisterId]
val (reg, consumed) = ValueSerializer.deserialize(bytes, p)
(m.updated(regId, reg.asInstanceOf[EvaluatedValue[SType]]), p + consumed)
}
val finalConsumed = finalPos - pos
new ErgoBoxCandidate(value, prop.asInstanceOf[Value[SBoolean.type]], regs) -> finalConsumed
new ErgoBoxCandidate(value, prop.asInstanceOf[Value[SBoolean.type]], additionalTokens, regs) -> finalConsumed
}

override def serializeBody(obj: ErgoBoxCandidate): Array[Byte] = toBytes(obj)
Expand Down
6 changes: 3 additions & 3 deletions src/main/scala/sigmastate/serialization/DataSerializer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package sigmastate.serialization
import java.math.BigInteger

import org.ergoplatform.ErgoBox
import org.ergoplatform.ErgoBox.NonMandatoryIdentifier
import org.ergoplatform.ErgoBox.NonMandatoryRegisterId
import scorex.crypto.authds.ADDigest
import sigmastate.SCollection.SByteArray
import sigmastate.Values.EvaluatedValue
Expand Down Expand Up @@ -130,13 +130,13 @@ object DataSerializer {
val nRegs = r.getUByte()
val regs = (0 until nRegs).map { iReg =>
val regId = ErgoBox.startingNonMandatoryIndex + iReg
val reg = ErgoBox.findRegisterByIndex(regId.toByte).get.asInstanceOf[NonMandatoryIdentifier]
val reg = ErgoBox.findRegisterByIndex(regId.toByte).get.asInstanceOf[NonMandatoryRegisterId]
val v = r.getValue().asInstanceOf[EvaluatedValue[SType]]
(reg, v)
}.toMap
val transId = r.getBytes(32)
val boxId = r.getUShort().toShort
val box = ErgoBox(value, proposition, regs, transId, boxId)
val box = ErgoBox(value, proposition, Seq(), regs, transId, boxId)
box

case SAvlTree =>
Expand Down
2 changes: 2 additions & 0 deletions src/main/scala/sigmastate/types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,8 @@ object STuple {

def apply(items: SType*): STuple = STuple(items.toIndexedSeq)
val componentNames = Range(1, 31).map(i => s"_$i")

val STokenType = STuple(SByteArray, SLong)
}

case class SFunc(tDom: IndexedSeq[SType], tRange: SType, tpeArgs: Seq[STypeIdent] = Nil) extends SType {
Expand Down
Loading