From 647ae31fc80bdf7e7746a04b8454beab3bc50fe0 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Sat, 27 Apr 2024 21:17:36 +0300 Subject: [PATCH 01/10] checkPow impl before tests --- .../src/main/scala/sigma/SigmaDsl.scala | 7 + .../sigma/reflection/ReflectionData.scala | 3 + .../main/scala/sigma/util/NBitsUtils.scala | 84 +++++++++ .../org/ergoplatform/HeaderWithoutPow.scala | 137 ++++++++++++++ .../src/main/scala/sigma/ast/methods.scala | 30 +++- .../scala/sigma/eval/ErgoTreeEvaluator.scala | 5 +- .../sigma/pow/Autolykos2PowValidation.scala | 167 ++++++++++++++++++ .../main/scala/sigmastate/eval/CHeader.scala | 25 ++- .../interpreter/CErgoTreeEvaluator.scala | 15 +- .../generators/ObjectGenerators.scala | 3 +- .../special/sigma/SigmaTestingData.scala | 9 +- .../scala/sigmastate/eval/GraphBuilding.scala | 4 +- .../scala/special/sigma/SigmaDslUnit.scala | 1 + .../special/sigma/impl/SigmaDslImpl.scala | 14 ++ .../scala/sigma/SigmaDslSpecification.scala | 3 +- .../org/ergoplatform/sdk/js/Header.scala | 4 +- .../scala/org/ergoplatform/sdk/js/Isos.scala | 7 +- .../org/ergoplatform/sdk/JsonCodecs.scala | 7 +- 18 files changed, 502 insertions(+), 23 deletions(-) create mode 100644 core/shared/src/main/scala/sigma/util/NBitsUtils.scala create mode 100644 data/shared/src/main/scala/org/ergoplatform/HeaderWithoutPow.scala create 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 df2b419273..a2894515a2 100644 --- a/core/shared/src/main/scala/sigma/SigmaDsl.scala +++ b/core/shared/src/main/scala/sigma/SigmaDsl.scala @@ -459,6 +459,13 @@ trait Header { /** Miner votes for changing system parameters. */ def votes: Coll[Byte] //3 bytes + + def unparsedBytes: Coll[Byte] + + 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 2aac1a5670..c5259e142f 100644 --- a/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala +++ b/core/shared/src/main/scala/sigma/reflection/ReflectionData.scala @@ -340,6 +340,9 @@ 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 new file mode 100644 index 0000000000..36d526d1d5 --- /dev/null +++ b/core/shared/src/main/scala/sigma/util/NBitsUtils.scala @@ -0,0 +1,84 @@ +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/HeaderWithoutPow.scala b/data/shared/src/main/scala/org/ergoplatform/HeaderWithoutPow.scala new file mode 100644 index 0000000000..d29cc6cbd8 --- /dev/null +++ b/data/shared/src/main/scala/org/ergoplatform/HeaderWithoutPow.scala @@ -0,0 +1,137 @@ +package org.ergoplatform + +import scorex.crypto.authds.ADDigest +import scorex.crypto.hash.Digest32 +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. + */ +class HeaderWithoutPow(val version: Byte, // 1 byte + val parentId: ModifierId, // 32 bytes + val ADProofsRoot: Digest32, // 32 bytes + val stateRoot: ADDigest, //33 bytes! extra byte with tree height here! + val transactionsRoot: Digest32, // 32 bytes + val timestamp: Long, + val nBits: Long, //actually it is unsigned int + val height: Int, + val extensionRoot: Digest32, + val votes: Array[Byte], //3 bytes + val unparsedBytes: Array[Byte]) + +object HeaderWithoutPow { + + def apply(version: Byte, parentId: ModifierId, ADProofsRoot: Digest32, stateRoot: ADDigest, + transactionsRoot: Digest32, timestamp: Long, nBits: Long, height: Int, + extensionRoot: Digest32, votes: Array[Byte], unparsedBytes: Array[Byte]): HeaderWithoutPow = { + new HeaderWithoutPow(version, parentId, ADProofsRoot, stateRoot, transactionsRoot, timestamp, + nBits, height, extensionRoot, votes, unparsedBytes) + } + +} + +object HeaderWithoutPowSerializer extends SigmaSerializer[HeaderWithoutPow, HeaderWithoutPow] { + + override def serialize(h: HeaderWithoutPow, w: SigmaByteWriter): Unit = { + w.put(h.version) + w.putBytes(idToBytes(h.parentId)) + w.putBytes(h.ADProofsRoot) + w.putBytes(h.transactionsRoot) + w.putBytes(h.stateRoot) + w.putULong(h.timestamp) + w.putBytes(h.extensionRoot) + DifficultySerializer.serialize(h.nBits, w) + w.putUInt(h.height.toLong) + w.putBytes(h.votes) + + // For block version >= 2, this new byte encodes length of possible new fields. + // Set to 0 for now, so no new fields. + if (h.version > HeaderVersion.InitialVersion) { + w.putUByte(h.unparsedBytes.length) + w.putBytes(h.unparsedBytes) + } + } + + override def parse(r: SigmaByteReader): HeaderWithoutPow = { + val version = r.getByte() + val parentId = bytesToId(r.getBytes(32)) + val ADProofsRoot = Digest32 @@ r.getBytes(32) + val transactionsRoot = Digest32 @@ r.getBytes(32) + val stateRoot = ADDigest @@ r.getBytes(33) + val timestamp = r.getULong() + val extensionHash = Digest32 @@ r.getBytes(32) + val nBits = DifficultySerializer.parse(r) + val height = r.getUInt().toIntExact + val votes = r.getBytes(3) + + // For block version >= 2, a new byte encodes length of possible new fields. + // If this byte > 0, we read new fields but do nothing, as semantics of the fields is not known. + val unparsedBytes = if (version > HeaderVersion.InitialVersion) { + val newFieldsSize = r.getUByte() + if (newFieldsSize > 0 && version > HeaderVersion.Interpreter60Version) { + // new bytes could be added only for block version >= 5 + r.getBytes(newFieldsSize) + } else { + Array.emptyByteArray + } + } else { + Array.emptyByteArray + } + + HeaderWithoutPow(version, parentId, ADProofsRoot, stateRoot, transactionsRoot, timestamp, + nBits, height, extensionHash, votes, unparsedBytes) + } + +} + + +object DifficultySerializer extends SigmaSerializer[Long, Long] { + + /** 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) + + def uint32ToByteArrayBE(value: Long): Array[Byte] = { + Array(0xFF & (value >> 24), 0xFF & (value >> 16), 0xFF & (value >> 8), 0xFF & value).map(_.toByte) + } + + override def serialize(obj: Long, w: SigmaByteWriter): Unit = { + w.putBytes(uint32ToByteArrayBE(obj)) + } + + override def parse(r: SigmaByteReader): Long = { + readUint32BE(r.getBytes(4)) + } + +} + +object HeaderVersion { + type Value = Byte + + /** + * Block version during mainnet launch + */ + val InitialVersion: Value = 1.toByte + + /** + * Block version after the Hardening hard-fork + * Autolykos v2 PoW, witnesses in transactions Merkle tree + */ + val HardeningVersion: Value = 2.toByte + + /** + * Block version after the 5.0 soft-fork + * 5.0 interpreter with JITC, monotonic height rule (EIP-39) + */ + val Interpreter50Version: Value = 3.toByte + + /** + * Block version after the 6.0 soft-fork + * 6.0 interpreter (EIP-50) + */ + val Interpreter60Version: Value = 4.toByte + +} diff --git a/data/shared/src/main/scala/sigma/ast/methods.scala b/data/shared/src/main/scala/sigma/ast/methods.scala index c6b71cd120..a8c6545a53 100644 --- a/data/shared/src/main/scala/sigma/ast/methods.scala +++ b/data/shared/src/main/scala/sigma/ast/methods.scala @@ -10,6 +10,7 @@ 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,11 +1458,30 @@ 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))) - protected override def getMethods() = super.getMethods() ++ Seq( - idMethod, versionMethod, parentIdMethod, ADProofsRootMethod, stateRootMethod, transactionsRootMethod, - timestampMethod, nBitsMethod, heightMethod, extensionRootMethod, minerPkMethod, powOnetimePkMethod, - powNonceMethod, powDistanceMethod, votesMethod - ) + lazy val checkPowMethod = SMethod( + this, "checkPow", SFunc(Array(SHeader), SBoolean), 3, GroupGenerator.costKind) // todo: cost + .withIRInfo(MethodCallIrBuilder) + .withInfo(Xor, "Byte-wise XOR of two collections of bytes") // 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) + } + } } /** Type descriptor of `PreHeader` type of ErgoTree. */ diff --git a/data/shared/src/main/scala/sigma/eval/ErgoTreeEvaluator.scala b/data/shared/src/main/scala/sigma/eval/ErgoTreeEvaluator.scala index 52f839354c..610be08c9c 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} +import sigma.{AvlTree, Coll, Context, Header} import sigma.ast.{Constant, FixedCost, MethodCall, OperationCostInfo, OperationDesc, PerItemCost, SType, TypeBasedCost} import sigma.data.KeyValueColl @@ -138,6 +138,9 @@ 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 new file mode 100644 index 0000000000..a27dc864d1 --- /dev/null +++ b/data/shared/src/main/scala/sigma/pow/Autolykos2PowValidation.scala @@ -0,0 +1,167 @@ +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/eval/CHeader.scala b/interpreter/shared/src/main/scala/sigmastate/eval/CHeader.scala index 7062fa0f0e..3bd0dd62f9 100644 --- a/interpreter/shared/src/main/scala/sigmastate/eval/CHeader.scala +++ b/interpreter/shared/src/main/scala/sigmastate/eval/CHeader.scala @@ -1,7 +1,12 @@ 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.{AvlTree, BigInt, Coll, GroupElement, Header} +import sigma.pow.Autolykos2PowValidation +import sigma.{AvlTree, BigInt, Coll, Colls, GroupElement, Header} /** A default implementation of [[Header]] interface. * @@ -22,8 +27,22 @@ case class CHeader( powOnetimePk: GroupElement, powNonce: Coll[Byte], powDistance: BigInt, - votes: Coll[Byte] -) extends Header + 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 = { + Autolykos2PowValidation.checkPoWForVersion2(this) + } + +} object CHeader { /** Size of of Header.votes array. */ diff --git a/interpreter/shared/src/main/scala/sigmastate/interpreter/CErgoTreeEvaluator.scala b/interpreter/shared/src/main/scala/sigmastate/interpreter/CErgoTreeEvaluator.scala index de8aa6b620..59c9af09ef 100644 --- a/interpreter/shared/src/main/scala/sigmastate/interpreter/CErgoTreeEvaluator.scala +++ b/interpreter/shared/src/main/scala/sigmastate/interpreter/CErgoTreeEvaluator.scala @@ -5,7 +5,7 @@ import sigma.ast._ import sigma.ast.syntax._ import sigmastate.eval.{CAvlTreeVerifier, CProfiler} import sigmastate.interpreter.Interpreter.ReductionResult -import sigma.{AvlTree, Coll, Colls, Context, VersionContext} +import sigma.{AvlTree, Coll, Colls, Context, Header, VersionContext} import sigma.util.Extensions._ import debox.{cfor, Buffer => DBuffer} import scorex.crypto.authds.ADKey @@ -14,6 +14,8 @@ 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} @@ -216,6 +218,15 @@ 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) @@ -449,7 +460,7 @@ object CErgoTreeEvaluator { * HOTSPOT: don't beautify the code * Note, `null` is used instead of Option to avoid allocations. */ - def fixedCostOp[R <: AnyRef](costInfo: OperationCostInfo[FixedCost]) + def fixedCostOp[R](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/generators/ObjectGenerators.scala b/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala index d4971f88c2..e625923413 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/generators/ObjectGenerators.scala @@ -707,8 +707,9 @@ trait ObjectGenerators extends TypeGenerators powNonce <- nonceBytesGen powDistance <- arbBigInt.arbitrary votes <- minerVotesGen + unparsedBytes <- collOfRange(0, 32, arbByte.arbitrary) } yield CHeader(id, version, parentId, adProofsRoot, stateRoot, transactionRoot, timestamp, nBits, - height, extensionRoot, minerPk.toGroupElement, powOnetimePk.toGroupElement, powNonce, powDistance, votes) + height, extensionRoot, minerPk.toGroupElement, powOnetimePk.toGroupElement, powNonce, powDistance, votes, unparsedBytes) lazy val headerGen: Gen[Header] = for { stateRoot <- avlTreeGen diff --git a/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala b/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala index d33f09dd80..08fa3439d6 100644 --- a/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala +++ b/interpreter/shared/src/test/scala/special/sigma/SigmaTestingData.scala @@ -84,7 +84,8 @@ trait SigmaTestingData extends TestingCommons with ObjectGenerators { powOnetimePk = SigmaDsl.groupGenerator, powNonce = Colls.fromArray(Array[Byte](0, 1, 2, 3, 4, 5, 6, 7)), powDistance = SigmaDsl.BigInt(BigInt("1405498250268750867257727119510201256371618473728619086008183115260323").bigInteger), - votes = Colls.fromArray(Array[Byte](0, 1, 2)) + votes = Colls.fromArray(Array[Byte](0, 1, 2)), + unparsedBytes = Colls.emptyColl[Byte] ) val header2: Header = CHeader(Blake2b256("Header2.id").toColl, 0, @@ -100,7 +101,8 @@ trait SigmaTestingData extends TestingCommons with ObjectGenerators { powOnetimePk = SigmaDsl.groupGenerator, powNonce = Colls.fromArray(Array.fill(0.toByte)(8)), powDistance = SigmaDsl.BigInt(BigInt("19306206489815517413186395405558417825367537880571815686937307203793939").bigInteger), - votes = Colls.fromArray(Array[Byte](0, 1, 0)) + votes = Colls.fromArray(Array[Byte](0, 1, 0)), + unparsedBytes = Colls.emptyColl[Byte] ) val headers = Colls.fromItems(header2, header1) val preHeader: PreHeader = CPreHeader(0, @@ -321,7 +323,8 @@ trait SigmaTestingData extends TestingCommons with ObjectGenerators { Helpers.decodeGroupElement("0361299207fa392231e23666f6945ae3e867b978e021d8d702872bde454e9abe9c"), Helpers.decodeBytes("7f4f09012a807f01"), CBigInt(new BigInteger("-e24990c47e15ed4d0178c44f1790cc72155d516c43c3e8684e75db3800a288", 16)), - Helpers.decodeBytes("7f0180") + Helpers.decodeBytes("7f0180"), + Colls.emptyColl[Byte] )) def create_h1(): Header = h1_instances.getNext diff --git a/sc/shared/src/main/scala/sigmastate/eval/GraphBuilding.scala b/sc/shared/src/main/scala/sigmastate/eval/GraphBuilding.scala index 9c9fa5ffe1..83db8d3ede 100644 --- a/sc/shared/src/main/scala/sigmastate/eval/GraphBuilding.scala +++ b/sc/shared/src/main/scala/sigmastate/eval/GraphBuilding.scala @@ -2,7 +2,7 @@ package sigmastate.eval import org.ergoplatform._ import scalan.MutableLazy -import sigma.{SigmaException, ast} +import sigma.{SigmaException, VersionContext, ast} import sigma.ast.TypeCodes.LastConstantCode import sigma.ast.Value.Typed import sigma.ast._ @@ -1130,6 +1130,8 @@ trait GraphBuilding extends SigmaLibrary { IR: IRContext => h.powDistance case SHeaderMethods.votesMethod.name => h.votes + case SHeaderMethods.checkPowMethod.name if VersionContext.current.isV6SoftForkActivated => + h.checkPow case _ => throwError } case (g: Ref[SigmaDslBuilder]@unchecked, SGlobalMethods) => method.name match { diff --git a/sc/shared/src/main/scala/special/sigma/SigmaDslUnit.scala b/sc/shared/src/main/scala/special/sigma/SigmaDslUnit.scala index 48548226a5..d84bf40c4f 100644 --- a/sc/shared/src/main/scala/special/sigma/SigmaDslUnit.scala +++ b/sc/shared/src/main/scala/special/sigma/SigmaDslUnit.scala @@ -76,6 +76,7 @@ package sigma { 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/special/sigma/impl/SigmaDslImpl.scala b/sc/shared/src/main/scala/special/sigma/impl/SigmaDslImpl.scala index 8da36ce6cf..1a6639c8cd 100644 --- a/sc/shared/src/main/scala/special/sigma/impl/SigmaDslImpl.scala +++ b/sc/shared/src/main/scala/special/sigma/impl/SigmaDslImpl.scala @@ -1367,6 +1367,13 @@ 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 @@ -1491,6 +1498,13 @@ 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 4dd576f03a..7fe0d4395c 100644 --- a/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala +++ b/sc/shared/src/test/scala/sigma/SigmaDslSpecification.scala @@ -4675,7 +4675,8 @@ class SigmaDslSpecification extends SigmaDslTesting Helpers.decodeGroupElement("034e2d3b5f9e409e3ae8a2e768340760362ca33764eda5855f7a43487f14883300"), Helpers.decodeBytes("974651c9efff7f00"), CBigInt(new BigInteger("478e827dfa1e4b57", 16)), - Helpers.decodeBytes("01ff13") + Helpers.decodeBytes("01ff13"), + Colls.emptyColl ) val ctx = CContext( diff --git a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Header.scala b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Header.scala index ef53e13dbd..ec3767c1cd 100644 --- a/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Header.scala +++ b/sdk/js/src/main/scala/org/ergoplatform/sdk/js/Header.scala @@ -48,5 +48,7 @@ class Header( val powDistance: js.BigInt, /** Miner votes for changing system parameters. */ - val votes: String + val votes: String, + + val unparsedBytes: String ) extends js.Object 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 f6393f62bb..340765d313 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 @@ -25,7 +25,6 @@ import sigmastate.fleetSdkCommon.distEsmTypesTokenMod.TokenAmount import sigmastate.fleetSdkCommon.distEsmTypesTransactionsMod.{SignedTransaction, UnsignedTransaction} import sigmastate.fleetSdkCommon.{distEsmTypesBoxesMod => boxesMod, distEsmTypesCommonMod => commonMod, distEsmTypesContextExtensionMod => contextExtensionMod, distEsmTypesInputsMod => inputsMod, distEsmTypesProverResultMod => proverResultMod, distEsmTypesRegistersMod => registersMod, distEsmTypesTokenMod => tokenMod} -import java.math.BigInteger import scala.collection.immutable.ListMap import scala.scalajs.js import scala.scalajs.js.Object @@ -87,7 +86,8 @@ object Isos { powOnetimePk = isoGroupElement.to(a.powOnetimePk), powNonce = isoStringToColl.to(a.powNonce), powDistance = sigma.js.Isos.isoBigInt.to(a.powDistance), - votes = isoStringToColl.to(a.votes) + votes = isoStringToColl.to(a.votes), + unparsedBytes = isoStringToColl.to(a.unparsedBytes) ) } override def from(b: sigma.Header): Header = { @@ -107,7 +107,8 @@ object Isos { powOnetimePk = isoGroupElement.from(header.powOnetimePk), powNonce = isoStringToColl.from(header.powNonce), powDistance = sigma.js.Isos.isoBigInt.from(header.powDistance), - votes = isoStringToColl.from(header.votes) + votes = isoStringToColl.from(header.votes), + unparsedBytes = isoStringToColl.from(header.unparsedBytes) ) } } 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 ae14fd831a..c2fc1c0c8c 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala @@ -125,7 +125,8 @@ trait JsonCodecs { "powOnetimePk" -> h.powOnetimePk.getEncoded.asJson, "powNonce" -> h.powNonce.asJson, "powDistance" -> h.powDistance.asJson, - "votes" -> h.votes.asJson + "votes" -> h.votes.asJson, + "unparsedBytes" -> h.unparsedBytes.asJson ).asJson }) @@ -146,8 +147,10 @@ trait JsonCodecs { powNonce <- cursor.downField("powNonce").as[Coll[Byte]] powDistance <- cursor.downField("powDistance").as[sigma.BigInt] votes <- cursor.downField("votes").as[Coll[Byte]] + unparsedBytes <- cursor.downField("unparsedBytes").as[Option[Coll[Byte]]] } yield new CHeader(id, version, parentId, adProofsRoot, stateRoot, transactionsRoot, timestamp, nBits, - height, extensionRoot, SigmaDsl.decodePoint(minerPk), SigmaDsl.decodePoint(powOnetimePk), powNonce, powDistance, votes) + height, extensionRoot, SigmaDsl.decodePoint(minerPk), SigmaDsl.decodePoint(powOnetimePk), powNonce, powDistance, + votes, unparsedBytes.getOrElse(Colls.emptyColl)) }) implicit val preHeaderEncoder: Encoder[PreHeader] = Encoder.instance({ v: PreHeader => From a8e1acff73cb9ad6cd6b2b065c85af333c2adc21 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Mon, 29 Apr 2024 23:21:24 +0300 Subject: [PATCH 02/10] checkPow test in TestingInterpreterSpecification --- .../serialization/CoreDataSerializer.scala | 3 +- .../src/main/scala/sigma/ast/methods.scala | 2 +- .../TestingInterpreterSpecification.scala | 102 ++++++++++++++++-- 3 files changed, 95 insertions(+), 12 deletions(-) diff --git a/core/shared/src/main/scala/sigma/serialization/CoreDataSerializer.scala b/core/shared/src/main/scala/sigma/serialization/CoreDataSerializer.scala index 479b199da5..56872cd68e 100644 --- a/core/shared/src/main/scala/sigma/serialization/CoreDataSerializer.scala +++ b/core/shared/src/main/scala/sigma/serialization/CoreDataSerializer.scala @@ -68,7 +68,8 @@ class CoreDataSerializer { i += 1 } - // TODO v6.0 (3h): support Option[T] (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/659) + // TODO v6.0 : support Option[T] (see https://github.com/ScorexFoundation/sigmastate-interpreter/issues/659) + // TODO v6.0 : support Header case _ => CheckSerializableTypeCode(tpe.typeCode) throw new SerializerException(s"Don't know how to serialize ($v, $tpe)") diff --git a/data/shared/src/main/scala/sigma/ast/methods.scala b/data/shared/src/main/scala/sigma/ast/methods.scala index a8c6545a53..ca4501967f 100644 --- a/data/shared/src/main/scala/sigma/ast/methods.scala +++ b/data/shared/src/main/scala/sigma/ast/methods.scala @@ -1459,7 +1459,7 @@ case object SHeaderMethods extends MonoTypeMethods { lazy val votesMethod = propertyCall("votes", SByteArray, 15, FixedCost(JitCost(10))) lazy val checkPowMethod = SMethod( - this, "checkPow", SFunc(Array(SHeader), SBoolean), 3, GroupGenerator.costKind) // todo: cost + 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 diff --git a/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala b/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala index fe5a678679..387efe7b1c 100644 --- a/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala @@ -6,17 +6,26 @@ import sigma.ast._ import sigma.ast.syntax._ import sigmastate.interpreter._ import Interpreter._ +import io.circe.parser.parse import sigma.ast.syntax._ import org.ergoplatform._ +import org.ergoplatform.sdk.JsonCodecs import org.scalatest.BeforeAndAfterAll import scorex.util.encode.Base58 +import sigma.Colls +import sigma.VersionContext.V6SoftForkVersion import sigma.crypto.CryptoConstants -import sigma.data.{AvlTreeData, CAND, ProveDlog, SigmaBoolean, TrivialProp} +import sigma.data.{AvlTreeData, CAND, CAvlTree, ProveDlog, SigmaBoolean, TrivialProp} +import sigma.interpreter.ContextExtension import sigma.util.Extensions.IntOps import sigmastate.helpers.{CompilerTestingCommons, ErgoLikeContextTesting, ErgoLikeTestInterpreter, ErgoLikeTestProvingInterpreter} import sigmastate.helpers.TestingHelpers._ -import sigma.serialization.ValueSerializer +import sigma.serialization.{GroupElementSerializer, SigmaSerializer, ValueSerializer} +import sigmastate.eval.CPreHeader +import sigmastate.helpers.ErgoLikeContextTesting.{dummyPreHeader, noBoxes, noHeaders} +import sigmastate.interpreter.CErgoTreeEvaluator.DefaultEvalSettings import sigmastate.utils.Helpers._ +import sigma.util.Extensions._ import scala.util.Random @@ -32,12 +41,60 @@ class TestingInterpreterSpecification extends CompilerTestingCommons implicit val soundness: Int = CryptoConstants.soundnessBits - def testingContext(h: Int) = - ErgoLikeContextTesting(h, - AvlTreeData.dummy, ErgoLikeContextTesting.dummyPubkey, IndexedSeq(fakeSelf), - ErgoLikeTransaction(IndexedSeq.empty, IndexedSeq.empty), - fakeSelf, activatedVersionInTests) - .withErgoTreeVersion(ergoTreeVersionInTests) + def testingContext(h: Int = 614401) = { + + // valid header from Ergo blockchain + val headerJson = + """ + |{ + | "extensionId" : "00cce45975d87414e8bdd8146bc88815be59cd9fe37a125b5021101e05675a18", + | "votes" : "000000", + | "timestamp" : 4928911477310178288, + | "size" : 223, + | "unparsedBytes" : "", + | "stateRoot" : { + | "digest" : "5c8c00b8403d3701557181c8df800001b6d5009e2201c6ff807d71808c00019780", + | "treeFlags" : "0", + | "keyLength" : "32" + | }, + | "height" : 614400, + | "nBits" : 37748736, + | "version" : 2, + | "id" : "5603a937ec1988220fc44fb5022fb82d5565b961f005ebb55d85bd5a9e6f801f", + | "adProofsRoot" : "5d3f80dcff7f5e7f59007294c180808d0158d1ff6ba10000f901c7f0ef87dcff", + | "transactionsRoot" : "f17fffacb6ff7f7f1180d2ff7f1e24ffffe1ff937f807f0797b9ff6ebdae007e", + | "extensionRoot" : "1480887f80007f4b01cf7f013ff1ffff564a0000b9a54f00770e807f41ff88c0", + | "minerPk" : "03bedaee069ff4829500b3c07c4d5fe6b3ea3d3bf76c5c28c1d4dcdb1bed0ade0c", + | "powOnetimePk" : "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + | "powNonce" : "0000000000003105", + | "powDistance" : 0, + | "adProofsId" : "dec129290a763f4de41f04e87e2b661dd59758af6bdd00dd51f5d97c3a8cb9b5", + | "transactionsId" : "eba1dd82cf51147232e09c1f72b37c554c30f63274d5093bff36849a83472a42", + | "parentId" : "ac2101807f0000ca01ff0119db227f202201007f62000177a080005d440896d0" + |} + |""".stripMargin + + object JsonCodecs extends JsonCodecs + val header1 = JsonCodecs.headerDecoder.decodeJson(parse(headerJson).toOption.get).toOption.get + + val boxesToSpend = IndexedSeq(fakeSelf) + + val preHeader = CPreHeader(activatedVersionInTests, + parentId = header1.id, + timestamp = 3, + nBits = 0, + height = h, + minerPk = GroupElementSerializer.parse(SigmaSerializer.startReader(ErgoLikeContextTesting.dummyPubkey)).toGroupElement, + votes = Colls.emptyColl[Byte] + ) + + new ErgoLikeContext( + header1.stateRoot.asInstanceOf[CAvlTree].treeData, Colls.fromArray(Array(header1)), + preHeader, noBoxes, + boxesToSpend, ErgoLikeTransaction(IndexedSeq.empty, IndexedSeq.empty), + boxesToSpend.indexOf(fakeSelf), ContextExtension.empty, vs, DefaultEvalSettings.scriptCostLimitInEvaluator, + initCost = 0L, activatedVersionInTests).withErgoTreeVersion(ergoTreeVersionInTests) + } property("Reduction to crypto #1") { forAll() { i: Int => @@ -119,7 +176,7 @@ class TestingInterpreterSpecification extends CompilerTestingCommons val dk1 = prover.dlogSecrets(0).publicImage val dk2 = prover.dlogSecrets(1).publicImage - val ctx = testingContext(99) + val ctx = testingContext() val env = Map( "dk1" -> dk1, "dk2" -> dk2, @@ -127,7 +184,9 @@ class TestingInterpreterSpecification extends CompilerTestingCommons "bytes2" -> Array[Byte](4, 5, 6), "box1" -> testBox(10, TrueTree, 0, Seq(), Map( reg1 -> IntArrayConstant(Array[Int](1, 2, 3)), - reg2 -> BoolArrayConstant(Array[Boolean](true, false, true))))) + reg2 -> BoolArrayConstant(Array[Boolean](true, false, true)) + )) + ) val prop = mkTestErgoTree(compile(env, code).asBoolValue.toSigmaProp) val challenge = Array.fill(32)(Random.nextInt(100).toByte) val proof1 = prover.prove(prop, ctx, challenge).get.proof @@ -374,6 +433,29 @@ 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") { + 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 69278ee1db2ac2af6bdf8693b271b8d3ec5360c9 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 1 May 2024 12:45:51 +0300 Subject: [PATCH 03/10] MethodCall deserialization round trip for Header.checkPow --- .../MethodCallSerializerSpecification.scala | 23 +++++++++++++++++++ .../TestingInterpreterSpecification.scala | 8 ++++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala index 1db166c685..c65b86930c 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/MethodCallSerializerSpecification.scala @@ -45,4 +45,27 @@ class MethodCallSerializerSpecification extends SerializationSpecification { } ) } + + property("MethodCall deserialization round trip for Header.checkPow") { + def code = { + val bi = HeaderConstant(headerGen.sample.get) + val expr = MethodCall(bi, + SHeaderMethods.checkPowMethod, + Vector(), + Map() + ) + roundTripTest(expr) + } + + VersionContext.withVersions(VersionContext.V6SoftForkVersion, 1) { + code + } + + an[ValidationException] should be thrownBy ( + VersionContext.withVersions((VersionContext.V6SoftForkVersion - 1).toByte, 1) { + code + } + ) + } + } diff --git a/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala b/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala index 387efe7b1c..d6bce80a4c 100644 --- a/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/TestingInterpreterSpecification.scala @@ -7,7 +7,6 @@ import sigma.ast.syntax._ import sigmastate.interpreter._ import Interpreter._ import io.circe.parser.parse -import sigma.ast.syntax._ import org.ergoplatform._ import org.ergoplatform.sdk.JsonCodecs import org.scalatest.BeforeAndAfterAll @@ -15,14 +14,14 @@ import scorex.util.encode.Base58 import sigma.Colls import sigma.VersionContext.V6SoftForkVersion import sigma.crypto.CryptoConstants -import sigma.data.{AvlTreeData, CAND, CAvlTree, ProveDlog, SigmaBoolean, TrivialProp} +import sigma.data.{CAND, CAvlTree, ProveDlog, SigmaBoolean, TrivialProp} import sigma.interpreter.ContextExtension import sigma.util.Extensions.IntOps import sigmastate.helpers.{CompilerTestingCommons, ErgoLikeContextTesting, ErgoLikeTestInterpreter, ErgoLikeTestProvingInterpreter} import sigmastate.helpers.TestingHelpers._ import sigma.serialization.{GroupElementSerializer, SigmaSerializer, ValueSerializer} import sigmastate.eval.CPreHeader -import sigmastate.helpers.ErgoLikeContextTesting.{dummyPreHeader, noBoxes, noHeaders} +import sigmastate.helpers.ErgoLikeContextTesting.noBoxes import sigmastate.interpreter.CErgoTreeEvaluator.DefaultEvalSettings import sigmastate.utils.Helpers._ import sigma.util.Extensions._ @@ -443,6 +442,9 @@ class TestingInterpreterSpecification extends CompilerTestingCommons } property("checkPow") { + + //todo: check invalid header + val source = """ { | val h = CONTEXT.headers(0) | h.checkPow From 16af268db73971f3bf5e910ae3d6fc2289e36beb Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 3 May 2024 17:14:25 +0300 Subject: [PATCH 04/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 05/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 06/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 d01469c71e46a815eaabceffe914004ef9bdeb75 Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Tue, 4 Jun 2024 12:44:33 +0300 Subject: [PATCH 07/10] fixing and improving tests --- .../scala/org/ergoplatform/ErgoHeader.scala | 14 ++-- .../src/main/scala/sigma/data/CHeader.scala | 22 ++++++- .../ConstantSerializerSpecification.scala | 41 ++++++++---- .../DataSerializerSpecification.scala | 65 +++++++++++++++---- .../SerializationSpecification.scala | 19 ++++-- .../generators/ObjectGenerators.scala | 6 +- .../generators/TypeGenerators.scala | 3 +- .../special/sigma/SigmaTestingData.scala | 8 +-- .../scala/sigma/SigmaDslSpecification.scala | 19 ++---- .../TestingInterpreterSpecification.scala | 26 -------- .../scala/org/ergoplatform/sdk/js/Isos.scala | 2 +- .../org/ergoplatform/sdk/JsonCodecs.scala | 2 +- 12 files changed, 142 insertions(+), 85 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..b937bef7a3 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, @@ -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 + } } diff --git a/data/shared/src/main/scala/sigma/data/CHeader.scala b/data/shared/src/main/scala/sigma/data/CHeader.scala index d17fb5671f..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 { @@ -87,7 +105,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 +125,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/ConstantSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigma/serialization/ConstantSerializerSpecification.scala index 43e9cf9e5d..c9478c8356 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/ConstantSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/ConstantSerializerSpecification.scala @@ -9,7 +9,7 @@ import sigma.ast.{BigIntConstant, ByteArrayConstant, Constant, DeserializationSi import sigmastate.eval._ import sigma.Extensions.ArrayOps import sigma.ast._ -import sigma.{AvlTree, Colls, Evaluation} +import sigma.{AvlTree, Colls, Evaluation, Header, VersionContext} import sigma.ast.SType.AnyOps import scorex.util.encode.Base16 import sigma.ast.BoolArrayConstant.BoolArrayTypeCode @@ -17,6 +17,7 @@ import sigma.ast.ByteArrayConstant.ByteArrayTypeCode import sigma.ast.syntax.{BoolValue, SValue} import sigma.crypto.EcPointType import sigma.util.Extensions.{BigIntegerOps, EcpOps, SigmaBooleanOps} + import scala.annotation.nowarn class ConstantSerializerSpecification extends TableSerializationSpecification { @@ -25,22 +26,29 @@ class ConstantSerializerSpecification extends TableSerializationSpecification { implicit val wWrapped = wrappedTypeGen(tpe) implicit val tT = Evaluation.stypeToRType(tpe) implicit val tag = tT.classTag + + val withVersion = if (tpe == SHeader) { + Some(VersionContext.V6SoftForkVersion) + } else { + None + } + forAll { xs: Array[T#WrappedType] => implicit val tAny = sigma.AnyType - roundTripTest(Constant[SCollection[T]](xs.toColl, SCollection(tpe))) - roundTripTest(Constant[SType](xs.toColl.map(x => (x, x)).asWrappedType, SCollection(STuple(tpe, tpe)))) // pairs are special case + roundTripTest(Constant[SCollection[T]](xs.toColl, SCollection(tpe)), withVersion) + roundTripTest(Constant[SType](xs.toColl.map(x => (x, x)).asWrappedType, SCollection(STuple(tpe, tpe))), withVersion) // pairs are special case val triples = xs.toColl.map(x => TupleColl(x, x, x)).asWrappedType - roundTripTest(Constant[SType](triples, SCollection(STuple(tpe, tpe, tpe)))) + roundTripTest(Constant[SType](triples, SCollection(STuple(tpe, tpe, tpe))), withVersion) val quartets = xs.toColl.map(x => TupleColl(x, x, x, x)).asWrappedType - roundTripTest(Constant[SType](quartets, SCollection(STuple(tpe, tpe, tpe, tpe)))) - roundTripTest(Constant[SCollection[SCollection[T]]](xs.toColl.map(x => Colls.fromItems(x, x)), SCollection(SCollection(tpe)))) + roundTripTest(Constant[SType](quartets, SCollection(STuple(tpe, tpe, tpe, tpe))), withVersion) + roundTripTest(Constant[SCollection[SCollection[T]]](xs.toColl.map(x => Colls.fromItems(x, x)), SCollection(SCollection(tpe))), withVersion) roundTripTest(Constant[SType]( xs.toColl.map { x => val arr = Colls.fromItems(x, x) (arr, arr) }.asWrappedType, SCollection(STuple(SCollection(tpe), SCollection(tpe))) - )) + ), withVersion) } } @@ -49,14 +57,19 @@ class ConstantSerializerSpecification extends TableSerializationSpecification { implicit val tT = Evaluation.stypeToRType(tpe) @nowarn implicit val tag = 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) - roundTripTest(Constant[SType]((x, y).asWrappedType, STuple(tpe, tpe))) - roundTripTest(Constant[SType](TupleColl(x, y, x).asWrappedType, STuple(tpe, tpe, tpe))) - roundTripTest(Constant[SType](TupleColl(x, y, x, y).asWrappedType, STuple(tpe, tpe, tpe, tpe))) - roundTripTest(Constant[STuple](Colls.fromItems[Any](x, y, (x, y)), STuple(tpe, tpe, STuple(tpe, tpe)))) - roundTripTest(Constant[STuple](Colls.fromItems[Any](x, y, TupleColl(x, y, x)), STuple(tpe, tpe, STuple(tpe, tpe, tpe)))) - roundTripTest(Constant[STuple](Colls.fromItems[Any](x, y, TupleColl(x, y, (x, y))), STuple(tpe, tpe, STuple(tpe, tpe, STuple(tpe, tpe))))) + roundTripTest(Constant[SType]((x, y).asWrappedType, STuple(tpe, tpe)), withVersion) + roundTripTest(Constant[SType](TupleColl(x, y, x).asWrappedType, STuple(tpe, tpe, tpe)), withVersion) + roundTripTest(Constant[SType](TupleColl(x, y, x, y).asWrappedType, STuple(tpe, tpe, tpe, tpe)), withVersion) + roundTripTest(Constant[STuple](Colls.fromItems[Any](x, y, (x, y)), STuple(tpe, tpe, STuple(tpe, tpe))), withVersion) + roundTripTest(Constant[STuple](Colls.fromItems[Any](x, y, TupleColl(x, y, x)), STuple(tpe, tpe, STuple(tpe, tpe, tpe))), withVersion) + roundTripTest(Constant[STuple](Colls.fromItems[Any](x, y, TupleColl(x, y, (x, y))), STuple(tpe, tpe, STuple(tpe, tpe, STuple(tpe, tpe)))), withVersion) } } @@ -71,6 +84,7 @@ class ConstantSerializerSpecification extends TableSerializationSpecification { forAll { x: SigmaBoolean => roundTripTest(Constant[SSigmaProp.type](x.toSigmaProp, SSigmaProp)) } forAll { x: ErgoBox => roundTripTest(Constant[SBox.type](x, SBox)) } forAll { x: AvlTree => roundTripTest(Constant[SAvlTree.type](x, SAvlTree)) } + forAll { x: Header => roundTripTest(Constant[SHeader.type](x, SHeader), Some(VersionContext.V6SoftForkVersion)) } forAll { x: Array[Byte] => roundTripTest(Constant[SByteArray](x.toColl, SByteArray)) } forAll { t: SPredefType => testCollection(t) } forAll { t: SPredefType => testTuples(t) } @@ -88,6 +102,7 @@ class ConstantSerializerSpecification extends TableSerializationSpecification { testCollection(SUnit) testCollection(SBox) testCollection(SAvlTree) + testCollection(SHeader) } private def caseObjectValue(v: SValue) = (v, Array[Byte](v.opCode)) diff --git a/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala b/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala index fecd077287..fe6f62dbe0 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/DataSerializerSpecification.scala @@ -3,7 +3,7 @@ package sigma.serialization import java.math.BigInteger import org.ergoplatform.ErgoBox import org.scalacheck.Arbitrary._ -import sigma.data.{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} @@ -55,7 +55,8 @@ class DataSerializerSpecification extends SerializationSpecification { VersionContext.withVersions(ver, 1) { test() } - case None => test() + case None => + test() } } @@ -64,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 ) } } @@ -92,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) } } @@ -185,4 +198,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/SerializationSpecification.scala b/interpreter/shared/src/test/scala/sigma/serialization/SerializationSpecification.scala index 36c75f3224..dc8eef7319 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/SerializationSpecification.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/SerializationSpecification.scala @@ -8,6 +8,7 @@ import org.scalacheck.Arbitrary._ import org.scalatest.matchers.should.Matchers import org.scalatest.propspec.AnyPropSpec import org.scalatestplus.scalacheck.{ScalaCheckDrivenPropertyChecks, ScalaCheckPropertyChecks} +import sigma.VersionContext import sigma.ast.SType import sigma.ast._ import sigmastate.helpers.NegativeTesting @@ -26,10 +27,20 @@ trait SerializationSpecification extends AnyPropSpec with ValidationSpecification with NegativeTesting { - protected def roundTripTest[V <: Value[_ <: SType]](v: V): Assertion = { - val bytes = ValueSerializer.serialize(v) - predefinedBytesTest(v, bytes) - predefinedBytesTestNotFomZeroElement(bytes, v) + protected def roundTripTest[V <: Value[_ <: SType]](v: V, withVersion: Option[Byte] = None): Assertion = { + def test() = { + val bytes = ValueSerializer.serialize(v) + predefinedBytesTest(v, bytes) + predefinedBytesTestNotFomZeroElement(bytes, v) + } + withVersion match { + case Some(ver) => + VersionContext.withVersions(ver, 1) { + test() + } + case None => + test() + } } protected def predefinedBytesTest[V <: Value[_ <: SType]](v: V, bytes: Array[Byte]): Assertion = { 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..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 @@ -707,8 +708,9 @@ trait ObjectGenerators extends TypeGenerators powDistance <- arbBigInt.arbitrary votes <- minerVotesGen unparsedBytes <- collOfRange(0, 32, arbByte.arbitrary) - } yield CHeader(version, parentId, adProofsRoot, stateRoot, transactionRoot, timestamp, nBits, - height, extensionRoot, minerPk.toGroupElement, powOnetimePk.toGroupElement, powNonce, powDistance, votes, unparsedBytes) + } yield CHeader(version, parentId, adProofsRoot, stateRoot.digest, transactionRoot, timestamp, nBits, + 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]]) 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 fddace6a5c..c4d1db777d 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 @@ -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 })) @@ -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, @@ -4683,7 +4676,7 @@ class SigmaDslSpecification extends SigmaDslTesting headers = Coll[Header](header), preHeader = CPreHeader( 0.toByte, - Helpers.decodeBytes("1c597f88969600d2fffffdc47f00d8ffc555a9e85001000001c505ff80ff8f7f"), + header.id, -755484979487531112L, 9223372036854775807L, 11, 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 = { } 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), 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 07945e9a19ec1dc6a57809730fc817f15dd4612f Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 5 Jun 2024 18:56:40 +0300 Subject: [PATCH 08/10] fixing DataJsonEncoder --- .../scala/sigmastate/TypesSpecification.scala | 2 - .../ergoplatform/sdk/DataJsonEncoder.scala | 8 ++ .../sdk/DataJsonEncoderSpecification.scala | 92 ++++++++++++++----- 3 files changed, 77 insertions(+), 25 deletions(-) diff --git a/sc/shared/src/test/scala/sigmastate/TypesSpecification.scala b/sc/shared/src/test/scala/sigmastate/TypesSpecification.scala index 9a4c5ce1b8..6d13863f26 100644 --- a/sc/shared/src/test/scala/sigmastate/TypesSpecification.scala +++ b/sc/shared/src/test/scala/sigmastate/TypesSpecification.scala @@ -13,8 +13,6 @@ class TypesSpecification extends SigmaTestingData { implicit val tWrapped = wrappedTypeGen(t) forAll { x: SPredefType#WrappedType => isValueOfType(x, t) shouldBe true - // since forall t. SHeader != t - isValueOfType(x, SHeader) shouldBe false } } } diff --git a/sdk/shared/src/main/scala/org/ergoplatform/sdk/DataJsonEncoder.scala b/sdk/shared/src/main/scala/org/ergoplatform/sdk/DataJsonEncoder.scala index fc95b77e61..6c0c866baf 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/DataJsonEncoder.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/DataJsonEncoder.scala @@ -122,6 +122,10 @@ object DataJsonEncoder { val w = SigmaSerializer.startWriter() DataSerializer.serialize(v, tpe, w) encodeBytes(w.toBytes) + case SHeader => + val w = SigmaSerializer.startWriter() + DataSerializer.serialize(v, tpe, w) + encodeBytes(w.toBytes) case SAvlTree => val w = SigmaSerializer.startWriter() DataSerializer.serialize(v, tpe, w) @@ -203,6 +207,10 @@ object DataJsonEncoder { val str = decodeBytes(json) val r = SigmaSerializer.startReader(str) DataSerializer.deserialize(SSigmaProp, r) + case SHeader => // for Sigma < 6.0 , exception will be thrown by DataSerializer + val str = decodeBytes(json) + val r = SigmaSerializer.startReader(str) + DataSerializer.deserialize(SHeader, r) case SBox => val value = decodeData(json.hcursor.downField(s"value").focus.get, SLong) val tree = ErgoTreeSerializer.DefaultSerializer.deserializeErgoTree(decodeBytes(json.hcursor.downField(s"ergoTree").focus.get)) diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/DataJsonEncoderSpecification.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/DataJsonEncoderSpecification.scala index c3f7b43af4..33e9546000 100644 --- a/sdk/shared/src/test/scala/org/ergoplatform/sdk/DataJsonEncoderSpecification.scala +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/DataJsonEncoderSpecification.scala @@ -13,7 +13,7 @@ import sigma.serialization.SerializerException import sigma.util.Extensions.{BigIntegerOps, EcpOps, SigmaBooleanOps} import sigma.Extensions.ArrayOps import sigma.eval.SigmaDsl -import sigma.{AvlTree, Box, Colls, Evaluation} +import sigma.{AvlTree, Box, Colls, Evaluation, Header, VersionContext} import sigma.serialization.SerializationSpecification import scala.annotation.nowarn @@ -22,29 +22,48 @@ import scala.reflect.ClassTag class DataJsonEncoderSpecification extends SerializationSpecification { object JsonCodecs extends JsonCodecs - def roundtrip[T <: SType](obj: T#WrappedType, tpe: T) = { - val json = DataJsonEncoder.encode(obj, tpe) - val res = DataJsonEncoder.decode(json) - res shouldBe obj + def roundtrip[T <: SType](obj: T#WrappedType, tpe: T, withVersion: Option[Byte] = None) = { + def test() = { + val json = DataJsonEncoder.encode(obj, tpe) + val res = DataJsonEncoder.decode(json) + res shouldBe obj + } + + withVersion match { + case Some(ver) => + VersionContext.withVersions(ver, 1) { + test() + } + case None => + test() + } } def testCollection[T <: SType](tpe: T) = { implicit val wWrapped = wrappedTypeGen(tpe) implicit val tT = Evaluation.stypeToRType(tpe) implicit val tagT = tT.classTag + + 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 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 ) } } @@ -54,11 +73,18 @@ class DataJsonEncoderSpecification extends SerializationSpecification { val tT = Evaluation.stypeToRType(tpe) @nowarn implicit val tag : ClassTag[T#WrappedType] = tT.classTag @nowarn 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](((x, y), (x, y)).asWrappedType, STuple(STuple(tpe, tpe), STuple(tpe, tpe))) - roundtrip[SType](((x, y), ((x, y), (x, y))).asWrappedType, STuple(STuple(tpe, tpe), STuple(STuple(tpe, tpe), STuple(tpe, tpe)))) + roundtrip[SType]((x, y).asWrappedType, STuple(tpe, tpe), withVersion) + roundtrip[SType](((x, y), (x, y)).asWrappedType, STuple(STuple(tpe, tpe), STuple(tpe, tpe)), withVersion) + roundtrip[SType](((x, y), ((x, y), (x, y))).asWrappedType, STuple(STuple(tpe, tpe), STuple(STuple(tpe, tpe), STuple(tpe, tpe))), withVersion) } } @@ -98,6 +124,7 @@ class DataJsonEncoderSpecification extends SerializationSpecification { forAll { x: AvlTree => roundtrip[SAvlTree.type](x, SAvlTree) } forAll { x: Array[Byte] => roundtrip[SByteArray](x.toColl, SByteArray) } forAll { x: Box => roundtrip[SBox.type](x, SBox) } + forAll { x: Header => roundtrip[SHeader.type](x, SHeader, Some(VersionContext.V6SoftForkVersion)) } forAll { x: Option[Byte] => roundtrip[SOption[SByte.type]](x, SOption[SByte.type]) } testCollection(SOption[SLong.type]) testTuples(SOption[SLong.type]) @@ -187,25 +214,44 @@ class DataJsonEncoderSpecification extends SerializationSpecification { val tT = Evaluation.stypeToRType(tpe) @nowarn implicit val tag = tT.classTag @nowarn implicit val tAny = sigma.AnyType - forAll { x: T#WrappedType => - an[SerializerException] should be thrownBy { - DataJsonEncoder.encode(TupleColl(x, x, x).asWrappedType, STuple(tpe, tpe, tpe)) - } - // supported case - DataJsonEncoder.encode(SigmaDsl.Colls.fromItems(TupleColl(x, x)).asWrappedType, SCollection(STuple(tpe, tpe))) - // not supported case - an[SerializerException] should be thrownBy { - DataJsonEncoder.encode(SigmaDsl.Colls.fromItems(TupleColl(x, x, x)).asWrappedType, SCollection(STuple(tpe, tpe, tpe))) + def test() = { + forAll { x: T#WrappedType => + an[SerializerException] should be thrownBy { + DataJsonEncoder.encode(TupleColl(x, x, x).asWrappedType, STuple(tpe, tpe, tpe)) + } + + // supported case + DataJsonEncoder.encode(SigmaDsl.Colls.fromItems(TupleColl(x, x)).asWrappedType, SCollection(STuple(tpe, tpe))) + + // not supported case + an[SerializerException] should be thrownBy { + DataJsonEncoder.encode(SigmaDsl.Colls.fromItems(TupleColl(x, x, x)).asWrappedType, SCollection(STuple(tpe, tpe, tpe))) + } } } + + if (tpe == SHeader) { + VersionContext.withVersions(VersionContext.V6SoftForkVersion, 1) { + test() + } + } else { + test() + } } property("AnyValue") { forAll { t: SPredefType => - testAnyValue(t) - testAnyValue(SOption(t)) + if (t == SHeader) { + VersionContext.withVersions(VersionContext.V6SoftForkVersion, 1) { + testAnyValue(t) + testAnyValue(SOption(t)) + } + } else { + testAnyValue(t) + testAnyValue(SOption(t)) + } } } From a8234b9fe2e0a46bfe1da6a165f8f1877cb35cca Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Wed, 12 Jun 2024 14:17:33 +0300 Subject: [PATCH 09/10] review comments addressed --- core/shared/src/main/scala/sigma/SigmaDsl.scala | 2 +- .../src/main/scala/org/ergoplatform/ErgoHeader.scala | 12 ++++++------ data/shared/src/main/scala/sigma/data/CHeader.scala | 2 +- .../serialization/SerializationSpecification.scala | 2 +- .../sdk/DataJsonEncoderSpecification.scala | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/core/shared/src/main/scala/sigma/SigmaDsl.scala b/core/shared/src/main/scala/sigma/SigmaDsl.scala index 7353b8f89e..52c2f97bd7 100644 --- a/core/shared/src/main/scala/sigma/SigmaDsl.scala +++ b/core/shared/src/main/scala/sigma/SigmaDsl.scala @@ -467,7 +467,7 @@ trait Header { def unparsedBytes: Coll[Byte] /** - * @return bytes without proof of work, needed for working to get the proof on + * @return header bytes without proof of work, a PoW is generated over them */ def serializeWithoutPoW: Coll[Byte] diff --git a/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala b/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala index b937bef7a3..b8f75bd862 100644 --- a/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala +++ b/data/shared/src/main/scala/org/ergoplatform/ErgoHeader.scala @@ -19,10 +19,10 @@ import sigma.serialization.{GroupElementSerializer, SigmaByteReader, SigmaByteWr * @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) { +class AutolykosSolution(val pk: EcPointType, + val w: EcPointType, + val n: Array[Byte], + val d: BigInt) { val encodedPk: Array[Byte] = GroupElementSerializer.toBytes(pk) @@ -52,7 +52,7 @@ object AutolykosSolution { val nonce = r.getBytes(8) val dBytesLength = r.getUByte() val d = BigInt(BigIntegers.fromUnsignedByteArray(r.getBytes(dBytesLength))) - AutolykosSolution(pk, w, nonce, d) + new AutolykosSolution(pk, w, nonce, d) } } @@ -66,7 +66,7 @@ object AutolykosSolution { override def parse(r: SigmaByteReader): AutolykosSolution = { val pk = GroupElementSerializer.parse(r) val nonce = r.getBytes(8) - AutolykosSolution(pk, wForV2, nonce, dForV2) + new AutolykosSolution(pk, wForV2, nonce, dForV2) } } } diff --git a/data/shared/src/main/scala/sigma/data/CHeader.scala b/data/shared/src/main/scala/sigma/data/CHeader.scala index 5999678e5f..d0b8db6173 100644 --- a/data/shared/src/main/scala/sigma/data/CHeader.scala +++ b/data/shared/src/main/scala/sigma/data/CHeader.scala @@ -118,7 +118,7 @@ object CHeader { votes: Coll[Byte], unparsedBytes: Coll[Byte]): CHeader = { - val solution = AutolykosSolution( + val solution = new AutolykosSolution( minerPk.asInstanceOf[CGroupElement].wrappedValue, powOnetimePk.asInstanceOf[CGroupElement].wrappedValue, powNonce.toArray, diff --git a/interpreter/shared/src/test/scala/sigma/serialization/SerializationSpecification.scala b/interpreter/shared/src/test/scala/sigma/serialization/SerializationSpecification.scala index dc8eef7319..aa7a8722ba 100644 --- a/interpreter/shared/src/test/scala/sigma/serialization/SerializationSpecification.scala +++ b/interpreter/shared/src/test/scala/sigma/serialization/SerializationSpecification.scala @@ -35,7 +35,7 @@ trait SerializationSpecification extends AnyPropSpec } withVersion match { case Some(ver) => - VersionContext.withVersions(ver, 1) { + VersionContext.withVersions(ver, 0) { test() } case None => diff --git a/sdk/shared/src/test/scala/org/ergoplatform/sdk/DataJsonEncoderSpecification.scala b/sdk/shared/src/test/scala/org/ergoplatform/sdk/DataJsonEncoderSpecification.scala index 33e9546000..5835f399cb 100644 --- a/sdk/shared/src/test/scala/org/ergoplatform/sdk/DataJsonEncoderSpecification.scala +++ b/sdk/shared/src/test/scala/org/ergoplatform/sdk/DataJsonEncoderSpecification.scala @@ -31,7 +31,7 @@ class DataJsonEncoderSpecification extends SerializationSpecification { withVersion match { case Some(ver) => - VersionContext.withVersions(ver, 1) { + VersionContext.withVersions(ver, 0) { test() } case None => @@ -233,7 +233,7 @@ class DataJsonEncoderSpecification extends SerializationSpecification { } if (tpe == SHeader) { - VersionContext.withVersions(VersionContext.V6SoftForkVersion, 1) { + VersionContext.withVersions(VersionContext.V6SoftForkVersion, 0) { test() } } else { From 1271a79fe29f5685b8c3cc7c5eda0ef6abe5b54d Mon Sep 17 00:00:00 2001 From: Alexander Chepurnoy Date: Fri, 26 Jul 2024 17:44:01 +0300 Subject: [PATCH 10/10] ScalaDoc for headerDecoder --- .../src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala | 5 +++++ 1 file changed, 5 insertions(+) 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 1c7a4156f4..98e8011bcc 100644 --- a/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala +++ b/sdk/shared/src/main/scala/org/ergoplatform/sdk/JsonCodecs.scala @@ -130,6 +130,11 @@ trait JsonCodecs { ).asJson }) + /** + * JSON decoder for Header instance. Field "unparsedBytes" is optional for now, to preserve compatibility + * with clients using older JSON decoders (before node 5.0.23). Better to add an (empty) field anyway if possible. + * This field could become mandatory in future. + */ implicit val headerDecoder: Decoder[Header] = Decoder.instance({ cursor => for { version <- cursor.downField("version").as[Byte]