From 16af268db73971f3bf5e910ae3d6fc2289e36beb Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 3 May 2024 17:14:25 +0300 Subject: [PATCH 01/10] checking header version in checkPow() --- .../shared/src/main/scala/sigmastate/eval/CHeader.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/interpreter/shared/src/main/scala/sigmastate/eval/CHeader.scala b/interpreter/shared/src/main/scala/sigmastate/eval/CHeader.scala index 3bd0dd62f9..c23118100b 100644 --- a/interpreter/shared/src/main/scala/sigmastate/eval/CHeader.scala +++ b/interpreter/shared/src/main/scala/sigmastate/eval/CHeader.scala @@ -39,7 +39,11 @@ case class CHeader( } override def checkPow: Boolean = { - Autolykos2PowValidation.checkPoWForVersion2(this) + if (version == 1) { + throw new Exception("Autolykos v1 is not supported") //todo: more specific exception? + } else { + Autolykos2PowValidation.checkPoWForVersion2(this) + } } } From 60aa790b9f7cd71e076ab3925aa30bb6612778af Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Sun, 5 May 2024 21:37:59 +0300 Subject: [PATCH 02/10] close #969 : versioned support for Header (de)serialization --- .../scala/org/ergoplatform/ErgoHeader.scala | 153 ++++++++++++++++++ .../org/ergoplatform/HeaderWithoutPow.scala | 6 +- .../src/main/scala/sigma/ast/methods.scala | 2 +- .../src/main/scala/sigma/data/CHeader.scala | 132 +++++++++++++++ .../sigma/serialization/DataSerializer.scala | 14 +- .../main/scala/sigmastate/eval/CHeader.scala | 57 ------- .../DataSerializerSpecification.scala | 18 ++- .../MethodCallSerializerSpecification.scala | 12 +- .../SerializationSpecification.scala | 2 +- .../special/sigma/SigmaTestingData.scala | 4 +- .../scala/org/ergoplatform/sdk/js/Isos.scala | 4 +- .../org/ergoplatform/sdk/JsonCodecs.scala | 4 +- 12 files changed, 334 insertions(+), 74 deletions(-) create mode 100644 data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala create mode 100644 data/shared/src/main/scala/sigma/data/CHeader.scala delete mode 100644 interpreter/shared/src/main/scala/sigmastate/eval/CHeader.scala diff --git a/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala b/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala new file mode 100644 index 0000000000..cc678003d6 --- /dev/null +++ b/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala @@ -0,0 +1,153 @@ +package org.ergoplatform + +import org.bouncycastle.util.BigIntegers +import scorex.crypto.authds.ADDigest +import scorex.crypto.hash.{Blake2b256, Digest32} +import scorex.util.ModifierId +import sigma.Colls +import sigma.crypto.{CryptoConstants, CryptoFacade, EcPointType} +import sigma.serialization.{GroupElementSerializer, SigmaByteReader, SigmaByteWriter, SigmaSerializer} + + +/** + * Solution for an Autolykos PoW puzzle. + * + * In Autolykos v.1 all the four fields are used, in Autolykos v.2 only pk and n fields are used. + * + * @param pk - miner public key. Should be used to collect block rewards + * @param w - one-time public key. Prevents revealing of miners secret + * @param n - nonce (8 bytes) + * @param d - distance between pseudo-random number, corresponding to nonce `n` and a secret, + * corresponding to `pk`. The lower `d` is, the harder it was to find this solution. + */ +case class AutolykosSolution(pk: EcPointType, + w: EcPointType, + n: Array[Byte], + d: BigInt) { + + val encodedPk: Array[Byte] = GroupElementSerializer.toBytes(pk) + +} + + +object AutolykosSolution { + // "pk", "w" and "d" values for Autolykos v2 solution, where they not passed from outside + val pkForV2: EcPointType = CryptoConstants.dlogGroup.identity + val wForV2: EcPointType = CryptoConstants.dlogGroup.generator + val dForV2: BigInt = 0 + + object sigmaSerializerV1 extends SigmaSerializer[AutolykosSolution, AutolykosSolution] { + override def serialize(s: AutolykosSolution, w: SigmaByteWriter): Unit = { + GroupElementSerializer.serialize(s.pk, w) + GroupElementSerializer.serialize(s.w, w) + require(s.n.length == 8) // non-consensus check on prover side + w.putBytes(s.n) + val dBytes = BigIntegers.asUnsignedByteArray(s.d.bigInteger) + w.putUByte(dBytes.length) + w.putBytes(dBytes) + } + + override def parse(r: SigmaByteReader): AutolykosSolution = { + val pk = GroupElementSerializer.parse(r) + val w = GroupElementSerializer.parse(r) + val nonce = r.getBytes(8) + val dBytesLength = r.getUByte() + val d = BigInt(BigIntegers.fromUnsignedByteArray(r.getBytes(dBytesLength))) + AutolykosSolution(pk, w, nonce, d) + } + } + + object sigmaSerializerV2 extends SigmaSerializer[AutolykosSolution, AutolykosSolution] { + override def serialize(s: AutolykosSolution, w: SigmaByteWriter): Unit = { + GroupElementSerializer.serialize(s.pk, w) + require(s.n.length == 8) // non-consensus check on prover side + w.putBytes(s.n) + } + + override def parse(r: SigmaByteReader): AutolykosSolution = { + val pk = GroupElementSerializer.parse(r) + val nonce = r.getBytes(8) + AutolykosSolution(pk, wForV2, nonce, dForV2) + } + } +} + +/** + * Header of a block. It authenticates link to a previous block, other block sections + * (transactions, UTXO set transformation proofs, extension), UTXO set, votes for parameters + * to be changed and proof-of-work related data. + * + * @param version - protocol version + * @param parentId - id of a parent block header + * @param ADProofsRoot - digest of UTXO set transformation proofs + * @param stateRoot - AVL+ tree digest of UTXO set (after the block) + * @param transactionsRoot - Merkle tree digest of transactions in the block (BlockTransactions section) + * @param timestamp - block generation time reported by a miner + * @param nBits - difficulty encoded + * @param height - height of the block (genesis block height == 1) + * @param extensionRoot - Merkle tree digest of the extension section of the block + * @param powSolution - solution for the proof-of-work puzzle + * @param votes - votes for changing system parameters + * @param _bytes - serialized bytes of the header when not `null` + */ +case class ErgoHeader(override val version: ErgoHeader.Version, + override val parentId: ModifierId, + override val ADProofsRoot: Digest32, + override val stateRoot: ADDigest, //33 bytes! extra byte with tree height here! + override val transactionsRoot: Digest32, + override val timestamp: ErgoHeader.Timestamp, + override val nBits: Long, //actually it is unsigned int + override val height: Int, + override val extensionRoot: Digest32, + powSolution: AutolykosSolution, + override val votes: Array[Byte], //3 bytes + override val unparsedBytes: Array[Byte], + _bytes: Array[Byte]) extends + HeaderWithoutPow(version, parentId, ADProofsRoot, stateRoot, transactionsRoot, timestamp, + nBits, height, extensionRoot, votes, unparsedBytes) { + + lazy val bytes = if(_bytes != null) { + _bytes + } else { + ErgoHeader.sigmaSerializer.toBytes(this) + } + + lazy val serializedId: Array[Byte] = Blake2b256.hash(bytes) + + lazy val id = Colls.fromArray(serializedId) + +} + + +object ErgoHeader { + + type Timestamp = Long + + type Version = Byte + + object sigmaSerializer extends SigmaSerializer[ErgoHeader, ErgoHeader] { + override def serialize(hdr: ErgoHeader, w: SigmaByteWriter): Unit = { + HeaderWithoutPowSerializer.serialize(hdr, w) + if (hdr.version == 1) { + AutolykosSolution.sigmaSerializerV1.serialize(hdr.powSolution, w) + } else { + AutolykosSolution.sigmaSerializerV2.serialize(hdr.powSolution, w) + } + } + + override def parse(r: SigmaByteReader): ErgoHeader = { + val start = r.position + val headerWithoutPow = HeaderWithoutPowSerializer.parse(r) + val powSolution = if (headerWithoutPow.version == 1) { + AutolykosSolution.sigmaSerializerV1.parse(r) + } else { + AutolykosSolution.sigmaSerializerV2.parse(r) + } + val end = r.position + val len = end - start + r.position = start + val headerBytes = r.getBytes(len) // also moves position back to end + headerWithoutPow.toHeader(powSolution, headerBytes) + } + } +} \ No newline at end of file diff --git a/data/shared/src/main/scala/org/ergoplatform/HeaderWithoutPow.scala b/data/shared/src/main/scala/org/ergoplatform/HeaderWithoutPow.scala index d29cc6cbd8..56e2eafb1b 100644 --- a/data/shared/src/main/scala/org/ergoplatform/HeaderWithoutPow.scala +++ b/data/shared/src/main/scala/org/ergoplatform/HeaderWithoutPow.scala @@ -21,7 +21,11 @@ class HeaderWithoutPow(val version: Byte, // 1 byte val height: Int, val extensionRoot: Digest32, val votes: Array[Byte], //3 bytes - val unparsedBytes: Array[Byte]) + val unparsedBytes: Array[Byte]) { + def toHeader(powSolution: AutolykosSolution, bytes: Array[Byte]): ErgoHeader = + ErgoHeader(version, parentId, ADProofsRoot, stateRoot, transactionsRoot, timestamp, + nBits, height, extensionRoot, powSolution, votes, unparsedBytes, bytes) +} object HeaderWithoutPow { diff --git a/data/shared/src/main/scala/sigma/ast/methods.scala b/data/shared/src/main/scala/sigma/ast/methods.scala index e58fa35adb..97c2302b2d 100644 --- a/data/shared/src/main/scala/sigma/ast/methods.scala +++ b/data/shared/src/main/scala/sigma/ast/methods.scala @@ -1460,7 +1460,7 @@ case object SHeaderMethods extends MonoTypeMethods { lazy val checkPowMethod = SMethod( this, "checkPow", SFunc(Array(SHeader), SBoolean), 16, GroupGenerator.costKind) // todo: cost .withIRInfo(MethodCallIrBuilder) - .withInfo(Xor, "Byte-wise XOR of two collections of bytes") // todo: desc + .withInfo(Xor, "Check PoW of this header") // todo: desc def checkPow_eval(mc: MethodCall, G: SigmaDslBuilder, header: Header) (implicit E: ErgoTreeEvaluator): Boolean = { diff --git a/data/shared/src/main/scala/sigma/data/CHeader.scala b/data/shared/src/main/scala/sigma/data/CHeader.scala new file mode 100644 index 0000000000..ed75fee4be --- /dev/null +++ b/data/shared/src/main/scala/sigma/data/CHeader.scala @@ -0,0 +1,132 @@ +package sigma.data + +import org.ergoplatform.{AutolykosSolution, ErgoHeader, HeaderWithoutPow, HeaderWithoutPowSerializer} +import scorex.crypto.authds.ADDigest +import scorex.crypto.hash.Digest32 +import scorex.util.{bytesToId, idToBytes} +import sigma.pow.Autolykos2PowValidation +import sigma.{AvlTree, BigInt, Coll, Colls, GroupElement, Header} + +/** A default implementation of [[Header]] interface. + * + * @see [[Header]] for detailed descriptions + */ +class CHeader(val ergoHeader: ErgoHeader) extends Header with WrapperOf[ErgoHeader] { + + /** Bytes representation of ModifierId of this Header */ + override lazy val id: Coll[Byte] = ergoHeader.id + + /** Block version, to be increased on every soft and hardfork. */ + override def version: Byte = ergoHeader.version + + /** Bytes representation of ModifierId of the parent block */ + override def parentId: Coll[Byte] = Colls.fromArray(idToBytes(ergoHeader.parentId)) + + /** Hash of ADProofs for transactions in a block */ + override def ADProofsRoot: Coll[Byte] = Colls.fromArray(ergoHeader.ADProofsRoot) + + /** AvlTree of a state after block application */ + override def stateRoot: AvlTree = CAvlTree(AvlTreeData.avlTreeFromDigest(Colls.fromArray(ergoHeader.stateRoot))) + + /** Root hash (for a Merkle tree) of transactions in a block. */ + override def transactionsRoot: Coll[Byte] = Colls.fromArray(ergoHeader.transactionsRoot) + + /** Block timestamp (in milliseconds since beginning of Unix Epoch) */ + override def timestamp: Long = ergoHeader.timestamp + + /** Current difficulty in a compressed view. + * NOTE: actually it is unsigned Int */ + override def nBits: Long = ergoHeader.nBits + + /** Block height */ + override def height: Int = ergoHeader.height + + /** Root hash of extension section */ + override def extensionRoot: Coll[Byte] = Colls.fromArray(ergoHeader.extensionRoot) + + /** Miner public key. Should be used to collect block rewards. + * Part of Autolykos solution. */ + override def minerPk: GroupElement = CGroupElement(ergoHeader.powSolution.pk) + + /** One-time public key. Prevents revealing of miners secret. */ + override def powOnetimePk: GroupElement = CGroupElement(ergoHeader.powSolution.w) + + /** nonce */ + override def powNonce: Coll[Byte] = Colls.fromArray(ergoHeader.powSolution.n) + + /** Distance between pseudo-random number, corresponding to nonce `powNonce` and a secret, + * corresponding to `minerPk`. The lower `powDistance` is, the harder it was to find this solution. */ + override def powDistance: BigInt = CBigInt(ergoHeader.powSolution.d.bigInteger) + + /** Miner votes for changing system parameters. */ + override def votes: Coll[Byte] = Colls.fromArray(ergoHeader.votes) + + override def unparsedBytes: Coll[Byte] = Colls.fromArray(ergoHeader.unparsedBytes) + + /** The data value wrapped by this wrapper. */ + override def wrappedValue: ErgoHeader = ergoHeader + + override def serializeWithoutPoW: Coll[Byte] = { + val headerWithoutPow = HeaderWithoutPow(version, bytesToId(parentId.toArray), Digest32 @@ ADProofsRoot.toArray, + ADDigest @@ stateRoot.digest.toArray, Digest32 @@ transactionsRoot.toArray, timestamp, + nBits, height, Digest32 @@ extensionRoot.toArray, votes.toArray, unparsedBytes.toArray) + Colls.fromArray(HeaderWithoutPowSerializer.toBytes(headerWithoutPow)) + } + + override def checkPow: Boolean = { + if (version == 1) { + throw new Exception("Autolykos v1 is not supported") //todo: more specific exception? + } else { + Autolykos2PowValidation.checkPoWForVersion2(this) + } + } + + override def hashCode(): Int = id.hashCode() + + override def equals(other: Any): Boolean = other match { + case ch: CHeader => ch.id == this.id + case _ => false + } +} + +object CHeader { + + def apply( id: Coll[Byte], // todo: ignored + version: Byte, + parentId: Coll[Byte], + ADProofsRoot: Coll[Byte], + stateRoot: AvlTree, + transactionsRoot: Coll[Byte], + timestamp: Long, + nBits: Long, + height: Int, + extensionRoot: Coll[Byte], + minerPk: GroupElement, + powOnetimePk: GroupElement, + powNonce: Coll[Byte], + powDistance: BigInt, + votes: Coll[Byte], + unparsedBytes: Coll[Byte] + ): CHeader = { + + val solution = AutolykosSolution( + minerPk.asInstanceOf[CGroupElement].wrappedValue, + powOnetimePk.asInstanceOf[CGroupElement].wrappedValue, + powNonce.toArray, + powDistance.asInstanceOf[CBigInt].wrappedValue) + + val h = ErgoHeader(version, bytesToId(parentId.toArray), Digest32 @@ ADProofsRoot.toArray, + ADDigest @@ stateRoot.digest.toArray, Digest32 @@ transactionsRoot.toArray, timestamp, nBits, height, + Digest32 @@ extensionRoot.toArray, solution, votes.toArray, unparsedBytes.toArray, null) + + new CHeader(h) + } + + /** Size of of Header.votes array. */ + val VotesSize: Int = SigmaConstants.VotesArraySize.value + + /** Size of nonce array from Autolykos POW solution in Header.powNonce array. */ + val NonceSize: Int = SigmaConstants.AutolykosPowSolutionNonceArraySize.value + + +} \ No newline at end of file diff --git a/data/shared/src/main/scala/sigma/serialization/DataSerializer.scala b/data/shared/src/main/scala/sigma/serialization/DataSerializer.scala index 5f554e96a1..92a54f9aa4 100644 --- a/data/shared/src/main/scala/sigma/serialization/DataSerializer.scala +++ b/data/shared/src/main/scala/sigma/serialization/DataSerializer.scala @@ -1,8 +1,9 @@ package sigma.serialization -import org.ergoplatform.ErgoBox +import org.ergoplatform.{ErgoBox, ErgoHeader} +import sigma.VersionContext import sigma.ast._ -import sigma.data.CBox +import sigma.data.{CBox, CHeader} /** This works in tandem with ConstantSerializer, if you change one make sure to check the other.*/ object DataSerializer extends CoreDataSerializer { @@ -15,6 +16,9 @@ object DataSerializer extends CoreDataSerializer { case SBox => val b = v.asInstanceOf[CBox] ErgoBox.sigmaSerializer.serialize(b.ebox, w.asInstanceOf[SigmaByteWriter]) + case SHeader if VersionContext.current.isV6SoftForkActivated => + val h = v.asInstanceOf[CHeader] + ErgoHeader.sigmaSerializer.serialize(h.ergoHeader, w.asInstanceOf[SigmaByteWriter]) case _ => super.serialize(v, tpe, w) } @@ -32,6 +36,12 @@ object DataSerializer extends CoreDataSerializer { val res = CBox(ErgoBox.sigmaSerializer.parse(r.asInstanceOf[SigmaByteReader])) r.level = r.level - 1 res + case SHeader if VersionContext.current.isV6SoftForkActivated => + val depth = r.level + r.level = depth + 1 + val res = new CHeader(ErgoHeader.sigmaSerializer.parse(r.asInstanceOf[SigmaByteReader])) + r.level = r.level - 1 + res case t => super.deserialize(t, r) }).asInstanceOf[T#WrappedType] diff --git a/interpreter/shared/src/main/scala/sigmastate/eval/CHeader.scala b/interpreter/shared/src/main/scala/sigmastate/eval/CHeader.scala deleted file mode 100644 index c23118100b..0000000000 --- a/interpreter/shared/src/main/scala/sigmastate/eval/CHeader.scala +++ /dev/null @@ -1,57 +0,0 @@ -package sigmastate.eval - -import org.ergoplatform.{HeaderWithoutPow, HeaderWithoutPowSerializer} -import scorex.crypto.authds.ADDigest -import scorex.crypto.hash.Digest32 -import scorex.util.bytesToId -import sigma.data.SigmaConstants -import sigma.pow.Autolykos2PowValidation -import sigma.{AvlTree, BigInt, Coll, Colls, GroupElement, Header} - -/** A default implementation of [[Header]] interface. - * - * @see [[Header]] for detailed descriptions - */ -case class CHeader( - id: Coll[Byte], - version: Byte, - parentId: Coll[Byte], - ADProofsRoot: Coll[Byte], - stateRoot: AvlTree, - transactionsRoot: Coll[Byte], - timestamp: Long, - nBits: Long, - height: Int, - extensionRoot: Coll[Byte], - minerPk: GroupElement, - powOnetimePk: GroupElement, - powNonce: Coll[Byte], - powDistance: BigInt, - votes: Coll[Byte], - unparsedBytes: Coll[Byte] -) extends Header { - - override def serializeWithoutPoW: Coll[Byte] = { - val headerWithoutPow = HeaderWithoutPow(version, bytesToId(parentId.toArray), Digest32 @@ ADProofsRoot.toArray, - ADDigest @@ stateRoot.digest.toArray, Digest32 @@ transactionsRoot.toArray, timestamp, - nBits, height, Digest32 @@ extensionRoot.toArray, votes.toArray, unparsedBytes.toArray) - Colls.fromArray(HeaderWithoutPowSerializer.toBytes(headerWithoutPow)) - } - - override def checkPow: Boolean = { - if (version == 1) { - throw new Exception("Autolykos v1 is not supported") //todo: more specific exception? - } else { - Autolykos2PowValidation.checkPoWForVersion2(this) - } - } - -} - -object CHeader { - /** Size of of Header.votes array. */ - val VotesSize: Int = SigmaConstants.VotesArraySize.value - - /** Size of nonce array from Autolykos POW solution in Header.powNonce array. */ - val NonceSize: Int = SigmaConstants.AutolykosPowSolutionNonceArraySize.value -} \ No newline at end of file diff --git a/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala index 7cd9967e54..6009d215d8 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala @@ -6,7 +6,7 @@ import org.scalacheck.Arbitrary._ import sigma.data.{DataValueComparer, RType, SigmaBoolean, TupleColl} import sigma.ast.SCollection.SByteArray import sigmastate.eval._ -import sigma.{AvlTree, Colls, Evaluation} +import sigma.{AvlTree, Colls, Evaluation, Header, VersionContext} import sigma.ast.SType.AnyOps import sigma.ast._ import org.scalacheck.Gen @@ -14,7 +14,7 @@ import sigma.Extensions.ArrayOps import sigma.crypto.EcPointType import sigma.eval.SigmaDsl import sigma.util.Extensions.{BigIntegerOps, EcpOps, SigmaBooleanOps} -import sigmastate.interpreter.{CostAccumulator, CErgoTreeEvaluator} +import sigmastate.interpreter.{CErgoTreeEvaluator, CostAccumulator} import sigmastate.interpreter.CErgoTreeEvaluator.DefaultProfiler import sigmastate.utils.Helpers @@ -132,6 +132,20 @@ class DataSerializerSpecification extends SerializationSpecification { t.isInstanceOf[SerializerException] && t.getMessage.contains(s"BigInt value doesn't not fit into ${SBigInt.MaxSizeInBytes} bytes") }) + } + + property("header roundtrip") { + VersionContext.withVersions(VersionContext.V6SoftForkVersion, 1) { + forAll { x: Header => roundtrip[SHeader.type](x, SHeader) } + } + an[SerializerException] should be thrownBy ( + VersionContext.withVersions((VersionContext.V6SoftForkVersion - 1).toByte, 1) { + val h = headerGen.sample.get + val res = roundtrip[SHeader.type](h, SHeader) + println("r: " + res) + res + }) } + } diff --git a/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala index c65b86930c..2332aaccaa 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala @@ -4,6 +4,9 @@ import sigma.VersionContext import sigma.ast._ import sigma.validation.ValidationException +import scala.util.Try + + class MethodCallSerializerSpecification extends SerializationSpecification { property("MethodCall deserialization round trip") { @@ -48,8 +51,8 @@ class MethodCallSerializerSpecification extends SerializationSpecification { property("MethodCall deserialization round trip for Header.checkPow") { def code = { - val bi = HeaderConstant(headerGen.sample.get) - val expr = MethodCall(bi, + val h = HeaderConstant(headerGen.sample.get) + val expr = MethodCall(h, SHeaderMethods.checkPowMethod, Vector(), Map() @@ -61,11 +64,12 @@ class MethodCallSerializerSpecification extends SerializationSpecification { code } - an[ValidationException] should be thrownBy ( + // sigma.serialization.SerializerException: Don't know how to serialize (sigma.data.CHeader@51dbec76, SHeader) + an[SerializerException] should be thrownBy ( VersionContext.withVersions((VersionContext.V6SoftForkVersion - 1).toByte, 1) { code } - ) + ) } } diff --git a/interpreter/shared/src/test/scala/sigma/serialization/SerializationSpecification.scala b/interpreter/shared/src/test/scala/sigma/serialization/SerializationSpecification.scala index 30ae6af19b..36c75f3224 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/SerializationSpecification.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/SerializationSpecification.scala @@ -41,7 +41,7 @@ trait SerializationSpecification extends AnyPropSpec r.positionLimit shouldBe positionLimitBefore } - //check that pos and consumed are being implented correctly + //check that pos and consumed are being implemented correctly protected def predefinedBytesTestNotFomZeroElement[V <: Value[_ <: SType]](bytes: Array[Byte], v: V): Assertion = { val randomInt = Gen.chooseNum(1, 20).sample.get val randomBytes = Gen.listOfN(randomInt, arbByte.arbitrary).sample.get.toArray diff --git a/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala b/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala index 6b8c833931..d1253d4686 100644 --- a/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala +++ b/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala @@ -12,7 +12,7 @@ import scorex.crypto.hash.{Blake2b256, Digest32} import scorex.util.ModifierId import sigma.ast._ import sigma.Extensions.ArrayOps -import sigmastate.eval.{CHeader, CPreHeader} +import sigmastate.eval.CPreHeader import sigmastate.helpers.TestingCommons import sigma.serialization.ErgoTreeSerializer import sigma.serialization.generators.ObjectGenerators @@ -268,7 +268,7 @@ trait SigmaTestingData extends TestingCommons with ObjectGenerators { val h1: Header = create_h1() - val h2: Header = create_h1().asInstanceOf[CHeader].copy(height = 2) + val h2: Header = new CHeader(h1.asInstanceOf[CHeader].wrappedValue.copy(height = 2)) val dlog_instances = new CloneSet(1000, ProveDlog( SigmaDsl.toECPoint(create_ge1()).asInstanceOf[EcPointType] diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala index 340765d313..5cf7889894 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala @@ -12,12 +12,12 @@ import sigma.Extensions.CollBytesOps import sigma.ast.syntax.GroupElementConstant import sigma.ast.{Constant, GroupElementConstant, SType} import sigma.data.Iso.{isoStringToArray, isoStringToColl} -import sigma.data.{CBigInt, CGroupElement, Digest32Coll, Digest32CollRType, Iso} +import sigma.data.{CBigInt, CGroupElement, CHeader, Digest32Coll, Digest32CollRType, Iso} import sigma.interpreter.{ContextExtension, ProverResult} import sigma.js.{AvlTree, GroupElement} import sigma.serialization.{ErgoTreeSerializer, ValueSerializer} import sigma.{Coll, Colls} -import sigmastate.eval.{CHeader, CPreHeader} +import sigmastate.eval.CPreHeader import sigmastate.fleetSdkCommon.distEsmTypesBoxesMod.Box import sigmastate.fleetSdkCommon.distEsmTypesCommonMod.HexString import sigmastate.fleetSdkCommon.distEsmTypesRegistersMod.NonMandatoryRegisters diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala index c2fc1c0c8c..f3ece3a894 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala @@ -12,7 +12,7 @@ import scorex.crypto.hash.Digest32 import scorex.util.ModifierId import sigma.Extensions.ArrayOps import sigma.ast.{ErgoTree, EvaluatedValue, SType} -import sigma.data.{AvlTreeData, AvlTreeFlags, CBigInt, Digest32Coll, WrapperOf} +import sigma.data.{AvlTreeData, AvlTreeFlags, CBigInt, CHeader, Digest32Coll, WrapperOf} import sigma.eval.Extensions.EvalIterableOps import sigma.eval.SigmaDsl import sigma.interpreter.{ContextExtension, ProverResult} @@ -20,7 +20,7 @@ import sigma.serialization.{ErgoTreeSerializer, ValueSerializer} import sigma.validation.SigmaValidationSettings import sigma.{AnyValue, Coll, Colls, Header, PreHeader, SigmaException} import sigmastate.eval.{CPreHeader, _} -import sigmastate.utils.Helpers._ // required for Scala 2.11 +import sigmastate.utils.Helpers._ import java.math.BigInteger import scala.collection.mutable From 7795e5da4a6a355f8b1410b728b0d5462e9dad7e Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 4 Jun 2024 12:18:14 +0300 Subject: [PATCH 03/10] remove non-PR related code, improved DataSerializerSpecification --- .../src/main/scala/sigma/SigmaDsl.scala | 9 +- .../sigma/reflection/ReflectionData.scala | 3 - .../main/scala/sigma/util/NBitsUtils.scala | 84 --------- .../scala/org/ergoplatform/ErgoHeader.scala | 30 ++-- .../org/ergoplatform/HeaderWithoutPow.scala | 2 - .../src/main/scala/sigma/ast/methods.scala | 30 +--- .../src/main/scala/sigma/data/CHeader.scala | 20 +-- .../scala/sigma/eval/ErgoTreeEvaluator.scala | 5 +- .../sigma/pow/Autolykos2PowValidation.scala | 167 ------------------ .../interpreter/CErgoTreeEvaluator.scala | 13 +- .../DataSerializerSpecification.scala | 61 ++++--- .../MethodCallSerializerSpecification.scala | 27 --- .../generators/ObjectGenerators.scala | 3 +- .../special/sigma/SigmaTestingData.scala | 1 - .../sigma/compiler/ir/GraphBuilding.scala | 1 - .../ir/wrappers/sigma/SigmaDslUnit.scala | 1 - .../ir/wrappers/sigma/impl/SigmaDslImpl.scala | 14 -- .../scala/sigma/SigmaDslSpecification.scala | 1 - .../scala/org/ergoplatform/sdk/js/Isos.scala | 7 +- .../org/ergoplatform/sdk/JsonCodecs.scala | 5 +- 20 files changed, 74 insertions(+), 410 deletions(-) delete mode 100644 core/shared/src/main/scala/sigma/util/NBitsUtils.scala delete mode 100644 data/shared/src/main/scala/sigma/pow/Autolykos2PowValidation.scala diff --git a/core/shared/src/main/scala/sigma/SigmaDsl.scala b/core/shared/src/main/scala/sigma/SigmaDsl.scala index a2894515a2..7353b8f89e 100644 --- a/core/shared/src/main/scala/sigma/SigmaDsl.scala +++ b/core/shared/src/main/scala/sigma/SigmaDsl.scala @@ -460,12 +460,17 @@ trait Header { /** Miner votes for changing system parameters. */ def votes: Coll[Byte] //3 bytes + /** Bytes which are coming from future versions of the protocol, so + * their meaning is not known to current version of Sigma, but they + * are stored to get the same id as future version users. + */ def unparsedBytes: Coll[Byte] + /** + * @return bytes without proof of work, needed for working to get the proof on + */ def serializeWithoutPoW: Coll[Byte] - def checkPow: Boolean - } /** Runtime representation of Context ErgoTree type. diff --git a/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala b/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala index 2a5e74e659..028e68bf72 100644 --- a/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala +++ b/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala @@ -346,9 +346,6 @@ object ReflectionData { }, mkMethod(clazz, "powDistance", Array[Class[_]]()) { (obj, _) => obj.asInstanceOf[Header].powDistance - }, - mkMethod(clazz, "checkPow", Array[Class[_]]()) { (obj, _) => - obj.asInstanceOf[Header].checkPow } ) ) diff --git a/core/shared/src/main/scala/sigma/util/NBitsUtils.scala b/core/shared/src/main/scala/sigma/util/NBitsUtils.scala deleted file mode 100644 index 36d526d1d5..0000000000 --- a/core/shared/src/main/scala/sigma/util/NBitsUtils.scala +++ /dev/null @@ -1,84 +0,0 @@ -package sigma.util - -import java.math.BigInteger - -object NBitsUtils { - - /** - *

The "compact" format is a representation of a whole number N using an unsigned 32 bit number similar to a - * floating point format. The most significant 8 bits are the unsigned exponent of base 256. This exponent can - * be thought of as "number of bytes of N". The lower 23 bits are the mantissa. Bit number 24 (0x800000) represents - * the sign of N. Therefore, N = (-1^sign) * mantissa * 256^(exponent-3).

- * - *

Satoshi's original implementation used BN_bn2mpi() and BN_mpi2bn(). MPI uses the most significant bit of the - * first byte as sign. Thus 0x1234560000 is compact 0x05123456 and 0xc0de000000 is compact 0x0600c0de. Compact - * 0x05c0de00 would be -0x40de000000.

- * - *

Bitcoin only uses this "compact" format for encoding difficulty targets, which are unsigned 256bit quantities. - * Thus, all the complexities of the sign bit and using base 256 are probably an implementation accident.

- */ - def decodeCompactBits(compact: Long): BigInt = { - val size: Int = (compact >> 24).toInt & 0xFF - val bytes: Array[Byte] = new Array[Byte](4 + size) - bytes(3) = size.toByte - if (size >= 1) bytes(4) = ((compact >> 16) & 0xFF).toByte - if (size >= 2) bytes(5) = ((compact >> 8) & 0xFF).toByte - if (size >= 3) bytes(6) = (compact & 0xFF).toByte - decodeMPI(bytes) - } - - /** - * @see Utils#decodeCompactBits(long) - */ - def encodeCompactBits(requiredDifficulty: BigInt): Long = { - val value = requiredDifficulty.bigInteger - var result: Long = 0L - var size: Int = value.toByteArray.length - if (size <= 3) { - result = value.longValue << 8 * (3 - size) - } else { - result = value.shiftRight(8 * (size - 3)).longValue - } - // The 0x00800000 bit denotes the sign. - // Thus, if it is already set, divide the mantissa by 256 and increase the exponent. - if ((result & 0x00800000L) != 0) { - result >>= 8 - size += 1 - } - result |= size << 24 - val a: Int = if (value.signum == -1) 0x00800000 else 0 - result |= a - result - } - - - /** Parse 4 bytes from the byte array (starting at the offset) as unsigned 32-bit integer in big endian format. */ - def readUint32BE(bytes: Array[Byte]): Long = ((bytes(0) & 0xffL) << 24) | ((bytes(1) & 0xffL) << 16) | ((bytes(2) & 0xffL) << 8) | (bytes(3) & 0xffL) - - /** - * MPI encoded numbers are produced by the OpenSSL BN_bn2mpi function. They consist of - * a 4 byte big endian length field, followed by the stated number of bytes representing - * the number in big endian format (with a sign bit). - * - */ - private def decodeMPI(mpi: Array[Byte]): BigInteger = { - - val length: Int = readUint32BE(mpi).toInt - val buf = new Array[Byte](length) - System.arraycopy(mpi, 4, buf, 0, length) - - if (buf.length == 0) { - BigInteger.ZERO - } else { - val isNegative: Boolean = (buf(0) & 0x80) == 0x80 - if (isNegative) buf(0) = (buf(0) & 0x7f).toByte - val result: BigInteger = new BigInteger(buf) - if (isNegative) { - result.negate - } else { - result - } - } - } - -} diff --git a/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala b/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala index cc678003d6..dc076c4173 100644 --- a/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala +++ b/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala @@ -5,7 +5,7 @@ import scorex.crypto.authds.ADDigest import scorex.crypto.hash.{Blake2b256, Digest32} import scorex.util.ModifierId import sigma.Colls -import sigma.crypto.{CryptoConstants, CryptoFacade, EcPointType} +import sigma.crypto.{CryptoConstants, EcPointType} import sigma.serialization.{GroupElementSerializer, SigmaByteReader, SigmaByteWriter, SigmaSerializer} @@ -91,22 +91,22 @@ object AutolykosSolution { * @param _bytes - serialized bytes of the header when not `null` */ case class ErgoHeader(override val version: ErgoHeader.Version, - override val parentId: ModifierId, - override val ADProofsRoot: Digest32, - override val stateRoot: ADDigest, //33 bytes! extra byte with tree height here! - override val transactionsRoot: Digest32, - override val timestamp: ErgoHeader.Timestamp, - override val nBits: Long, //actually it is unsigned int - override val height: Int, - override val extensionRoot: Digest32, - powSolution: AutolykosSolution, - override val votes: Array[Byte], //3 bytes - override val unparsedBytes: Array[Byte], - _bytes: Array[Byte]) extends + override val parentId: ModifierId, + override val ADProofsRoot: Digest32, + override val stateRoot: ADDigest, //33 bytes! extra byte with tree height here! + override val transactionsRoot: Digest32, + override val timestamp: ErgoHeader.Timestamp, + override val nBits: Long, //actually it is unsigned int + override val height: Int, + override val extensionRoot: Digest32, + powSolution: AutolykosSolution, + override val votes: Array[Byte], //3 bytes + override val unparsedBytes: Array[Byte], + _bytes: Array[Byte]) extends HeaderWithoutPow(version, parentId, ADProofsRoot, stateRoot, transactionsRoot, timestamp, - nBits, height, extensionRoot, votes, unparsedBytes) { + nBits, height, extensionRoot, votes, unparsedBytes) { - lazy val bytes = if(_bytes != null) { + lazy val bytes = if (_bytes != null) { _bytes } else { ErgoHeader.sigmaSerializer.toBytes(this) diff --git a/data/shared/src/main/scala/org/ergoplatform/HeaderWithoutPow.scala b/data/shared/src/main/scala/org/ergoplatform/HeaderWithoutPow.scala index 56e2eafb1b..4eba9b708e 100644 --- a/data/shared/src/main/scala/org/ergoplatform/HeaderWithoutPow.scala +++ b/data/shared/src/main/scala/org/ergoplatform/HeaderWithoutPow.scala @@ -6,8 +6,6 @@ import scorex.util.{ModifierId, bytesToId, idToBytes} import sigma.serialization.{SigmaByteReader, SigmaByteWriter, SigmaSerializer} import scorex.util.Extensions._ -//todo: unify with Ergo node codebase - /** * Header without proof-of-work puzzle solution, see Header class description for details. */ diff --git a/data/shared/src/main/scala/sigma/ast/methods.scala b/data/shared/src/main/scala/sigma/ast/methods.scala index 97c2302b2d..e4cf0007e0 100644 --- a/data/shared/src/main/scala/sigma/ast/methods.scala +++ b/data/shared/src/main/scala/sigma/ast/methods.scala @@ -10,7 +10,6 @@ import sigma.ast.syntax.{SValue, ValueOps} import sigma.data.OverloadHack.Overloaded1 import sigma.data.{DataValueComparer, KeyValueColl, Nullable, RType, SigmaConstants} import sigma.eval.{CostDetails, ErgoTreeEvaluator, TracedCost} -import sigma.pow.Autolykos2PowValidation import sigma.reflection.RClass import sigma.serialization.CoreByteWriter.ArgInfo import sigma.utils.SparseArrayContainer @@ -1457,30 +1456,11 @@ case object SHeaderMethods extends MonoTypeMethods { lazy val powDistanceMethod = propertyCall("powDistance", SBigInt, 14, FixedCost(JitCost(10))) lazy val votesMethod = propertyCall("votes", SByteArray, 15, FixedCost(JitCost(10))) - lazy val checkPowMethod = SMethod( - this, "checkPow", SFunc(Array(SHeader), SBoolean), 16, GroupGenerator.costKind) // todo: cost - .withIRInfo(MethodCallIrBuilder) - .withInfo(Xor, "Check PoW of this header") // todo: desc - - def checkPow_eval(mc: MethodCall, G: SigmaDslBuilder, header: Header) - (implicit E: ErgoTreeEvaluator): Boolean = { - E.checkPow_eval(mc, header) - } - - protected override def getMethods() = { - if (VersionContext.current.isV6SoftForkActivated) { - // 6.0 : checkPow method added - super.getMethods() ++ Seq( - idMethod, versionMethod, parentIdMethod, ADProofsRootMethod, stateRootMethod, transactionsRootMethod, - timestampMethod, nBitsMethod, heightMethod, extensionRootMethod, minerPkMethod, powOnetimePkMethod, - powNonceMethod, powDistanceMethod, votesMethod, checkPowMethod) - } else { - super.getMethods() ++ Seq( - idMethod, versionMethod, parentIdMethod, ADProofsRootMethod, stateRootMethod, transactionsRootMethod, - timestampMethod, nBitsMethod, heightMethod, extensionRootMethod, minerPkMethod, powOnetimePkMethod, - powNonceMethod, powDistanceMethod, votesMethod) - } - } + protected override def getMethods() = super.getMethods() ++ Seq( + idMethod, versionMethod, parentIdMethod, ADProofsRootMethod, stateRootMethod, transactionsRootMethod, + timestampMethod, nBitsMethod, heightMethod, extensionRootMethod, minerPkMethod, powOnetimePkMethod, + powNonceMethod, powDistanceMethod, votesMethod + ) } /** Type descriptor of `PreHeader` type of ErgoTree. */ diff --git a/data/shared/src/main/scala/sigma/data/CHeader.scala b/data/shared/src/main/scala/sigma/data/CHeader.scala index 96fffb0822..d17fb5671f 100644 --- a/data/shared/src/main/scala/sigma/data/CHeader.scala +++ b/data/shared/src/main/scala/sigma/data/CHeader.scala @@ -4,7 +4,6 @@ import org.ergoplatform.{AutolykosSolution, ErgoHeader, HeaderWithoutPow, Header import scorex.crypto.authds.ADDigest import scorex.crypto.hash.Digest32 import scorex.util.{bytesToId, idToBytes} -import sigma.pow.Autolykos2PowValidation import sigma.{AvlTree, BigInt, Coll, Colls, GroupElement, Header} /** A default implementation of [[Header]] interface. @@ -73,26 +72,19 @@ class CHeader(val ergoHeader: ErgoHeader) extends Header with WrapperOf[ErgoHead Colls.fromArray(HeaderWithoutPowSerializer.toBytes(headerWithoutPow)) } - override def checkPow: Boolean = { - if (version == 1) { - throw new Exception("Autolykos v1 is not supported") //todo: more specific exception? - } else { - Autolykos2PowValidation.checkPoWForVersion2(this) - } - } - override def hashCode(): Int = id.hashCode() override def equals(other: Any): Boolean = other match { case ch: CHeader => ch.id == this.id case _ => false } + + def copy(): CHeader = new CHeader(ergoHeader.copy()) // used in tests only } object CHeader { - def apply( id: Coll[Byte], // todo: ignored, remove - version: Byte, + def apply( version: Byte, parentId: Coll[Byte], ADProofsRoot: Coll[Byte], stateRoot: AvlTree, @@ -106,8 +98,7 @@ object CHeader { powNonce: Coll[Byte], powDistance: BigInt, votes: Coll[Byte], - unparsedBytes: Coll[Byte] - ): CHeader = { + unparsedBytes: Coll[Byte]): CHeader = { val solution = AutolykosSolution( minerPk.asInstanceOf[CGroupElement].wrappedValue, @@ -128,5 +119,4 @@ object CHeader { /** Size of nonce array from Autolykos POW solution in Header.powNonce array. */ val NonceSize: Int = SigmaConstants.AutolykosPowSolutionNonceArraySize.value - -} \ No newline at end of file +} diff --git a/data/shared/src/main/scala/sigma/eval/ErgoTreeEvaluator.scala b/data/shared/src/main/scala/sigma/eval/ErgoTreeEvaluator.scala index 610be08c9c..52f839354c 100644 --- a/data/shared/src/main/scala/sigma/eval/ErgoTreeEvaluator.scala +++ b/data/shared/src/main/scala/sigma/eval/ErgoTreeEvaluator.scala @@ -1,6 +1,6 @@ package sigma.eval -import sigma.{AvlTree, Coll, Context, Header} +import sigma.{AvlTree, Coll, Context} import sigma.ast.{Constant, FixedCost, MethodCall, OperationCostInfo, OperationDesc, PerItemCost, SType, TypeBasedCost} import sigma.data.KeyValueColl @@ -138,9 +138,6 @@ abstract class ErgoTreeEvaluator { def remove_eval( mc: MethodCall, tree: AvlTree, operations: Coll[Coll[Byte]], proof: Coll[Byte]): Option[AvlTree] - - def checkPow_eval(mc: MethodCall, header: Header): Boolean - } object ErgoTreeEvaluator { diff --git a/data/shared/src/main/scala/sigma/pow/Autolykos2PowValidation.scala b/data/shared/src/main/scala/sigma/pow/Autolykos2PowValidation.scala deleted file mode 100644 index a27dc864d1..0000000000 --- a/data/shared/src/main/scala/sigma/pow/Autolykos2PowValidation.scala +++ /dev/null @@ -1,167 +0,0 @@ -package sigma.pow - - -import org.bouncycastle.util.BigIntegers -import scorex.crypto.hash.Blake2b256 -import scorex.utils.{Bytes, Ints, Longs} -import sigma.Header -import sigma.crypto.{BcDlogGroup, CryptoConstants} -import sigma.util.NBitsUtils - -object Autolykos2PowValidation { - - type Height = Int - - val k = 32 - - val NStart = 26 - - val group: BcDlogGroup = CryptoConstants.dlogGroup - - // Group order, used in Autolykos V.1 for non-outsourceability, - // and also to obtain target in both Autolykos v1 and v2 - val q: BigInt = group.order - - /** - * Number of elements in a table to find k-sum problem solution on top of - */ - val NBase: Int = Math.pow(2, NStart.toDouble).toInt - - /** - * Initial height since which table (`N` value) starting to increase by 5% per `IncreasePeriodForN` blocks - */ - val IncreaseStart: Height = 600 * 1024 - - /** - * Table size (`N`) increased every 50 * 1024 blocks - */ - val IncreasePeriodForN: Height = 50 * 1024 - - /** - * On this height, the table (`N` value) will stop to grow. - * Max N on and after this height would be 2,143,944,600 which is still less than 2^^31. - */ - val NIncreasementHeightMax: Height = 4198400 - - /** - * Blake2b256 hash function invocation - * @param in - input bit-string - * @return - 256 bits (32 bytes) array - */ - def hash(in: Array[Byte]): Array[Byte] = Blake2b256.hash(in) - - /** - * Convert byte array to unsigned integer - * @param in - byte array - * @return - unsigned integer - */ - def toBigInt(in: Array[Byte]): BigInt = BigInt(BigIntegers.fromUnsignedByteArray(in)) - - /** - * Constant data to be added to hash function to increase its calculation time - */ - val M: Array[Byte] = (0 until 1024).toArray.flatMap(i => Longs.toByteArray(i.toLong)) - - /** - * Calculates table size (N value) for a given height (moment of time) - * - * @see papers/yellow/pow/ErgoPow.tex for full description and test vectors - * @param headerHeight - height of a header to mine - * @return - N value - */ - def calcN(headerHeight: Height): Int = { - val height = Math.min(NIncreasementHeightMax, headerHeight) - if (height < IncreaseStart) { - NBase - } else { - val itersNumber = (height - IncreaseStart) / IncreasePeriodForN + 1 - (1 to itersNumber).foldLeft(NBase) { case (step, _) => - step / 100 * 105 - } - } - } - - def calcN(header: Header): Int = calcN(header.height) - - /** - * Hash function that takes `m` and `nonceBytes` and returns a list of size `k` with numbers in - * [0,`N`) - */ - private def genIndexes(k: Int, seed: Array[Byte], N: Int): Seq[Int] = { - val hash = Blake2b256(seed) - val extendedHash = Bytes.concat(hash, hash.take(3)) - (0 until k).map { i => - BigInt(1, extendedHash.slice(i, i + 4)).mod(N).toInt - } - }.ensuring(_.length == k) - - /** - * Generate element of Autolykos equation. - */ - private def genElementV2(indexBytes: Array[Byte], heightBytes: => Array[Byte]): BigInt = { - // Autolykos v. 2: H(j|h|M) (line 5 from the Algo 2 of the spec) - toBigInt(hash(Bytes.concat(indexBytes, heightBytes, M)).drop(1)) - } - - def hitForVersion2ForMessage(k: Int, msg: Array[Byte], nonce: Array[Byte], h: Array[Byte], N: Int): BigInt = { - - val prei8 = BigIntegers.fromUnsignedByteArray(hash(Bytes.concat(msg, nonce)).takeRight(8)) - val i = BigIntegers.asUnsignedByteArray(4, prei8.mod(BigInt(N).underlying())) - val f = Blake2b256(Bytes.concat(i, h, M)).drop(1) // .drop(1) is the same as takeRight(31) - val seed = Bytes.concat(f, msg, nonce) // Autolykos v1, Alg. 2, line4: - - val indexes = genIndexes(k, seed, N) - //pk and w not used in v2 - val elems = indexes.map(idx => genElementV2(Ints.toByteArray(idx), h)) - val f2 = elems.sum - - // sum as byte array is always about 32 bytes - val array: Array[Byte] = BigIntegers.asUnsignedByteArray(32, f2.underlying()) - val ha = hash(array) - toBigInt(ha) - } - - /** - * Header digest ("message" for default GPU miners) a miner is working on - */ - def msgByHeader(h: Header): Array[Byte] = Blake2b256(h.serializeWithoutPoW.toArray) - - /** - * Get hit for Autolykos v2 header (to test it then against PoW target) - * - * @param header - header to check PoW for - * @return PoW hit - */ - def hitForVersion2(header: Header): BigInt = { - - val msg = msgByHeader(header) - val nonce = header.powNonce - - val h = Ints.toByteArray(header.height) // used in AL v.2 only - - val N = calcN(header) - - hitForVersion2ForMessage(k, msg, nonce.toArray, h, N) - } - - /** - * Get target `b` from encoded difficulty `nBits` - */ - def getB(nBits: Long): BigInt = { - q / NBitsUtils.decodeCompactBits(nBits) - } - - /** - * Check PoW for Autolykos v2 header - * - * @param header - header to check PoW for - * @return whether PoW is valid or not - */ - def checkPoWForVersion2(header: Header): Boolean = { - val b = getB(header.nBits) - // for version 2, we're calculating hit and compare it with target - val hit = hitForVersion2(header) - hit < b - } - -} diff --git a/interpreter/shared/src/main/scala/sigmastate/interpreter/CErgoTreeEvaluator.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/CErgoTreeEvaluator.scala index 59c9af09ef..2f7d527e74 100644 --- a/interpreter/shared/src/main/scala/sigmastate/interpreter/CErgoTreeEvaluator.scala +++ b/interpreter/shared/src/main/scala/sigmastate/interpreter/CErgoTreeEvaluator.scala @@ -14,8 +14,6 @@ import sigma.ast.SType import sigma.data.{CSigmaProp, KeyValueColl, SigmaBoolean} import sigma.eval.{AvlTreeVerifier, ErgoTreeEvaluator, EvalSettings, Profiler} import sigma.eval.ErgoTreeEvaluator.DataEnv -import sigma.pow.Autolykos2PowValidation -import sigmastate.interpreter.CErgoTreeEvaluator.fixedCostOp import scala.collection.compat.immutable.ArraySeq import scala.util.{DynamicVariable, Failure, Success} @@ -218,15 +216,6 @@ class CErgoTreeEvaluator( } } - override def checkPow_eval(mc: MethodCall, header: Header): Boolean = { - VersionContext.checkVersions(context.activatedScriptVersion, context.currentErgoTreeVersion) - // todo: consider cost - val checkPowCostInfo = OperationCostInfo(FixedCost(JitCost(10)), NamedDesc("Header.checkPow")) - fixedCostOp(checkPowCostInfo){ - header.checkPow - }(this) - } - /** Evaluates the given expression in the given data environment. */ def eval(env: DataEnv, exp: SValue): Any = { VersionContext.checkVersions(context.activatedScriptVersion, context.currentErgoTreeVersion) @@ -460,7 +449,7 @@ object CErgoTreeEvaluator { * HOTSPOT: don't beautify the code * Note, `null` is used instead of Option to avoid allocations. */ - def fixedCostOp[R](costInfo: OperationCostInfo[FixedCost]) + def fixedCostOp[R <: AnyRef](costInfo: OperationCostInfo[FixedCost]) (block: => R)(implicit E: ErgoTreeEvaluator): R = { if (E != null) { var res: R = null.asInstanceOf[R] diff --git a/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala index 34ada33ba3..fecd077287 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala @@ -7,7 +7,6 @@ import sigma.data.{DataValueComparer, OptionType, RType, SigmaBoolean, TupleColl import sigma.ast.SCollection.SByteArray import sigmastate.eval._ import sigma.{AvlTree, Colls, Evaluation, Header, VersionContext} -import sigma.{AvlTree, Colls, Evaluation, VersionContext} import sigma.ast.SType.AnyOps import sigma.ast._ import org.scalacheck.Gen @@ -24,29 +23,40 @@ import scala.reflect.ClassTag class DataSerializerSpecification extends SerializationSpecification { - def roundtrip[T <: SType](obj: T#WrappedType, tpe: T) = { - val w = SigmaSerializer.startWriter() - DataSerializer.serialize(obj, tpe, w) - val bytes = w.toBytes - val r = SigmaSerializer.startReader(bytes) - val res = DataSerializer.deserialize(tpe, r) - res shouldBe obj - - val es = CErgoTreeEvaluator.DefaultEvalSettings - val accumulator = new CostAccumulator( - initialCost = JitCost(0), - costLimit = Some(JitCost.fromBlockCost(es.scriptCostLimitInEvaluator))) - val evaluator = new CErgoTreeEvaluator( - context = null, - constants = ErgoTree.EmptyConstants, - coster = accumulator, DefaultProfiler, es) - val ok = DataValueComparer.equalDataValues(res, obj)(evaluator) - ok shouldBe true - - val randomPrefix = arrayGen[Byte].sample.get - val r2 = SigmaSerializer.startReader(randomPrefix ++ bytes, randomPrefix.length) - val res2 = DataSerializer.deserialize(tpe, r2) - res2 shouldBe obj + def roundtrip[T <: SType](obj: T#WrappedType, tpe: T, withVersion: Option[Byte] = None) = { + + def test() = { + val w = SigmaSerializer.startWriter() + DataSerializer.serialize(obj, tpe, w) + val bytes = w.toBytes + val r = SigmaSerializer.startReader(bytes) + val res = DataSerializer.deserialize(tpe, r) + res shouldBe obj + + val es = CErgoTreeEvaluator.DefaultEvalSettings + val accumulator = new CostAccumulator( + initialCost = JitCost(0), + costLimit = Some(JitCost.fromBlockCost(es.scriptCostLimitInEvaluator))) + val evaluator = new CErgoTreeEvaluator( + context = null, + constants = ErgoTree.EmptyConstants, + coster = accumulator, DefaultProfiler, es) + val ok = DataValueComparer.equalDataValues(res, obj)(evaluator) + ok shouldBe true + + val randomPrefix = arrayGen[Byte].sample.get + val r2 = SigmaSerializer.startReader(randomPrefix ++ bytes, randomPrefix.length) + val res2 = DataSerializer.deserialize(tpe, r2) + res2 shouldBe obj + } + + withVersion match { + case Some(ver) => + VersionContext.withVersions(ver, 1) { + test() + } + case None => test() + } } def testCollection[T <: SType](tpe: T) = { @@ -130,6 +140,7 @@ class DataSerializerSpecification extends SerializationSpecification { forAll { x: ErgoBox => roundtrip[SBox.type](x, SBox) } forAll { x: AvlTree => roundtrip[SAvlTree.type](x, SAvlTree) } forAll { x: Array[Byte] => roundtrip[SByteArray](x.toColl, SByteArray) } + forAll { x: Header => roundtrip[SHeader.type](x, SHeader, Some(VersionContext.V6SoftForkVersion)) } forAll { t: SPredefType => testCollection(t) } forAll { t: SPredefType => testTuples(t) } forAll { t: SPredefType => testOption(t) } @@ -162,7 +173,7 @@ class DataSerializerSpecification extends SerializationSpecification { }) } - property("header roundtrip") { + property("nuanced versioned test for header roundtrip") { VersionContext.withVersions(VersionContext.V6SoftForkVersion, 1) { forAll { x: Header => roundtrip[SHeader.type](x, SHeader) } } diff --git a/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala index 2332aaccaa..1db166c685 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala @@ -4,9 +4,6 @@ import sigma.VersionContext import sigma.ast._ import sigma.validation.ValidationException -import scala.util.Try - - class MethodCallSerializerSpecification extends SerializationSpecification { property("MethodCall deserialization round trip") { @@ -48,28 +45,4 @@ class MethodCallSerializerSpecification extends SerializationSpecification { } ) } - - property("MethodCall deserialization round trip for Header.checkPow") { - def code = { - val h = HeaderConstant(headerGen.sample.get) - val expr = MethodCall(h, - SHeaderMethods.checkPowMethod, - Vector(), - Map() - ) - roundTripTest(expr) - } - - VersionContext.withVersions(VersionContext.V6SoftForkVersion, 1) { - code - } - - // sigma.serialization.SerializerException: Don't know how to serialize (sigma.data.CHeader@51dbec76, SHeader) - an[SerializerException] should be thrownBy ( - VersionContext.withVersions((VersionContext.V6SoftForkVersion - 1).toByte, 1) { - code - } - ) - } - } diff --git a/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala b/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala index e625923413..f48e1ffc96 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala @@ -694,7 +694,6 @@ trait ObjectGenerators extends TypeGenerators } yield ErgoTree.withSegregation(ZeroHeader, prop) def headerGen(stateRoot: AvlTree, parentId: Coll[Byte]): Gen[Header] = for { - id <- modifierIdBytesGen version <- arbByte.arbitrary adProofsRoot <- digest32Gen transactionRoot <- digest32Gen @@ -708,7 +707,7 @@ trait ObjectGenerators extends TypeGenerators powDistance <- arbBigInt.arbitrary votes <- minerVotesGen unparsedBytes <- collOfRange(0, 32, arbByte.arbitrary) - } yield CHeader(id, version, parentId, adProofsRoot, stateRoot, transactionRoot, timestamp, nBits, + } yield CHeader(version, parentId, adProofsRoot, stateRoot, transactionRoot, timestamp, nBits, height, extensionRoot, minerPk.toGroupElement, powOnetimePk.toGroupElement, powNonce, powDistance, votes, unparsedBytes) lazy val headerGen: Gen[Header] = for { diff --git a/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala b/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala index d1253d4686..63fc202516 100644 --- a/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala +++ b/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala @@ -246,7 +246,6 @@ trait SigmaTestingData extends TestingCommons with ObjectGenerators { ) val h1_instances = new CloneSet(1000, CHeader( - Helpers.decodeBytes("957f008001808080ffe4ffffc8f3802401df40006aa05e017fa8d3f6004c804a"), 0.toByte, Helpers.decodeBytes("0180dd805b0000ff5400b997fd7f0b9b00de00fb03c47e37806a8186b94f07ff"), Helpers.decodeBytes("01f07f60d100ffb970c3007f60ff7f24d4070bb8fffa7fca7f34c10001ffe39d"), diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala b/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala index 9bb501faed..7c7b80d39a 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/GraphBuilding.scala @@ -1,7 +1,6 @@ package sigma.compiler.ir import org.ergoplatform._ -import sigma.{SigmaException, VersionContext, ast} import sigma.ast.TypeCodes.LastConstantCode import sigma.ast.Value.Typed import sigma.ast.syntax.{SValue, ValueOps} diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala index f38748bbe4..2a6a341686 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/SigmaDslUnit.scala @@ -78,7 +78,6 @@ import scalan._ def powNonce: Ref[Coll[Byte]]; def powDistance: Ref[BigInt]; def votes: Ref[Coll[Byte]] - def checkPow: Ref[Boolean] }; trait Context extends Def[Context] { def OUTPUTS: Ref[Coll[Box]]; diff --git a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala index 8d1b3aa1f5..c113cb7de3 100644 --- a/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala +++ b/sc/shared/src/main/scala/sigma/compiler/ir/wrappers/sigma/impl/SigmaDslImpl.scala @@ -1368,13 +1368,6 @@ object Header extends EntityObject("Header") { ArraySeq.empty, true, false, element[Coll[Byte]])) } - - override def checkPow: Ref[Boolean] = { - asRep[Boolean](mkMethodCall(self, - HeaderClass.getMethod("checkPow"), - ArraySeq.empty, - true, false, element[Boolean])) - } } implicit object LiftableHeader @@ -1499,13 +1492,6 @@ object Header extends EntityObject("Header") { ArraySeq.empty, true, true, element[Coll[Byte]])) } - - def checkPow: Ref[Boolean] = { - asRep[Boolean](mkMethodCall(source, - HeaderClass.getMethod("checkPow"), - ArraySeq.empty, - true, true, element[Boolean])) - } } // entityUnref: single unref method for each type family diff --git a/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala b/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala index 1977537cbf..fddace6a5c 100644 --- a/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala +++ b/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala @@ -4654,7 +4654,6 @@ class SigmaDslSpecification extends SigmaDslTesting ) val header = CHeader( - Helpers.decodeBytes("1c597f88969600d2fffffdc47f00d8ffc555a9e85001000001c505ff80ff8f7f"), 0.toByte, Helpers.decodeBytes("7a7fe5347f09017818010062000001807f86808000ff7f66ffb07f7ad27f3362"), Helpers.decodeBytes("c1d70ad9b1ffc1fb9a715fff19807f2401017fcd8b73db017f1cff77727fff08"), diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala index 89d5e3413c..66df205e85 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala @@ -7,13 +7,9 @@ import sigma.ast.{Constant, SType} import sigma.data.Iso import sigma.data.Iso.{isoStringToArray, isoStringToColl} import sigma.data.js.{Isos => DataIsos} -import sigma.data.{CBigInt, CGroupElement, CHeader, Digest32Coll, Digest32CollRType, Iso} +import sigma.data.CHeader import sigma.interpreter.{ContextExtension, ProverResult} import sigma.js.AvlTree -import sigmastate.eval.{CHeader, CPreHeader} -import sigma.js.{AvlTree, GroupElement} -import sigma.serialization.{ErgoTreeSerializer, ValueSerializer} -import sigma.{Coll, Colls} import sigmastate.eval.CPreHeader import sigmastate.fleetSdkCommon.distEsmTypesBoxesMod.Box import sigmastate.fleetSdkCommon.distEsmTypesRegistersMod.NonMandatoryRegisters @@ -32,7 +28,6 @@ object Isos { implicit val isoHeader: Iso[Header, sigma.Header] = new Iso[Header, sigma.Header] { override def to(a: Header): sigma.Header = { CHeader( - id = isoStringToColl.to(a.id), version = a.version, parentId = isoStringToColl.to(a.parentId), ADProofsRoot = isoStringToColl.to(a.ADProofsRoot), diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala index b7f445d902..0b6aa5555f 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala @@ -20,7 +20,7 @@ import sigma.serialization.{ErgoTreeSerializer, ValueSerializer} import sigma.validation.SigmaValidationSettings import sigma.{AnyValue, Coll, Colls, Header, PreHeader, SigmaException} import sigmastate.eval.{CPreHeader, _} -import sigmastate.utils.Helpers._ +import sigmastate.utils.Helpers._ // required for Scala 2.11 import java.math.BigInteger import scala.collection.mutable @@ -132,7 +132,6 @@ trait JsonCodecs { implicit val headerDecoder: Decoder[Header] = Decoder.instance({ cursor => for { - id <- cursor.downField("id").as[Coll[Byte]] version <- cursor.downField("version").as[Byte] parentId <- cursor.downField("parentId").as[Coll[Byte]] adProofsRoot <- cursor.downField("adProofsRoot").as[Coll[Byte]] @@ -148,7 +147,7 @@ trait JsonCodecs { powDistance <- cursor.downField("powDistance").as[sigma.BigInt] votes <- cursor.downField("votes").as[Coll[Byte]] unparsedBytes <- cursor.downField("unparsedBytes").as[Option[Coll[Byte]]] - } yield CHeader(id, version, parentId, adProofsRoot, stateRoot, transactionsRoot, timestamp, nBits, + } yield CHeader(version, parentId, adProofsRoot, stateRoot, transactionsRoot, timestamp, nBits, height, extensionRoot, SigmaDsl.decodePoint(minerPk), SigmaDsl.decodePoint(powOnetimePk), powNonce, powDistance, votes, unparsedBytes.getOrElse(Colls.emptyColl)) }) From 21a6c742184f3295c3100decbbcff99e08502d67 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 4 Jun 2024 12:44:33 +0300 Subject: [PATCH 04/10] JS compilation fix, fix for preheader id in SigmaDslSpecification --- .../src/main/scala/org/ergoplatform/ErgoHeader.scala | 4 ++-- .../src/test/scala/sigma/SigmaDslSpecification.scala | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala b/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala index dc076c4173..dd1417073f 100644 --- a/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala +++ b/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala @@ -1,11 +1,10 @@ package org.ergoplatform -import org.bouncycastle.util.BigIntegers import scorex.crypto.authds.ADDigest import scorex.crypto.hash.{Blake2b256, Digest32} import scorex.util.ModifierId import sigma.Colls -import sigma.crypto.{CryptoConstants, EcPointType} +import sigma.crypto.{BigIntegers, CryptoConstants, EcPointType} import sigma.serialization.{GroupElementSerializer, SigmaByteReader, SigmaByteWriter, SigmaSerializer} @@ -88,6 +87,7 @@ object AutolykosSolution { * @param extensionRoot - Merkle tree digest of the extension section of the block * @param powSolution - solution for the proof-of-work puzzle * @param votes - votes for changing system parameters + * @param unparsedBytes - bytes from future versions of the protocol our version can't parse * @param _bytes - serialized bytes of the header when not `null` */ case class ErgoHeader(override val version: ErgoHeader.Version, diff --git a/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala b/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala index fddace6a5c..06016c40f0 100644 --- a/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala +++ b/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala @@ -41,11 +41,11 @@ import scala.util.{Failure, Success} /** This suite tests every method of every SigmaDsl type to be equivalent to * the evaluation of the corresponding ErgoScript operation. * - * The properties of this suite excercise two interpreters: the current (aka `old` + * The properties of this suite exercise two interpreters: the current (aka `old` * interpreter) and the new interpreter for a next soft-fork. After the soft-fork is * released, the new interpreter becomes current at which point the `old` and `new` * interpreters in this suite should be equivalent. This change is reflected in this - * suite by commiting changes in expected values. + * suite by committing changes in expected values. * The `old` and `new` interpreters are compared like the following: * 1) for existingFeature the interpreters should be equivalent * 2) for changedFeature the test cases contain different expected values @@ -53,7 +53,7 @@ import scala.util.{Failure, Success} * against expected values. * * This suite can be used for Cost profiling, i.e. measurements of operations times and - * comparing them with cost parameteres of the operations. + * comparing them with cost parameters of the operations. * * The following settings should be specified for profiling: * isMeasureOperationTime = true @@ -4683,7 +4683,7 @@ class SigmaDslSpecification extends SigmaDslTesting headers = Coll[Header](header), preHeader = CPreHeader( 0.toByte, - Helpers.decodeBytes("1c597f88969600d2fffffdc47f00d8ffc555a9e85001000001c505ff80ff8f7f"), + header.id, -755484979487531112L, 9223372036854775807L, 11, From e5f504cc66cd49fed9412c528f20d1584d5df333 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 4 Jun 2024 12:54:26 +0300 Subject: [PATCH 05/10] checkPoW test removed --- .../TestingInterpreterSpecification.scala | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala b/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala index 4948f70c23..cc98bc79c3 100644 --- a/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala @@ -429,32 +429,6 @@ class TestingInterpreterSpecification extends CompilerTestingCommons testEval(s"""deserialize[Coll[Byte]]("$str")(0) == 2""") } - property("header.id") { - testEval( - """ { - | val h = CONTEXT.headers(0) - | val id = h.id - | id.size == 32 - | }""".stripMargin) - } - - property("checkPow") { - - //todo: check invalid header - - val source = """ { - | val h = CONTEXT.headers(0) - | h.checkPow - | } - | """.stripMargin - - if (activatedVersionInTests < V6SoftForkVersion) { - an [sigmastate.exceptions.MethodNotFound] should be thrownBy testEval(source) - } else { - testEval(source) - } - } - override protected def afterAll(): Unit = { } From e763c4a153d4523082464dfd5cfec7c23476a646 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 4 Jun 2024 13:15:36 +0300 Subject: [PATCH 06/10] fixing header.id euivalence test --- sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala b/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala index 06016c40f0..7376d7f1e7 100644 --- a/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala +++ b/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala @@ -4516,7 +4516,7 @@ class SigmaDslSpecification extends SigmaDslTesting property("Header properties equivalence") { verifyCases( Seq((h1, Expected(Success( - Helpers.decodeBytes("957f008001808080ffe4ffffc8f3802401df40006aa05e017fa8d3f6004c804a")), + Helpers.decodeBytes("cea31f0e0a794b103f65f8296a22ac8ff214e1bc75442186b90df4844c978e81")), cost = 1766, methodCostDetails(SHeaderMethods.idMethod, 10), 1766))), existingPropTest("id", { (x: Header) => x.id })) From 4d01c24f21726ec798decfbf421885748a5c48b1 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 4 Jun 2024 15:05:34 +0300 Subject: [PATCH 07/10] stateRootDigest arg --- .../src/main/scala/sigma/data/CHeader.scala | 4 +-- .../DataSerializerSpecification.scala | 27 ++++++++++++++++++- .../generators/ObjectGenerators.scala | 2 +- .../special/sigma/SigmaTestingData.scala | 8 +++--- .../scala/sigma/SigmaDslSpecification.scala | 9 +------ .../org/ergoplatform/sdk/JsonCodecs.scala | 2 +- 6 files changed, 35 insertions(+), 17 deletions(-) diff --git a/data/shared/src/main/scala/sigma/data/CHeader.scala b/data/shared/src/main/scala/sigma/data/CHeader.scala index d17fb5671f..89ac3de7f7 100644 --- a/data/shared/src/main/scala/sigma/data/CHeader.scala +++ b/data/shared/src/main/scala/sigma/data/CHeader.scala @@ -87,7 +87,7 @@ object CHeader { def apply( version: Byte, parentId: Coll[Byte], ADProofsRoot: Coll[Byte], - stateRoot: AvlTree, + stateRootDigest: Coll[Byte], transactionsRoot: Coll[Byte], timestamp: Long, nBits: Long, @@ -107,7 +107,7 @@ object CHeader { powDistance.asInstanceOf[CBigInt].wrappedValue) val h = ErgoHeader(version, bytesToId(parentId.toArray), Digest32 @@ ADProofsRoot.toArray, - ADDigest @@ stateRoot.digest.toArray, Digest32 @@ transactionsRoot.toArray, timestamp, nBits, height, + ADDigest @@ stateRootDigest.toArray, Digest32 @@ transactionsRoot.toArray, timestamp, nBits, height, Digest32 @@ extensionRoot.toArray, solution, votes.toArray, unparsedBytes.toArray, null) new CHeader(h) diff --git a/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala index fecd077287..f79c6a8c47 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala @@ -2,8 +2,9 @@ package sigma.serialization import java.math.BigInteger import org.ergoplatform.ErgoBox +import org.ergoplatform.settings.ErgoAlgos import org.scalacheck.Arbitrary._ -import sigma.data.{DataValueComparer, OptionType, RType, SigmaBoolean, TupleColl} +import sigma.data.{AvlTreeData, AvlTreeFlags, CAvlTree, CBigInt, CHeader, DataValueComparer, OptionType, RType, SigmaBoolean, TupleColl} import sigma.ast.SCollection.SByteArray import sigmastate.eval._ import sigma.{AvlTree, Colls, Evaluation, Header, VersionContext} @@ -185,4 +186,28 @@ class DataSerializerSpecification extends SerializationSpecification { }) } + property("header vector") { + val header = CHeader( + 0.toByte, + Helpers.decodeBytes("7a7fe5347f09017818010062000001807f86808000ff7f66ffb07f7ad27f3362"), + Helpers.decodeBytes("c1d70ad9b1ffc1fb9a715fff19807f2401017fcd8b73db017f1cff77727fff08"), + Helpers.decodeBytes("54d23dd080006bdb56800100356080935a80ffb77e90b800057f00661601807f17"), + Helpers.decodeBytes("5e7f1164ccd0990080c501fc0e0181cb387fc17f00ff00c7d5ff767f91ff5e68"), + -7421721754642387858L, + -4826493284887861030L, + 10, + Helpers.decodeBytes("e580c88001ff6fc89c5501017f80e001ff0101fe48c153ff7f00666b80d780ab"), + Helpers.decodeGroupElement("03e7f2875298fddd933c2e0a38968fe85bdeeb70dd8b389559a1d36e2ff1b58fc5"), + Helpers.decodeGroupElement("034e2d3b5f9e409e3ae8a2e768340760362ca33764eda5855f7a43487f14883300"), + Helpers.decodeBytes("974651c9efff7f00"), + CBigInt(new BigInteger("478e827dfa1e4b57", 16)), + Helpers.decodeBytes("01ff13"), + Colls.emptyColl + ) + + VersionContext.withVersions(VersionContext.V6SoftForkVersion, 1) { + roundtrip[SHeader.type](header, SHeader) + } + } + } diff --git a/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala b/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala index f48e1ffc96..b0af46a5d3 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala @@ -707,7 +707,7 @@ trait ObjectGenerators extends TypeGenerators powDistance <- arbBigInt.arbitrary votes <- minerVotesGen unparsedBytes <- collOfRange(0, 32, arbByte.arbitrary) - } yield CHeader(version, parentId, adProofsRoot, stateRoot, transactionRoot, timestamp, nBits, + } yield CHeader(version, parentId, adProofsRoot, stateRoot.digest, transactionRoot, timestamp, nBits, height, extensionRoot, minerPk.toGroupElement, powOnetimePk.toGroupElement, powNonce, powDistance, votes, unparsedBytes) lazy val headerGen: Gen[Header] = for { diff --git a/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala b/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala index 63fc202516..b8bf76cacf 100644 --- a/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala +++ b/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala @@ -240,16 +240,16 @@ trait SigmaTestingData extends TestingCommons with ObjectGenerators { def createAvlTreeData() = AvlTreeData( ErgoAlgos.decodeUnsafe("010180017f7f7b7f720c00007f7f7f0f01e857a626f37f1483d06af8077a008080").toColl, - AvlTreeFlags(false, true, false), - 728138553, - Some(2147483647) + AvlTreeFlags(true, true, true), + 32, + None ) val h1_instances = new CloneSet(1000, CHeader( 0.toByte, Helpers.decodeBytes("0180dd805b0000ff5400b997fd7f0b9b00de00fb03c47e37806a8186b94f07ff"), Helpers.decodeBytes("01f07f60d100ffb970c3007f60ff7f24d4070bb8fffa7fca7f34c10001ffe39d"), - CAvlTree(createAvlTreeData()), + CAvlTree(createAvlTreeData()).digest, Helpers.decodeBytes("804101ff01000080a3ffbd006ac080098df132a7017f00649311ec0e00000100"), 1L, -1L, diff --git a/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala b/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala index 7376d7f1e7..c4d1db777d 100644 --- a/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala +++ b/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala @@ -4657,14 +4657,7 @@ class SigmaDslSpecification extends SigmaDslTesting 0.toByte, Helpers.decodeBytes("7a7fe5347f09017818010062000001807f86808000ff7f66ffb07f7ad27f3362"), Helpers.decodeBytes("c1d70ad9b1ffc1fb9a715fff19807f2401017fcd8b73db017f1cff77727fff08"), - CAvlTree( - AvlTreeData( - ErgoAlgos.decodeUnsafe("54d23dd080006bdb56800100356080935a80ffb77e90b800057f00661601807f17").toColl, - AvlTreeFlags(true, true, false), - 2147483647, - None - ) - ), + Helpers.decodeBytes("54d23dd080006bdb56800100356080935a80ffb77e90b800057f00661601807f17"), Helpers.decodeBytes("5e7f1164ccd0990080c501fc0e0181cb387fc17f00ff00c7d5ff767f91ff5e68"), -7421721754642387858L, -4826493284887861030L, diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala index 0b6aa5555f..1c7a4156f4 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala @@ -147,7 +147,7 @@ trait JsonCodecs { powDistance <- cursor.downField("powDistance").as[sigma.BigInt] votes <- cursor.downField("votes").as[Coll[Byte]] unparsedBytes <- cursor.downField("unparsedBytes").as[Option[Coll[Byte]]] - } yield CHeader(version, parentId, adProofsRoot, stateRoot, transactionsRoot, timestamp, nBits, + } yield CHeader(version, parentId, adProofsRoot, stateRoot.digest, transactionsRoot, timestamp, nBits, height, extensionRoot, SigmaDsl.decodePoint(minerPk), SigmaDsl.decodePoint(powOnetimePk), powNonce, powDistance, votes, unparsedBytes.getOrElse(Colls.emptyColl)) }) From fe69eef356f5c96a19b8b617d816d878c2891e4b Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 4 Jun 2024 19:11:37 +0300 Subject: [PATCH 08/10] Isos compilation fix --- sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala index 66df205e85..84f2b21da8 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Isos.scala @@ -31,7 +31,7 @@ object Isos { version = a.version, parentId = isoStringToColl.to(a.parentId), ADProofsRoot = isoStringToColl.to(a.ADProofsRoot), - stateRoot = AvlTree.isoAvlTree.to(a.stateRoot), + stateRootDigest = AvlTree.isoAvlTree.to(a.stateRoot).digest, transactionsRoot = isoStringToColl.to(a.transactionsRoot), timestamp = sigma.js.Isos.isoBigIntToLong.to(a.timestamp), nBits = sigma.js.Isos.isoBigIntToLong.to(a.nBits), From 1092b08a83e3209eb634185a8c5cf85d9ef22029 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 4 Jun 2024 22:37:17 +0300 Subject: [PATCH 09/10] improving and fixing tests --- .../src/main/scala/sigma/data/CHeader.scala | 18 ++++++++ .../DataSerializerSpecification.scala | 42 ++++++++++++------- .../generators/ObjectGenerators.scala | 4 +- .../generators/TypeGenerators.scala | 3 +- 4 files changed, 50 insertions(+), 17 deletions(-) diff --git a/data/shared/src/main/scala/sigma/data/CHeader.scala b/data/shared/src/main/scala/sigma/data/CHeader.scala index 89ac3de7f7..5999678e5f 100644 --- a/data/shared/src/main/scala/sigma/data/CHeader.scala +++ b/data/shared/src/main/scala/sigma/data/CHeader.scala @@ -72,6 +72,24 @@ class CHeader(val ergoHeader: ErgoHeader) extends Header with WrapperOf[ErgoHead Colls.fromArray(HeaderWithoutPowSerializer.toBytes(headerWithoutPow)) } + override def toString: String = + s"""CHeader( + | id: ${id}, + | version: ${version}, + | tx proofs hash: ${ADProofsRoot}, + | state root: ${stateRoot.digest}, + | transactions root: ${transactionsRoot}, + | time: $timestamp, + | nbits: $nBits, + | extension root: $extensionRoot, + | miner pubkey: $minerPk, + | pow one time pubkey(from AL 1): $powOnetimePk, + | pow nonce: $powNonce, + | pow distance (from AL 1): $powDistance, + | votes: $votes, + | unparsed bytes: $unparsedBytes + |)""".stripMargin + override def hashCode(): Int = id.hashCode() override def equals(other: Any): Boolean = other match { diff --git a/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala index f79c6a8c47..fe6f62dbe0 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala @@ -2,9 +2,8 @@ package sigma.serialization import java.math.BigInteger import org.ergoplatform.ErgoBox -import org.ergoplatform.settings.ErgoAlgos import org.scalacheck.Arbitrary._ -import sigma.data.{AvlTreeData, AvlTreeFlags, CAvlTree, CBigInt, CHeader, DataValueComparer, OptionType, RType, SigmaBoolean, TupleColl} +import sigma.data.{CBigInt, CHeader, DataValueComparer, OptionType, RType, SigmaBoolean, TupleColl} import sigma.ast.SCollection.SByteArray import sigmastate.eval._ import sigma.{AvlTree, Colls, Evaluation, Header, VersionContext} @@ -56,7 +55,8 @@ class DataSerializerSpecification extends SerializationSpecification { VersionContext.withVersions(ver, 1) { test() } - case None => test() + case None => + test() } } @@ -65,25 +65,32 @@ class DataSerializerSpecification extends SerializationSpecification { implicit val tT = Evaluation.stypeToRType(tpe) implicit val tagT = tT.classTag implicit val tAny = sigma.AnyType + + val withVersion = if (tpe == SHeader) { + Some(VersionContext.V6SoftForkVersion) + } else { + None + } forAll { xs: Array[T#WrappedType] => - roundtrip[SCollection[T]](xs.toColl, SCollection(tpe)) - roundtrip[SType](xs.toColl.map(x => (x, x)).asWrappedType, SCollection(STuple(tpe, tpe))) + roundtrip[SCollection[T]](xs.toColl, SCollection(tpe), withVersion) + roundtrip[SType](xs.toColl.map(x => (x, x)).asWrappedType, SCollection(STuple(tpe, tpe)), withVersion) val triples = xs.toColl.map(x => TupleColl(x, x, x)).asWrappedType - roundtrip(triples, SCollection(STuple(tpe, tpe, tpe))) + roundtrip(triples, SCollection(STuple(tpe, tpe, tpe)), withVersion) val quartets = xs.toColl.map(x => TupleColl(x, x, x, x)).asWrappedType - roundtrip(quartets, SCollection(STuple(tpe, tpe, tpe, tpe))) + roundtrip(quartets, SCollection(STuple(tpe, tpe, tpe, tpe)), withVersion) val nested = xs.toColl.map(x => Colls.fromItems[T#WrappedType](x, x)) - roundtrip[SCollection[SCollection[T]]](nested, SCollection(SCollection(tpe))) + roundtrip[SCollection[SCollection[T]]](nested, SCollection(SCollection(tpe)), withVersion) roundtrip[SType]( xs.toColl.map { x => val arr = Colls.fromItems[T#WrappedType](x, x) (arr, arr) }.asWrappedType, - SCollection(STuple(SCollection(tpe), SCollection(tpe))) + SCollection(STuple(SCollection(tpe), SCollection(tpe))), + withVersion ) } } @@ -93,14 +100,19 @@ class DataSerializerSpecification extends SerializationSpecification { val tT = Evaluation.stypeToRType(tpe) @nowarn implicit val tag: ClassTag[T#WrappedType] = tT.classTag implicit val tAny : RType[Any] = sigma.AnyType + val withVersion = if (tpe == SHeader) { + Some(VersionContext.V6SoftForkVersion) + } else { + None + } forAll { in: (T#WrappedType, T#WrappedType) => val (x,y) = (in._1, in._2) - roundtrip[SType]((x, y).asWrappedType, STuple(tpe, tpe)) - roundtrip[SType](TupleColl(x, y, x).asWrappedType, STuple(tpe, tpe, tpe)) - roundtrip[SType](TupleColl(x, y, x, y).asWrappedType, STuple(tpe, tpe, tpe, tpe)) - roundtrip[STuple](Colls.fromItems[Any](x, y, (x, y)), STuple(tpe, tpe, STuple(tpe, tpe))) - roundtrip[STuple](Colls.fromItems[Any](x, y, TupleColl(x, y, x)), STuple(tpe, tpe, STuple(tpe, tpe, tpe))) - roundtrip[STuple](Colls.fromItems[Any](x, y, TupleColl(x, y, (x, y))), STuple(tpe, tpe, STuple(tpe, tpe, STuple(tpe, tpe)))) + roundtrip[SType]((x, y).asWrappedType, STuple(tpe, tpe), withVersion) + roundtrip[SType](TupleColl(x, y, x).asWrappedType, STuple(tpe, tpe, tpe), withVersion) + roundtrip[SType](TupleColl(x, y, x, y).asWrappedType, STuple(tpe, tpe, tpe, tpe), withVersion) + roundtrip[STuple](Colls.fromItems[Any](x, y, (x, y)), STuple(tpe, tpe, STuple(tpe, tpe)), withVersion) + roundtrip[STuple](Colls.fromItems[Any](x, y, TupleColl(x, y, x)), STuple(tpe, tpe, STuple(tpe, tpe, tpe)), withVersion) + roundtrip[STuple](Colls.fromItems[Any](x, y, TupleColl(x, y, (x, y))), STuple(tpe, tpe, STuple(tpe, tpe, STuple(tpe, tpe))), withVersion) } } diff --git a/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala b/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala index b0af46a5d3..bd77766830 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala @@ -311,6 +311,7 @@ trait ObjectGenerators extends TypeGenerators case SAvlTree => arbAvlTree case SAny => arbAnyVal case SUnit => arbUnit + case SHeader => arbHeader case opt: SOption[a] => Arbitrary(frequency((5, None), (5, for (x <- wrappedTypeGen(opt.elemType)) yield Some(x)))) }).asInstanceOf[Arbitrary[T#WrappedType]].arbitrary @@ -708,7 +709,8 @@ trait ObjectGenerators extends TypeGenerators votes <- minerVotesGen unparsedBytes <- collOfRange(0, 32, arbByte.arbitrary) } yield CHeader(version, parentId, adProofsRoot, stateRoot.digest, transactionRoot, timestamp, nBits, - height, extensionRoot, minerPk.toGroupElement, powOnetimePk.toGroupElement, powNonce, powDistance, votes, unparsedBytes) + height, extensionRoot, minerPk.toGroupElement, powOnetimePk.toGroupElement, powNonce, powDistance, votes, + if(version > HeaderVersion.Interpreter60Version){ unparsedBytes } else {Colls.emptyColl[Byte]}) lazy val headerGen: Gen[Header] = for { stateRoot <- avlTreeGen diff --git a/interpreter/shared/src/test/scala/sigma/serialization/generators/TypeGenerators.scala b/interpreter/shared/src/test/scala/sigma/serialization/generators/TypeGenerators.scala index 81073c4849..70a215e831 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/generators/TypeGenerators.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/generators/TypeGenerators.scala @@ -16,12 +16,13 @@ trait TypeGenerators { implicit val boxTypeGen: Gen[SBox.type] = Gen.const(SBox) implicit val avlTreeTypeGen: Gen[SAvlTree.type] = Gen.const(SAvlTree) implicit val optionSigmaPropTypeGen: Gen[SOption[SSigmaProp.type]] = Gen.const(SOption(SSigmaProp)) + implicit val headerTypeGen: Gen[SHeader.type] = Gen.const(SHeader) implicit val primTypeGen: Gen[SPrimType] = Gen.oneOf[SPrimType](SBoolean, SByte, SShort, SInt, SLong, SBigInt, SGroupElement, SSigmaProp, SUnit) implicit val arbPrimType: Arbitrary[SPrimType] = Arbitrary(primTypeGen) implicit val predefTypeGen: Gen[SPredefType] = - Gen.oneOf[SPredefType](SBoolean, SByte, SShort, SInt, SLong, SBigInt, SGroupElement, SSigmaProp, SUnit, SBox, SAvlTree) + Gen.oneOf[SPredefType](SBoolean, SByte, SShort, SInt, SLong, SBigInt, SGroupElement, SSigmaProp, SUnit, SBox, SAvlTree, SHeader) implicit val arbPredefType: Arbitrary[SPredefType] = Arbitrary(predefTypeGen) implicit def genToArbitrary[T: Gen]: Arbitrary[T] = Arbitrary(implicitly[Gen[T]]) From 0d6b2e080c89218caf0662945dba1f45f2866100 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 5 Jun 2024 13:08:54 +0300 Subject: [PATCH 10/10] custom hashcode and comparison for ErgoHeader --- .../src/main/scala/org/ergoplatform/ErgoHeader.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala b/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala index dd1417073f..b937bef7a3 100644 --- a/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala +++ b/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala @@ -104,9 +104,9 @@ case class ErgoHeader(override val version: ErgoHeader.Version, override val unparsedBytes: Array[Byte], _bytes: Array[Byte]) extends HeaderWithoutPow(version, parentId, ADProofsRoot, stateRoot, transactionsRoot, timestamp, - nBits, height, extensionRoot, votes, unparsedBytes) { + nBits, height, extensionRoot, votes, unparsedBytes) { - lazy val bytes = if (_bytes != null) { + lazy val bytes = if(_bytes != null) { _bytes } else { ErgoHeader.sigmaSerializer.toBytes(this) @@ -116,6 +116,12 @@ case class ErgoHeader(override val version: ErgoHeader.Version, lazy val id = Colls.fromArray(serializedId) + override def hashCode(): Int = id.hashCode() + + override def equals(other: Any): Boolean = other match { + case h: ErgoHeader => h.id == this.id + case _ => false + } }