From 6c1264cc085bf18ec973e8c0b015157ae5f29ac5 Mon Sep 17 00:00:00 2001 From: Denys Zadorozhnyi Date: Wed, 13 Jul 2022 15:15:37 +0300 Subject: [PATCH 01/13] remove incorrect comment in tx serialization; --- .../src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala | 3 --- 1 file changed, 3 deletions(-) diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala index d67a71fa83..43481b39c1 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala @@ -121,9 +121,6 @@ object ErgoLikeTransactionSerializer extends SigmaSerializer[ErgoLikeTransaction w.putBytes(input.boxId) } // Serialize distinct ids of tokens in transaction outputs. - // This optimization is crucial to allow up to MaxTokens (== 255) in a box. - // Without it total size of all token ids 255 * 32 = 8160, - // way beyond MaxBoxSize (== 4K) val tokenIds = tx.outputCandidates.toColl .flatMap(box => box.additionalTokens.map(t => t._1)) From acb9544adf46dd380dde5e8dd1b81fc8b98ebc12 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 15 Aug 2022 13:08:02 +0300 Subject: [PATCH 02/13] Update README.md: added Acknowledgments --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index afda393b92..2fa7ba7a75 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,17 @@ libraryDependencies += "org.scorexfoundation" %% "sigma-state" % "4.0.3" | sigma-library | Implementation of graph IR nodes for Sigma types | | sigmastate | Implementation ErgoTree, Interpreter and cost estimation | +## Acknowledgments + +We thank JetBrains for [supporting](https://www.jetbrains.com/buy/opensource/) this project since 2021 by providing All Products Pack subscription. + + + +We thank YourKit for support of open source projects with its full-featured Java Profiler. +YourKit, LLC is the creator of YourKit Java Profiler +and YourKit .NET Profiler, +innovative and intelligent tools for profiling Java and .NET applications. + ## References - [Ergo Site](https://ergoplatform.org/en/) From 7f12a934079962a65c0a7a8fef480e772f7a6ae0 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 27 Sep 2022 21:46:17 +0300 Subject: [PATCH 03/13] Update CONTRIBUTING.md --- CONTRIBUTING.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ff928680e4..e6ab243abc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,13 @@ +## Building + +Clone the repository and then run tests. + +```shell +git clone git@github.com:ScorexFoundation/sigmastate-interpreter.git +cd sigmastate-interpreter +sbt test +``` + ## Releasing To publish release version to Sonatype, do the following: - make a tag with version number `vX.Y.Z` (used by `sbt-dynver` to set `version` in `build.sbt`); From 884ee94b8fdce22ebcfd86a3730caf6d10cbf941 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sat, 22 Oct 2022 18:10:36 +0200 Subject: [PATCH 04/13] tuple-in-reg-tests: property("Tuple in register test vector") --- .../org/ergoplatform/ErgoLikeTransactionSpec.scala | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/sigmastate/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala b/sigmastate/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala index 9c92c7b74f..157b81b222 100644 --- a/sigmastate/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala +++ b/sigmastate/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala @@ -3,11 +3,12 @@ package org.ergoplatform import org.ergoplatform.ErgoBox.TokenId import org.ergoplatform.settings.ErgoAlgos import scorex.crypto.hash.Digest32 +import scorex.util.encode.Base16 import scorex.util.{Random, ModifierId} import sigmastate.SCollection.SByteArray -import sigmastate.{SSigmaProp, SPair, SInt, TrivialProp, SType} +import sigmastate.{SSigmaProp, TrivialProp, SType, SPair, SInt} import sigmastate.Values._ -import sigmastate.interpreter.{ProverResult, ContextExtension} +import sigmastate.interpreter.{ContextExtension, ProverResult} import sigmastate.serialization.SigmaSerializer import sigmastate.eval._ import sigmastate.eval.Extensions._ @@ -300,4 +301,12 @@ class ErgoLikeTransactionSpec extends SigmaDslTesting { } } } + + property("Tuple in register test vector") { + val txId = "b5fd96c9f8c1ff436d609a225a12377c6873b890a145fc05f4099845b89315e5" + val txHex = "0278c93ee55eec066ec06290524dc01773440fbaff644bd181ab4334f97083863738644bf834de150b9abd077cf52ecb4892536ab6ad6ef8505fb932d9ca7b9fb211fc8294618723eb4909d4553f4d419762332f68865b1d703d00faae81c0e57683108c55647c5837a4fedfb52ffde04db74a8b4b77e37ab81afc3828c904862db6134e0f859ff5f62e4a5cb9da6b15ea1cd1a11dc932899d7b99e9073034ccd4e7c54fa9790d8ee356dce22ee151b044561c96000000038094ebdc031007040205c80105c8010500040004000e1f3faf2cb329f2e90d6d23b58d91bbb6c046aa143261cc21f52fbe2824bfcbf0d807d601e4c6a70408d602b2a5730000d603e4c6a70601d604e4c6a70856d605e4c6a70505d606e4c6a70705d60795720399c1a7c1720299c17202c1a7eb027201d1ededededededededed93c27202c2a793e4c672020408720193e4c6720205059572039d9c72057e8c7204010573019d9c72057e8c72040205730294e4c672020601720393e4c672020705720693e4c672020856720493e4c67202090ec5a7929c720672057207917207730395ef720393b1db630872027304d801d608b2db63087202730500ed938c7208017306938c7208027206d18f34000508cd030c8f9c4dc08f3c006fa85a47c9156dedbede000a8b764c6e374fd097e873ba0405dceeaa0401010564860202660263c0edcea7090008cd030c8f9c4dc08f3c006fa85a47c9156dedbede000a8b764c6e374fd097e873ba04d18f340000c0843d1005040004000e36100204a00b08cd0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ea02d192a39a8cc7a701730073011001020402d19683030193a38cc7b2a57300000193c2b2a57301007473027303830108cdeeac93b1a57304d18f340000" + val txBytes = Base16.decode(txHex).get + val tx = ErgoLikeTransactionSerializer.fromBytes(txBytes) + tx.id shouldBe txId + } } From 00b0d8685ccc35187fb1546a555ede6f9a8a5d96 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sat, 22 Oct 2022 19:44:32 +0200 Subject: [PATCH 05/13] v5.0.1-tuple-fix: rollback Tuple changes --- sigmastate/src/main/scala/sigmastate/Values.scala | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index 4667097dbe..8b5c0a13b7 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -899,10 +899,18 @@ object Values { * * @param items source collection of expressions */ - case class Tuple(items: IndexedSeq[Value[SType]]) extends Value[STuple] { + case class Tuple(items: IndexedSeq[Value[SType]]) + extends EvaluatedValue[STuple] with EvaluatedCollection[SAny.type, STuple] { override def companion = Tuple override lazy val tpe = STuple(items.map(_.tpe)) override def opType: SFunc = ??? + override def elementType: SAny.type = SAny + + override lazy val value = { + val xs = items.cast[EvaluatedValue[SAny.type]].map(_.value) + Colls.fromArray(xs.toArray(SAny.classTag.asInstanceOf[ClassTag[SAny.WrappedType]]))(RType.AnyType) + } + protected final override def eval(env: DataEnv)(implicit E: ErgoTreeEvaluator): Any = { // in v5.0 version we support only tuples of 2 elements to be equivalent with v4.x if (items.length != 2) From b7779052cec169c46e15c177647efe96fa64c5f7 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Sat, 22 Oct 2022 19:58:18 +0200 Subject: [PATCH 06/13] v5.0.1-tuple-fix: remove TODO and comment the change --- sigmastate/src/main/scala/sigmastate/Values.scala | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/Values.scala b/sigmastate/src/main/scala/sigmastate/Values.scala index 8b5c0a13b7..d42242ccae 100644 --- a/sigmastate/src/main/scala/sigmastate/Values.scala +++ b/sigmastate/src/main/scala/sigmastate/Values.scala @@ -890,8 +890,6 @@ object Values { def tpe = SBox } - // TODO refactor: only Constant make sense to inherit from EvaluatedValue - /** ErgoTree node which converts a collection of expressions into a tuple of data values * of different types. Each data value of the resulting collection is obtained by * evaluating the corresponding expression in `items`. All items may have different @@ -900,7 +898,8 @@ object Values { * @param items source collection of expressions */ case class Tuple(items: IndexedSeq[Value[SType]]) - extends EvaluatedValue[STuple] with EvaluatedCollection[SAny.type, STuple] { + extends EvaluatedValue[STuple] // note, this superclass is required as Tuple can be in a register + with EvaluatedCollection[SAny.type, STuple] { override def companion = Tuple override lazy val tpe = STuple(items.map(_.tpe)) override def opType: SFunc = ??? From efd15701c407e4633567b2c9147dd5bb80e4a320 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 24 Oct 2022 13:21:31 +0200 Subject: [PATCH 07/13] v5.0.1-tuple-fix: added property("getUIntExact vs getUInt().toInt") --- .../serialization/BlockValueSerializer.scala | 3 +- .../DeserializationResilience.scala | 72 +++++++++++++++++-- 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala index 7348156f96..2299723d74 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala @@ -26,8 +26,9 @@ case class BlockValueSerializer(cons: (IndexedSeq[BlockItem], Value[SType]) => V override def parse(r: SigmaByteReader): Value[SType] = { val itemsSize = r.getUIntExact - val values: IndexedSeq[BlockItem] = if (itemsSize == 0) + val values: IndexedSeq[BlockItem] = if (itemsSize == 0) { BlockItem.EmptySeq + } else { // HOTSPOT:: allocate new array only if it is not empty val buf = safeNewArray[BlockItem](itemsSize) diff --git a/sigmastate/src/test/scala/sigmastate/serialization/DeserializationResilience.scala b/sigmastate/src/test/scala/sigmastate/serialization/DeserializationResilience.scala index 9ae7d01ab8..cc5572b43d 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/DeserializationResilience.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/DeserializationResilience.scala @@ -7,19 +7,20 @@ import org.ergoplatform.{ErgoBoxCandidate, Outputs} import org.scalacheck.Gen import scalan.util.BenchmarkUtil import scorex.util.serialization.{Reader, VLQByteBufferReader} -import sigmastate.Values.{BlockValue, GetVarInt, IntConstant, SValue, SigmaBoolean, SigmaPropValue, Tuple, ValDef, ValUse} +import sigmastate.Values.{SValue, BlockValue, GetVarInt, SigmaBoolean, ValDef, ValUse, SigmaPropValue, Tuple, IntConstant} import sigmastate._ import sigmastate.eval.Extensions._ import sigmastate.eval._ -import sigmastate.helpers.{ErgoLikeContextTesting, ErgoLikeTestInterpreter, SigmaTestingCommons} -import sigmastate.interpreter.{ContextExtension, CostedProverResult, CryptoConstants} -import sigmastate.lang.exceptions.{DeserializeCallDepthExceeded, InvalidTypePrefix, ReaderPositionLimitExceeded, SerializerException} +import sigmastate.helpers.{ErgoLikeTestInterpreter, SigmaTestingCommons, ErgoLikeContextTesting} +import sigmastate.interpreter.{CryptoConstants, CostedProverResult, ContextExtension} +import sigmastate.lang.exceptions.{ReaderPositionLimitExceeded, InvalidTypePrefix, DeserializeCallDepthExceeded, SerializerException} import sigmastate.serialization.OpCodes._ -import sigmastate.utils.SigmaByteReader +import sigmastate.utils.{SigmaByteWriter, SigmaByteReader} import sigmastate.utxo.SizeOf import sigmastate.utils.Helpers._ import scala.collection.mutable +import scala.util.{Try, Success, Failure} class DeserializationResilience extends SerializationSpecification with SigmaTestingCommons with CrossVersionProps { @@ -308,5 +309,66 @@ class DeserializationResilience extends SerializationSpecification }) } + /** Because this method is called from many places it should always be called with `hint`. */ + protected def checkResult[B](res: Try[B], expectedRes: Try[B], hint: String): Unit = { + (res, expectedRes) match { + case (Failure(exception), Failure(expectedException)) => + withClue(hint) { + rootCause(exception).getClass shouldBe rootCause(expectedException).getClass + } + case _ => + val actual = rootCause(res) + if (actual != expectedRes) { + assert(false, s"$hint\nActual: $actual;\nExpected: $expectedRes\n") + } + } + } + def writeUInt(x: Long): Array[Byte] = { + val w = SigmaSerializer.startWriter() + val bytes = w.putUInt(x).toBytes + bytes + } + + def readToInt(bytes: Array[Byte]): Try[Int] = Try { + val r = SigmaSerializer.startReader(bytes) + r.getUInt().toInt + } + + def readToIntExact(bytes: Array[Byte]): Try[Int] = Try { + val r = SigmaSerializer.startReader(bytes) + r.getUIntExact + } + + property("getUIntExact vs getUInt().toInt") { + val intOverflow = Failure(new ArithmeticException("Int overflow")) + val cases = Table(("stored", "toInt", "toIntExact"), + (0L, Success(0), Success(0)), + (Int.MaxValue.toLong - 1, Success(Int.MaxValue - 1), Success(Int.MaxValue - 1)), + (Int.MaxValue.toLong, Success(Int.MaxValue), Success(Int.MaxValue)), + (Int.MaxValue.toLong + 1, Success(Int.MinValue), intOverflow), + (Int.MaxValue.toLong + 2, Success(Int.MinValue + 1), intOverflow), + (0xFFFFFFFFL, Success(-1), intOverflow) + ) + forAll(cases) { (x, res, resExact) => + val bytes = writeUInt(x) + checkResult(readToInt(bytes), res, "toInt") + checkResult(readToIntExact(bytes), resExact, "toIntExact") + } + + // check it is impossible to write negative value with reference implementation of serializer + // ALSO NOTE that VLQ encoded bytes are always interpreted as positive Long + // so the difference between getUIntExact vs getUInt().toInt boils down to how Int.MaxValue + // overflow is handled + assertExceptionThrown( + writeUInt(-1L), + exceptionLike[IllegalArgumentException]("-1 is out of unsigned int range") + ) + + val MaxUIntPlusOne = 0xFFFFFFFFL + 1 + assertExceptionThrown( + writeUInt(MaxUIntPlusOne), + exceptionLike[IllegalArgumentException](s"$MaxUIntPlusOne is out of unsigned int range") + ) + } } From 7da0f42597427664e838c365745cf5da79320ea4 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Mon, 24 Oct 2022 15:29:00 +0200 Subject: [PATCH 08/13] v5.0.1-tuple-fix: rollback breaking changes only --- .../org/ergoplatform/ErgoBoxCandidate.scala | 7 +++++- .../ergoplatform/ErgoLikeTransaction.scala | 3 +++ .../SigmaValidationSettingsSerializer.scala | 4 +++- .../main/scala/sigmastate/AvlTreeData.scala | 6 +++-- .../serialization/BlockValueSerializer.scala | 3 +++ .../ConstantPlaceholderSerializer.scala | 3 +++ .../serialization/DataSerializer.scala | 3 +++ .../serialization/ErgoTreeSerializer.scala | 12 ++++++++-- .../serialization/FuncValueSerializer.scala | 7 +++++- .../serialization/ValDefSerializer.scala | 4 +++- .../serialization/ValUseSerializer.scala | 4 +++- .../SigmaTransformerSerializer.scala | 3 +++ .../DeserializationResilience.scala | 23 ++++++++++++++++++- 13 files changed, 72 insertions(+), 10 deletions(-) diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala index 1b7638f233..354424de16 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala @@ -187,7 +187,9 @@ object ErgoBoxCandidate { r.positionLimit = r.position + ErgoBox.MaxBoxSize val value = r.getULong() // READ val tree = DefaultSerializer.deserializeErgoTree(r, SigmaSerializer.MaxPropositionSize) // READ - val creationHeight = r.getUIntExact // READ + val creationHeight = r.getUInt().toInt // READ + // Note, when creationHeight < 0 as a result of Int overflow nothing happens here + // and ErgoBoxCandidate with negative creation height is created val nTokens = r.getUByte() // READ val tokenIds = safeNewArray[Array[Byte]](nTokens) val tokenAmounts = safeNewArray[Long](nTokens) @@ -195,6 +197,9 @@ object ErgoBoxCandidate { val nDigests = digestsInTx.length cfor(0)(_ < nTokens, _ + 1) { i => val digestIndex = r.getUIntExact // READ + // NO-FORK: in v5.x getUIntExact throws Int overflow exception + // in v4.x r.getUInt().toInt is used and may return negative Int in which case + // the error below is thrown if (digestIndex < 0 || digestIndex >= nDigests) sys.error(s"failed to find token id with index $digestIndex") val amount = r.getULong() // READ diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala index 683602dcc8..f1c986fca1 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoLikeTransaction.scala @@ -162,6 +162,9 @@ object ErgoLikeTransactionSerializer extends SigmaSerializer[ErgoLikeTransaction // parse distinct ids of tokens in transaction outputs val tokensCount = r.getUIntExact + // NO-FORK: in v5.x getUIntExact may throw Int overflow exception + // in v4.x r.getUInt().toInt is used and may return negative Int instead of the overflow + // in which case the array allocation will throw NegativeArraySizeException val tokens = safeNewArray[Array[Byte]](tokensCount) cfor(0)(_ < tokensCount, _ + 1) { i => tokens(i) = r.getBytes(TokenId.size) diff --git a/sigmastate/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala b/sigmastate/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala index 2c66b05e71..4b6ef9083e 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala @@ -19,7 +19,9 @@ object SigmaValidationSettingsSerializer extends SigmaSerializer[SigmaValidation } override def parse(r: SigmaByteReader): SigmaValidationSettings = { - val nRules = r.getUIntExact + val nRules = r.getUInt().toInt + // Note, when nRules < 0 as a result of Int overflow, the loop is empty + // deserialization will likely fail elsewhere val parsed = (0 until nRules).map { _ => val ruleId = r.getUShort().toShortExact val status = RuleStatusSerializer.parse(r) diff --git a/sigmastate/src/main/scala/sigmastate/AvlTreeData.scala b/sigmastate/src/main/scala/sigmastate/AvlTreeData.scala index ae94121920..b8b897fb0b 100644 --- a/sigmastate/src/main/scala/sigmastate/AvlTreeData.scala +++ b/sigmastate/src/main/scala/sigmastate/AvlTreeData.scala @@ -92,8 +92,10 @@ object AvlTreeData { override def parse(r: SigmaByteReader): AvlTreeData = { val digest = r.getBytes(DigestSize) val tf = AvlTreeFlags(r.getByte()) - val keyLength = r.getUIntExact - val valueLengthOpt = r.getOption(r.getUIntExact) + val keyLength = r.getUInt().toInt + val valueLengthOpt = r.getOption(r.getUInt().toInt) + // Note, when keyLength and valueLengthOpt < 0 as a result of Int overflow, + // the deserializer succeeds with invalid AvlTreeData AvlTreeData(ADDigest @@ digest, tf, keyLength, valueLengthOpt) } } diff --git a/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala index 2299723d74..52c6137869 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/BlockValueSerializer.scala @@ -26,6 +26,9 @@ case class BlockValueSerializer(cons: (IndexedSeq[BlockItem], Value[SType]) => V override def parse(r: SigmaByteReader): Value[SType] = { val itemsSize = r.getUIntExact + // NO-FORK: in v5.x getUIntExact may throw Int overflow exception + // in v4.x r.getUInt().toInt is used and may return negative Int instead of the overflow + // in which case the array allocation will throw NegativeArraySizeException val values: IndexedSeq[BlockItem] = if (itemsSize == 0) { BlockItem.EmptySeq } diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ConstantPlaceholderSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ConstantPlaceholderSerializer.scala index c3b834d00e..803f17079f 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ConstantPlaceholderSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ConstantPlaceholderSerializer.scala @@ -15,6 +15,9 @@ case class ConstantPlaceholderSerializer(cons: (Int, SType) => Value[SType]) override def parse(r: SigmaByteReader): Value[SType] = { val id = r.getUIntExact + // NO-FORK: in v5.x getUIntExact may throw Int overflow exception + // in v4.x r.getUInt().toInt is used and may return negative Int instead of the overflow + // in which case the constantStore.get will throw ArrayIndexOutOfBoundsException val constant = r.constantStore.get(id) if (r.resolvePlaceholdersToConstants) constant diff --git a/sigmastate/src/main/scala/sigmastate/serialization/DataSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/DataSerializer.scala index 3071334c8d..1cff3961d3 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/DataSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/DataSerializer.scala @@ -100,6 +100,9 @@ object DataSerializer { case SLong => r.getLong() case SString => val size = r.getUIntExact + // NO-FORK: in v5.x getUIntExact may throw Int overflow exception + // in v4.x r.getUInt().toInt is used and may return negative Int instead of the overflow + // in which case the getBytes will throw NegativeArraySizeException val bytes = r.getBytes(size) new String(bytes, StandardCharsets.UTF_8) case SBigInt => diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala index 06b39bc4eb..1b4a2d4a17 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala @@ -207,7 +207,12 @@ class ErgoTreeSerializer { val header = r.getByte() CheckHeaderSizeBit(header) val sizeOpt = if (ErgoTree.hasSize(header)) { - Some(r.getUIntExact) + val size = r.getUInt().toInt + // Note, when size < 0 as a result of Int overflow nothing happens here and later + // when deserialization proceeds normally as sizeOpt is not used on this pass. + // However, when ValidationException is thrown in deserializeErgoTree this negative + // size value will lead to undefined behavior + Some(size) } else None (header, sizeOpt) @@ -221,7 +226,10 @@ class ErgoTreeSerializer { private def deserializeConstants(header: Byte, r: SigmaByteReader): IndexedSeq[Constant[SType]] = { val constants: IndexedSeq[Constant[SType]] = if (ErgoTree.isConstantSegregation(header)) { - val nConsts = r.getUIntExact + val nConsts = r.getUInt().toInt + // Note, when nConsts < 0 as a result of Int overflow, the empty seq is returned + // deserialization will succeed + if (nConsts > 0) { // HOTSPOT:: allocate new array only if it is not empty val res = safeNewArray[Constant[SType]](nConsts) diff --git a/sigmastate/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala index 9e84acddf5..a9748162b3 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala @@ -28,9 +28,14 @@ case class FuncValueSerializer(cons: (IndexedSeq[(Int, SType)], Value[SType]) => override def parse(r: SigmaByteReader): Value[SType] = { val argsSize = r.getUIntExact + // NO-FORK: in v5.x getUIntExact may throw Int overflow exception + // in v4.x r.getUInt().toInt is used and may return negative Int instead of the overflow + // in which case the array allocation will throw NegativeArraySizeException val args = safeNewArray[(Int, SType)](argsSize) cfor(0)(_ < argsSize, _ + 1) { i => - val id = r.getUIntExact + val id = r.getUInt().toInt + // Note, when id < 0 as a result of Int overflow, the r.valDefTypeStore(id) won't throw + // but this will likely fail elsewhere val tpe = r.getType() r.valDefTypeStore(id) = tpe args(i) = (id, tpe) diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ValDefSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ValDefSerializer.scala index 8a3394ee89..e5311365b6 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ValDefSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ValDefSerializer.scala @@ -30,7 +30,9 @@ case class ValDefSerializer(override val opDesc: ValueCompanion) extends ValueSe } override def parse(r: SigmaByteReader): Value[SType] = { - val id = r.getUIntExact + val id = r.getUInt().toInt + // Note, when id < 0 as a result of Int overflow, the r.valDefTypeStore(id) won't throw + // but this will likely fail elsewhere val tpeArgs: Seq[STypeVar] = opCode match { case FunDefCode => val nTpeArgs = r.getByte() diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ValUseSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ValUseSerializer.scala index 3daaad877f..bf1d198cd2 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ValUseSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ValUseSerializer.scala @@ -12,7 +12,9 @@ case class ValUseSerializer(cons: (Int, SType) => Value[SType]) extends ValueSer } override def parse(r: SigmaByteReader): Value[SType] = { - val id = r.getUIntExact + val id = r.getUInt().toInt + // Note, when id < 0 as a result of Int overflow, the r.valDefTypeStore(id) won't throw + // but this will likely fail elsewhere val tpe = r.valDefTypeStore(id) cons(id, tpe) } diff --git a/sigmastate/src/main/scala/sigmastate/serialization/transformers/SigmaTransformerSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/transformers/SigmaTransformerSerializer.scala index c8fe96e1bb..e6a1d9c264 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/transformers/SigmaTransformerSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/transformers/SigmaTransformerSerializer.scala @@ -19,6 +19,9 @@ case class SigmaTransformerSerializer[I <: SigmaPropValue, O <: SigmaPropValue] override def parse(r: SigmaByteReader): SigmaPropValue = { val itemsSize = r.getUIntExact + // NO-FORK: in v5.x getUIntExact may throw Int overflow exception + // in v4.x r.getUInt().toInt is used and may return negative Int instead of the overflow + // in which case the array allocation will throw NegativeArraySizeException val res = safeNewArray[SigmaPropValue](itemsSize) cfor(0)(_ < itemsSize, _ + 1) { i => res(i) = r.getValue().asInstanceOf[SigmaPropValue] diff --git a/sigmastate/src/test/scala/sigmastate/serialization/DeserializationResilience.scala b/sigmastate/src/test/scala/sigmastate/serialization/DeserializationResilience.scala index cc5572b43d..e4103a88fc 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/DeserializationResilience.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/DeserializationResilience.scala @@ -15,7 +15,8 @@ import sigmastate.helpers.{ErgoLikeTestInterpreter, SigmaTestingCommons, ErgoLik import sigmastate.interpreter.{CryptoConstants, CostedProverResult, ContextExtension} import sigmastate.lang.exceptions.{ReaderPositionLimitExceeded, InvalidTypePrefix, DeserializeCallDepthExceeded, SerializerException} import sigmastate.serialization.OpCodes._ -import sigmastate.utils.{SigmaByteWriter, SigmaByteReader} +import sigmastate.util.safeNewArray +import sigmastate.utils.SigmaByteReader import sigmastate.utxo.SizeOf import sigmastate.utils.Helpers._ @@ -371,4 +372,24 @@ class DeserializationResilience extends SerializationSpecification exceptionLike[IllegalArgumentException](s"$MaxUIntPlusOne is out of unsigned int range") ) } + + property("test assumptions of how negative value from getUInt().toInt is handled") { + assertExceptionThrown( + safeNewArray[Int](-1), + exceptionLike[NegativeArraySizeException]("-1")) + + val bytes = writeUInt(10) + val store = new ConstantStore(IndexedSeq(IntConstant(1))) + + val r = SigmaSerializer.startReader(bytes, store, true) + assertExceptionThrown( + r.constantStore.get(-1), + exceptionLike[ArrayIndexOutOfBoundsException]()) + + assertExceptionThrown( + r.getBytes(-1), + exceptionLike[NegativeArraySizeException]("-1")) + + r.valDefTypeStore(-1) = SAny // no exception on negative key + } } From 17e15dfb7999736c6165d9c872075b3c8efbd56d Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 25 Oct 2022 13:27:37 +0200 Subject: [PATCH 09/13] v5.0.1-tuple-fix: fixes after review --- .../org/ergoplatform/ErgoBoxCandidate.scala | 8 +++++--- .../SigmaValidationSettingsSerializer.scala | 2 +- .../main/scala/sigmastate/AvlTreeData.scala | 1 + .../serialization/ErgoTreeSerializer.scala | 10 +++++++++- .../serialization/FuncValueSerializer.scala | 3 ++- .../serialization/ValDefSerializer.scala | 8 +++++--- .../serialization/ValUseSerializer.scala | 7 +++++-- .../DeserializationResilience.scala | 20 ++++++++++++++++++- 8 files changed, 47 insertions(+), 12 deletions(-) diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala index 354424de16..bf13b8d9c8 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala @@ -187,9 +187,11 @@ object ErgoBoxCandidate { r.positionLimit = r.position + ErgoBox.MaxBoxSize val value = r.getULong() // READ val tree = DefaultSerializer.deserializeErgoTree(r, SigmaSerializer.MaxPropositionSize) // READ - val creationHeight = r.getUInt().toInt // READ - // Note, when creationHeight < 0 as a result of Int overflow nothing happens here - // and ErgoBoxCandidate with negative creation height is created + val creationHeight = r.getUIntExact // READ + // NO-FORK: ^ in v5.x getUIntExact may throw Int overflow exception + // in v4.x r.getUInt().toInt is used and may return negative Int instead of the overflow + // and ErgoBoxCandidate with negative creation height is created, which is then invalidated + // during transaction validation. See validation rule # 122 in the Ergo node (ValidationRules.scala) val nTokens = r.getUByte() // READ val tokenIds = safeNewArray[Array[Byte]](nTokens) val tokenAmounts = safeNewArray[Long](nTokens) diff --git a/sigmastate/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala b/sigmastate/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala index 4b6ef9083e..1e2b8b352a 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/validation/SigmaValidationSettingsSerializer.scala @@ -4,6 +4,7 @@ import sigmastate.serialization.SigmaSerializer import sigmastate.utils.{SigmaByteReader, SigmaByteWriter} import scalan.util.Extensions.{IntOps,LongOps} +// TODO v5.x: remove unused class and related json encoders /** The rules are serialized ordered by ruleId. * This serializer preserves roundtrip identity `deserialize(serialize(_)) = identity` * however it may not preserve `serialize(deserialize(_)) = identity` */ @@ -21,7 +22,6 @@ object SigmaValidationSettingsSerializer extends SigmaSerializer[SigmaValidation override def parse(r: SigmaByteReader): SigmaValidationSettings = { val nRules = r.getUInt().toInt // Note, when nRules < 0 as a result of Int overflow, the loop is empty - // deserialization will likely fail elsewhere val parsed = (0 until nRules).map { _ => val ruleId = r.getUShort().toShortExact val status = RuleStatusSerializer.parse(r) diff --git a/sigmastate/src/main/scala/sigmastate/AvlTreeData.scala b/sigmastate/src/main/scala/sigmastate/AvlTreeData.scala index b8b897fb0b..81aafa1a19 100644 --- a/sigmastate/src/main/scala/sigmastate/AvlTreeData.scala +++ b/sigmastate/src/main/scala/sigmastate/AvlTreeData.scala @@ -96,6 +96,7 @@ object AvlTreeData { val valueLengthOpt = r.getOption(r.getUInt().toInt) // Note, when keyLength and valueLengthOpt < 0 as a result of Int overflow, // the deserializer succeeds with invalid AvlTreeData + // but still some AvlTree operations (remove_eval, update_eval, contains_eval) won't throw AvlTreeData(ADDigest @@ digest, tf, keyLength, valueLengthOpt) } } diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala index 1b4a2d4a17..35d760b459 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ErgoTreeSerializer.scala @@ -211,7 +211,15 @@ class ErgoTreeSerializer { // Note, when size < 0 as a result of Int overflow nothing happens here and later // when deserialization proceeds normally as sizeOpt is not used on this pass. // However, when ValidationException is thrown in deserializeErgoTree this negative - // size value will lead to undefined behavior + // tree size value will be used in + // val val numBytes = bodyPos - startPos + treeSize + // r.position = startPos + // val bytes = r.getBytes(numBytes) = bodyPos - startPos + treeSize + // val bytes = r.getBytes(numBytes) + // If numBytes < 0 then it throws on getBytes and the whole deserialization fails + // On the other hand if numBytes >= 0 then UnparsedErgoTree will be created. + // The Reader however will be in some unpredictable state, as not all ErgoTree bytes + // are consumed. Some(size) } else None diff --git a/sigmastate/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala index a9748162b3..5fe2b481fe 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/FuncValueSerializer.scala @@ -35,7 +35,8 @@ case class FuncValueSerializer(cons: (IndexedSeq[(Int, SType)], Value[SType]) => cfor(0)(_ < argsSize, _ + 1) { i => val id = r.getUInt().toInt // Note, when id < 0 as a result of Int overflow, the r.valDefTypeStore(id) won't throw - // but this will likely fail elsewhere + // More over evaluation of such FuncValue will not throw either if the body contains + // ValUse with the same negative id val tpe = r.getType() r.valDefTypeStore(id) = tpe args(i) = (id, tpe) diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ValDefSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ValDefSerializer.scala index e5311365b6..8a7d8d37bb 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ValDefSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ValDefSerializer.scala @@ -30,9 +30,11 @@ case class ValDefSerializer(override val opDesc: ValueCompanion) extends ValueSe } override def parse(r: SigmaByteReader): Value[SType] = { - val id = r.getUInt().toInt - // Note, when id < 0 as a result of Int overflow, the r.valDefTypeStore(id) won't throw - // but this will likely fail elsewhere + val id = r.getUIntExact + // NO-FORK: in v5.x getUIntExact may throw Int overflow exception + // in v4.x r.getUInt().toInt is used and may return negative Int instead of the overflow + // When id < 0 as a result of Int overflow, the r.valDefTypeStore(id) won't throw + // but ValDef constructor fails on require(id >= 0, "id must be >= 0") val tpeArgs: Seq[STypeVar] = opCode match { case FunDefCode => val nTpeArgs = r.getByte() diff --git a/sigmastate/src/main/scala/sigmastate/serialization/ValUseSerializer.scala b/sigmastate/src/main/scala/sigmastate/serialization/ValUseSerializer.scala index bf1d198cd2..523139c3ab 100644 --- a/sigmastate/src/main/scala/sigmastate/serialization/ValUseSerializer.scala +++ b/sigmastate/src/main/scala/sigmastate/serialization/ValUseSerializer.scala @@ -12,9 +12,12 @@ case class ValUseSerializer(cons: (Int, SType) => Value[SType]) extends ValueSer } override def parse(r: SigmaByteReader): Value[SType] = { - val id = r.getUInt().toInt + val id = r.getUInt.toInt // Note, when id < 0 as a result of Int overflow, the r.valDefTypeStore(id) won't throw - // but this will likely fail elsewhere + // and also ValUse node will be created, but then its evaluation will throw (because + // there will be no ValDef with negative id in the env. + // However, in general, there is no guarantee that this ValUse will ever be executed + // as it may be in an `if` branch. val tpe = r.valDefTypeStore(id) cons(id, tpe) } diff --git a/sigmastate/src/test/scala/sigmastate/serialization/DeserializationResilience.scala b/sigmastate/src/test/scala/sigmastate/serialization/DeserializationResilience.scala index e4103a88fc..996bd8fcc9 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/DeserializationResilience.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/DeserializationResilience.scala @@ -6,6 +6,9 @@ import org.ergoplatform.validation.ValidationRules.CheckPositionLimit import org.ergoplatform.{ErgoBoxCandidate, Outputs} import org.scalacheck.Gen import scalan.util.BenchmarkUtil +import scorex.crypto.authds.{ADKey, ADValue} +import scorex.crypto.authds.avltree.batch.{BatchAVLProver, Insert} +import scorex.crypto.hash.{Blake2b256, Digest32} import scorex.util.serialization.{Reader, VLQByteBufferReader} import sigmastate.Values.{SValue, BlockValue, GetVarInt, SigmaBoolean, ValDef, ValUse, SigmaPropValue, Tuple, IntConstant} import sigmastate._ @@ -390,6 +393,21 @@ class DeserializationResilience extends SerializationSpecification r.getBytes(-1), exceptionLike[NegativeArraySizeException]("-1")) - r.valDefTypeStore(-1) = SAny // no exception on negative key + r.valDefTypeStore(-1) = SInt // no exception on negative key + + // the following example shows how far negative keyLength can go inside AvlTree operations + val avlProver = new BatchAVLProver[Digest32, Blake2b256.type](keyLength = 32, None) + val digest = avlProver.digest + val flags = AvlTreeFlags(true, false, false) + val treeData = new AvlTreeData(digest, flags, -1, None) + val tree = SigmaDsl.avlTree(treeData) + val k = Blake2b256.hash("1") + val v = k + avlProver.performOneOperation(Insert(ADKey @@ k, ADValue @@ v)) + val proof = avlProver.generateProof() + val verifier = tree.createVerifier(Colls.fromArray(proof)) + verifier.performOneOperation(Insert(ADKey @@ k, ADValue @@ v)).isFailure shouldBe true + // NOTE, even though performOneOperation fails, some AvlTree$ methods used in Interpreter + // (remove_eval, update_eval, contains_eval) won't throw, while others will. } } From db1e1037c9db1f7bc916870655e520dccd5d620e Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 25 Oct 2022 15:46:07 +0200 Subject: [PATCH 10/13] v5.0.1-tuple-fix: fixes tests --- .../src/test/scala/sigmastate/helpers/NegativeTesting.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sigmastate/src/test/scala/sigmastate/helpers/NegativeTesting.scala b/sigmastate/src/test/scala/sigmastate/helpers/NegativeTesting.scala index 60668c8c81..66875d878c 100644 --- a/sigmastate/src/test/scala/sigmastate/helpers/NegativeTesting.scala +++ b/sigmastate/src/test/scala/sigmastate/helpers/NegativeTesting.scala @@ -50,7 +50,7 @@ trait NegativeTesting extends Matchers { */ def exceptionLike[E <: Throwable : ClassTag] (msgParts: String*): Throwable => Boolean = { - case t: E => msgParts.forall(t.getMessage.contains(_)) + case t: E => msgParts.forall(part => t.getMessage != null && t.getMessage.contains(part)) case _ => false } From ad40a6ef353d44142236f896defb7246d9164324 Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Tue, 25 Oct 2022 20:27:27 +0200 Subject: [PATCH 11/13] v5.0.1-tuple-fix: fix tests (2) --- .../sigmastate/serialization/DeserializationResilience.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sigmastate/src/test/scala/sigmastate/serialization/DeserializationResilience.scala b/sigmastate/src/test/scala/sigmastate/serialization/DeserializationResilience.scala index 996bd8fcc9..0ee994e413 100644 --- a/sigmastate/src/test/scala/sigmastate/serialization/DeserializationResilience.scala +++ b/sigmastate/src/test/scala/sigmastate/serialization/DeserializationResilience.scala @@ -379,7 +379,7 @@ class DeserializationResilience extends SerializationSpecification property("test assumptions of how negative value from getUInt().toInt is handled") { assertExceptionThrown( safeNewArray[Int](-1), - exceptionLike[NegativeArraySizeException]("-1")) + exceptionLike[NegativeArraySizeException]()) val bytes = writeUInt(10) val store = new ConstantStore(IndexedSeq(IntConstant(1))) @@ -391,7 +391,7 @@ class DeserializationResilience extends SerializationSpecification assertExceptionThrown( r.getBytes(-1), - exceptionLike[NegativeArraySizeException]("-1")) + exceptionLike[NegativeArraySizeException]()) r.valDefTypeStore(-1) = SInt // no exception on negative key From e5e6554581975b23d35667e082d5c5a3d288eafb Mon Sep 17 00:00:00 2001 From: Benjamin Schulte Date: Thu, 3 Nov 2022 10:12:53 +0100 Subject: [PATCH 12/13] Fix ErgoBoxCandidate.tokens does not account for same token multiple times in array, closes #838 --- .../org/ergoplatform/ErgoBoxCandidate.scala | 21 ++++++++++++------- .../ErgoLikeTransactionSpec.scala | 2 ++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala index bf13b8d9c8..003bfbe977 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala @@ -3,7 +3,7 @@ package org.ergoplatform import java.util import org.ergoplatform.ErgoBox._ import org.ergoplatform.settings.ErgoAlgos -import scorex.util.{bytesToId, ModifierId} +import scorex.util.{ModifierId, bytesToId} import sigmastate.Values._ import sigmastate._ import sigmastate.SType.AnyOps @@ -16,7 +16,7 @@ import sigmastate.serialization.ErgoTreeSerializer.DefaultSerializer import sigmastate.util.safeNewArray import spire.syntax.all.cfor -import scala.collection.immutable +import scala.collection.{immutable, mutable} import scala.runtime.ScalaRunTime /** @@ -100,12 +100,17 @@ class ErgoBoxCandidate(val value: Long, s"tokens: (${additionalTokens.map(t => ErgoAlgos.encode(t._1) + ":" + t._2).toArray.mkString(", ")}), " + s"$additionalRegisters, creationHeight: $creationHeight)" - /** Additional tokens stored in the box. */ - lazy val tokens: Map[ModifierId, Long] = - additionalTokens - .toArray - .map(t => bytesToId(t._1) -> t._2) - .toMap + /** Additional tokens stored in the box, merged into a Map */ + lazy val tokens: Map[ModifierId, Long] = { + val merged = new mutable.HashMap[ModifierId, Long] + additionalTokens.foreach { + case (id, amount) => { + val mId = bytesToId(id) + merged.put(mId, java7.compat.Math.addExact(merged.getOrElse(mId, 0L), amount)) + } + } + merged.toMap + } } object ErgoBoxCandidate { diff --git a/sigmastate/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala b/sigmastate/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala index ab27179bec..5de774def8 100644 --- a/sigmastate/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala +++ b/sigmastate/src/test/scala/org/ergoplatform/ErgoLikeTransactionSpec.scala @@ -46,6 +46,7 @@ class ErgoLikeTransactionSpec extends SigmaDslTesting { 100, Coll( Digest32 @@ (ErgoAlgos.decodeUnsafe(token1)) -> 10000000L, + Digest32 @@ (ErgoAlgos.decodeUnsafe(token1)) -> 500L, Digest32 @@ (ErgoAlgos.decodeUnsafe(token2)) -> 500L ) ) @@ -70,6 +71,7 @@ class ErgoLikeTransactionSpec extends SigmaDslTesting { b1.tokens shouldBe expectedTokens b1_clone.tokens shouldBe expectedTokens b3.tokens shouldBe expectedTokens + b2.tokens shouldBe Map[ModifierId, Long](ModifierId @@ token1 -> 10000500L, ModifierId @@ token2 -> 500L) assertResult(true)(b1.hashCode() == b1.hashCode()) assertResult(true)(b1 == b1) From 0c2ba58b3d30ac168418d1f5eeda4c898e003d9b Mon Sep 17 00:00:00 2001 From: Alexander Slesarenko Date: Fri, 4 Nov 2022 09:52:47 +0100 Subject: [PATCH 13/13] fix-838: comments on consensus + formatting --- .../scala/org/ergoplatform/ErgoBoxCandidate.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala index 003bfbe977..9fa6cafaf0 100644 --- a/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala +++ b/sigmastate/src/main/scala/org/ergoplatform/ErgoBoxCandidate.scala @@ -100,14 +100,14 @@ class ErgoBoxCandidate(val value: Long, s"tokens: (${additionalTokens.map(t => ErgoAlgos.encode(t._1) + ":" + t._2).toArray.mkString(", ")}), " + s"$additionalRegisters, creationHeight: $creationHeight)" - /** Additional tokens stored in the box, merged into a Map */ + /** Additional tokens stored in the box, merged into a Map. + * This method is not used in ErgoTree and serialization, not part of consensus. + */ lazy val tokens: Map[ModifierId, Long] = { val merged = new mutable.HashMap[ModifierId, Long] - additionalTokens.foreach { - case (id, amount) => { - val mId = bytesToId(id) - merged.put(mId, java7.compat.Math.addExact(merged.getOrElse(mId, 0L), amount)) - } + additionalTokens.foreach { case (id, amount) => + val mId = bytesToId(id) + merged.put(mId, java7.compat.Math.addExact(merged.getOrElse(mId, 0L), amount)) } merged.toMap }