diff --git a/README.md b/README.md index 25839148c5..51e3b9070a 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ To run specific Ergo version `` as a service with custom config `/path/ -e MAX_HEAP=3G \ ergoplatform/ergo: -- -c /etc/myergo.conf -Available versions can be found on [Ergo Docker image page](https://hub.docker.com/r/ergoplatform/ergo/tags), for example, `v4.0.30`. +Available versions can be found on [Ergo Docker image page](https://hub.docker.com/r/ergoplatform/ergo/tags), for example, `v4.0.31`. This will connect to the Ergo mainnet or testnet following your configuration passed in `myergo.conf` and network flag `--`. Every default config value would be overwritten with corresponding value in `myergo.conf`. `MAX_HEAP` variable can be used to control how much memory can the node consume. diff --git a/ergo-wallet/src/main/scala/org/ergoplatform/contracts/ReemissionContracts.scala b/ergo-wallet/src/main/scala/org/ergoplatform/contracts/ReemissionContracts.scala index 0e1890f5f3..63070a1e9a 100644 --- a/ergo-wallet/src/main/scala/org/ergoplatform/contracts/ReemissionContracts.scala +++ b/ergo-wallet/src/main/scala/org/ergoplatform/contracts/ReemissionContracts.scala @@ -97,7 +97,7 @@ trait ReemissionContracts { val correctCoinsIssued = EQ(reemissionRewardPerBlock, Minus(ExtractAmount(Self), ExtractAmount(reemissionOut))) // when reemission contract box got merged with other boxes - val sponsored = { + val merging = { val feeOut = secondOut AND( GT(ExtractAmount(reemissionOut), ExtractAmount(Self)), @@ -110,7 +110,7 @@ trait ReemissionContracts { correctNftId, sameScriptRule, OR( - sponsored, + merging, AND( heightCorrect, correctMinerOutput, diff --git a/papers/emission.md b/papers/emission.md index 1127478cf5..0450143f7e 100644 --- a/papers/emission.md +++ b/papers/emission.md @@ -126,7 +126,7 @@ second one is to pay mining fee supposedly (its value can be 0.01 ERG at most) val correctCoinsIssued = EQ(reemissionRewardPerBlock, Minus(ExtractAmount(Self), ExtractAmount(reemissionOut))) // when reemission contract box got merged with other boxes - val sponsored = AND( + val merging = AND( GT(ExtractAmount(reemissionOut), ExtractAmount(Self)), LE(ExtractAmount(ByIndex(Outputs, IntConstant(1))), LongConstant(10000000)), // 0.01 ERG EQ(SizeOf(Outputs), 2) @@ -136,7 +136,7 @@ second one is to pay mining fee supposedly (its value can be 0.01 ERG at most) correctNftId, sameScriptRule, OR( - sponsored, + merging, AND( heightCorrect, correctMinerOutput, diff --git a/src/main/resources/api/openapi.yaml b/src/main/resources/api/openapi.yaml index b5eff10e35..063357144e 100644 --- a/src/main/resources/api/openapi.yaml +++ b/src/main/resources/api/openapi.yaml @@ -1,7 +1,7 @@ openapi: "3.0.2" info: - version: "4.0.30" + version: "4.0.31" title: Ergo Node API description: API docs for Ergo Node. Models are shared between all Ergo products contact: diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 5ecc67f077..97c81ad908 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -286,6 +286,13 @@ ergo { # Number of keys to be generated for tests # testKeysQty = 5 + # Whitelisted tokens, if non-null, the wallet will automatically burn non-whitelisted tokens from + # inputs when doing transactions. + # If tokensWhitelist = [], all the tokens will be burnt, + # tokensWhitelist = ["example"] means that all the tokens except of "example" will be burnt + # tokensWhitelist = null means no tokens burnt automatically + tokensWhitelist = null + # Enable this setting to handle re-emission tokens in the wallet properly, # e.g. doing transfers correctly in the presence of re-emission tokens checkEIP27 = false @@ -381,7 +388,7 @@ scorex { nodeName = "ergo-node" # Network protocol version to be sent in handshakes - appVersion = 4.0.30 + appVersion = 4.0.31 # Network agent name. May contain information about client code # stack, starting from core code-base up to the end graphical interface. diff --git a/src/main/resources/mainnet.conf b/src/main/resources/mainnet.conf index 63d67eaabf..a9dff18a81 100644 --- a/src/main/resources/mainnet.conf +++ b/src/main/resources/mainnet.conf @@ -65,7 +65,7 @@ scorex { network { magicBytes = [1, 0, 2, 4] bindAddress = "0.0.0.0:9030" - nodeName = "ergo-mainnet-4.0.30" + nodeName = "ergo-mainnet-4.0.31" nodeName = ${?NODENAME} knownPeers = [ "213.239.193.208:9030", diff --git a/src/main/resources/testnet.conf b/src/main/resources/testnet.conf index c99c8a8f06..dc8ae00ad9 100644 --- a/src/main/resources/testnet.conf +++ b/src/main/resources/testnet.conf @@ -77,7 +77,7 @@ scorex { network { magicBytes = [2, 0, 0, 2] bindAddress = "0.0.0.0:9020" - nodeName = "ergo-testnet-4.0.30" + nodeName = "ergo-testnet-4.0.31" nodeName = ${?NODENAME} knownPeers = [ "213.239.193.208:9020", diff --git a/src/main/scala/org/ergoplatform/local/ErgoStatsCollector.scala b/src/main/scala/org/ergoplatform/local/ErgoStatsCollector.scala index 2df3dd6148..f4297a2326 100644 --- a/src/main/scala/org/ergoplatform/local/ErgoStatsCollector.scala +++ b/src/main/scala/org/ergoplatform/local/ErgoStatsCollector.scala @@ -67,7 +67,7 @@ class ErgoStatsCollector(readersHolder: ActorRef, lastIncomingMessageTime = networkTime(), None, LaunchParameters, - eip27Supported = false) + eip27Supported = true) override def receive: Receive = onConnectedPeers orElse @@ -91,8 +91,7 @@ class ErgoStatsCollector(readersHolder: ActorRef, genesisBlockIdOpt = h.headerIdsAtHeight(ErgoHistory.GenesisHeight).headOption, stateRoot = Some(Algos.encode(s.rootHash)), stateVersion = Some(s.version), - parameters = s.stateContext.currentParameters, - eip27Supported = s.stateContext.eip27Supported + parameters = s.stateContext.currentParameters ) } @@ -108,7 +107,7 @@ class ErgoStatsCollector(readersHolder: ActorRef, private def onStateChanged: Receive = { case ChangedState(s: ErgoStateReader@unchecked) => val sc = s.stateContext - nodeInfo = nodeInfo.copy(parameters = sc.currentParameters, eip27Supported = sc.eip27Supported) + nodeInfo = nodeInfo.copy(parameters = sc.currentParameters) } private def onHistoryChanged: Receive = { diff --git a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala index 9622688859..d8fdbbfb88 100644 --- a/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala +++ b/src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala @@ -627,12 +627,8 @@ object CandidateGenerator extends ScorexLogging { // forming transaction collecting emission val reemissionSettings = chainSettings.reemission val reemissionRules = reemissionSettings.reemissionRules - val eip27Supported = stateContext.eip27Supported - val eip27ActivationHeight = if (eip27Supported) { - reemissionSettings.activationHeight - } else { - Int.MaxValue - } + + val eip27ActivationHeight = reemissionSettings.activationHeight val reemissionTokenId = Digest32 @@ reemissionSettings.reemissionTokenIdBytes val nextHeight = currentHeight + 1 diff --git a/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala b/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala index 02eee7640d..3aa7426204 100644 --- a/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala +++ b/src/main/scala/org/ergoplatform/modifiers/mempool/ErgoTransaction.scala @@ -217,105 +217,96 @@ case class ErgoTransaction(override val inputs: IndexedSeq[Input], // we check that we're in utxo mode, as eip27Supported flag available only in this mode // if we're in digest mode, skip validation // todo: this check could be removed after EIP-27 activation - if (stateContext.ergoSettings.nodeSettings.stateType.holdsUtxoSet) { - lazy val reemissionSettings = stateContext.ergoSettings.chainSettings.reemission - lazy val reemissionRules = reemissionSettings.reemissionRules + lazy val reemissionSettings = stateContext.ergoSettings.chainSettings.reemission + lazy val reemissionRules = reemissionSettings.reemissionRules - lazy val reemissionTokenId = ModifierId @@ reemissionSettings.reemissionTokenId - lazy val reemissionTokenIdBytes = reemissionSettings.reemissionTokenIdBytes + lazy val reemissionTokenId = ModifierId @@ reemissionSettings.reemissionTokenId + lazy val reemissionTokenIdBytes = reemissionSettings.reemissionTokenIdBytes - lazy val emissionNftId = ModifierId @@ reemissionSettings.emissionNftId - lazy val emissionNftIdBytes = reemissionSettings.emissionNftIdBytes + lazy val emissionNftId = ModifierId @@ reemissionSettings.emissionNftId + lazy val emissionNftIdBytes = reemissionSettings.emissionNftIdBytes - lazy val chainSettings = stateContext.ergoSettings.chainSettings - lazy val emissionRules = chainSettings.emissionRules + lazy val chainSettings = stateContext.ergoSettings.chainSettings + lazy val emissionRules = chainSettings.emissionRules - lazy val height = stateContext.currentHeight - lazy val eip27Supported = stateContext.eip27Supported + lazy val height = stateContext.currentHeight - // considering voting for eip27 done, via eip27Supported flag - val activationHeight = if (eip27Supported) { - reemissionSettings.activationHeight - } else { - Int.MaxValue - } + val activationHeight = reemissionSettings.activationHeight + + if (stateContext.currentHeight >= activationHeight) { + // reemission check logic below + var reemissionSpending = false + boxesToSpend.foreach { box => + // checking EIP-27 rules for emission box + // for efficiency, skip boxes with less than 100K ERG + if (box.value > 100000 * EmissionRules.CoinsInOneErgo) { + // on activation height, emissionNft is not in emission box yet, but in injection box + // injection box index (1) is enforced by injection box contract + if (box.tokens.contains(emissionNftId) || + (height == activationHeight && boxesToSpend(1).tokens.contains(emissionNftId))) { - if (stateContext.currentHeight >= activationHeight) { - // reemission check logic below - var reemissionSpending = false - boxesToSpend.foreach { box => - // checking EIP-27 rules for emission box - // for efficiency, skip boxes with less than 100K ERG - if (box.value > 100000 * EmissionRules.CoinsInOneErgo) { - // on activation height, emissionNft is not in emission box yet, but in injection box - if (box.tokens.contains(emissionNftId) || - (height == activationHeight && boxesToSpend(1).tokens.contains(emissionNftId))) { - - // if emission contract NFT is in the input, remission tokens should be there also - val reemissionTokensIn = if (height == activationHeight) { - boxesToSpend(1).tokens.getOrElse(reemissionTokenId, 0L) - } else { - box.tokens.getOrElse(reemissionTokenId, 0L) - } - require(reemissionTokensIn > 0, "No re-emission tokens in the emission or injection box") - - // output positions guaranteed by emission contract - val emissionOut = outputCandidates(0) - val rewardsOut = outputCandidates(1) - - // check positions of emission NFT and reemission token - val firstEmissionBoxTokenId = emissionOut.additionalTokens.apply(0)._1 - val secondEmissionBoxTokenId = emissionOut.additionalTokens.apply(1)._1 - require( - firstEmissionBoxTokenId.sameElements(emissionNftIdBytes), - "No emission box NFT in the emission box" - ) - require( - secondEmissionBoxTokenId.sameElements(reemissionTokenIdBytes), - "No re-emission token in the emission box" - ) - - //we're checking how emission box is paying reemission tokens below - val emissionTokensOut = emissionOut.tokens.getOrElse(reemissionTokenId, 0L) - val rewardsTokensOut = rewardsOut.tokens.getOrElse(reemissionTokenId, 0L) - require(reemissionTokensIn == emissionTokensOut + rewardsTokensOut, "Reemission tokens not preserved") - - val properReemissionRewardPart = reemissionRules.reemissionForHeight(height, emissionRules) - require(rewardsTokensOut == properReemissionRewardPart, "Rewards out condition violated") + // if emission contract NFT is in the input, remission tokens should be there also + val reemissionTokensIn = if (height == activationHeight) { + boxesToSpend(1).tokens.getOrElse(reemissionTokenId, 0L) } else { - //this path can be removed after EIP-27 activation - if (height >= activationHeight && box.ergoTree == chainSettings.monetary.emissionBoxProposition) { - //we require emission contract NFT and reemission token to be presented in emission output - val emissionOutTokens = outputCandidates(0).tokens - require(emissionOutTokens.contains(emissionNftId)) - require(emissionOutTokens.contains(reemissionTokenId)) - } + box.tokens.getOrElse(reemissionTokenId, 0L) + } + require(reemissionTokensIn > 0, "No re-emission tokens in the emission or injection box") + + // output positions guaranteed by emission contract + val emissionOut = outputCandidates(0) + val rewardsOut = outputCandidates(1) + + // check positions of emission NFT and reemission token + val firstEmissionBoxTokenId = emissionOut.additionalTokens.apply(0)._1 + val secondEmissionBoxTokenId = emissionOut.additionalTokens.apply(1)._1 + require( + firstEmissionBoxTokenId.sameElements(emissionNftIdBytes), + "No emission box NFT in the emission box" + ) + require( + secondEmissionBoxTokenId.sameElements(reemissionTokenIdBytes), + "No re-emission token in the emission box" + ) + + //we're checking how emission box is paying reemission tokens below + val emissionTokensOut = emissionOut.tokens.getOrElse(reemissionTokenId, 0L) + val rewardsTokensOut = rewardsOut.tokens.getOrElse(reemissionTokenId, 0L) + require(reemissionTokensIn == emissionTokensOut + rewardsTokensOut, "Reemission tokens not preserved") + + val properReemissionRewardPart = reemissionRules.reemissionForHeight(height, emissionRules) + require(rewardsTokensOut == properReemissionRewardPart, "Rewards out condition violated") + } else { + //this path can be removed after EIP-27 activation + if (height >= activationHeight && box.ergoTree == chainSettings.monetary.emissionBoxProposition) { + //we require emission contract NFT and reemission token to be presented in emission output + val emissionOutTokens = outputCandidates(0).tokens + require(emissionOutTokens.contains(emissionNftId)) + require(emissionOutTokens.contains(reemissionTokenId)) } - } else if (box.tokens.contains(reemissionTokenId) && height > activationHeight) { - // reemission tokens spent after EIP-27 activation - reemissionSpending = true } + } else if (box.tokens.contains(reemissionTokenId) && height > activationHeight) { + // reemission tokens spent after EIP-27 activation + reemissionSpending = true } + } - // if box with reemission tokens spent - if (reemissionSpending) { - val payToReemissionContract = reemissionRules.payToReemission - val toBurn = boxesToSpend.map { box => - box.tokens.getOrElse(reemissionTokenId, 0L) - }.sum - log.debug(s"Reemission tokens to burn: $toBurn") - val reemissionOutputs = outputCandidates.filter { out => - require(!out.tokens.contains(reemissionTokenId), "outputs contain reemission token") - out.ergoTree == payToReemissionContract - } - val sentToReemission = reemissionOutputs.map(_.value).sum - require(sentToReemission == toBurn, "Burning condition violated") + // if box with reemission tokens spent + if (reemissionSpending) { + val payToReemissionContract = reemissionRules.payToReemission + val toBurn = boxesToSpend.map { box => + box.tokens.getOrElse(reemissionTokenId, 0L) + }.sum + log.debug(s"Reemission tokens to burn: $toBurn") + val reemissionOutputs = outputCandidates.filter { out => + require(!out.tokens.contains(reemissionTokenId), "outputs contain reemission token") + out.ergoTree == payToReemissionContract } - } else { - Success(()) + val sentToReemission = reemissionOutputs.map(_.value).sum + require(sentToReemission == toBurn, "Burning condition violated") } } else { - log.warn("Checking EIP-27 in digest mode") + Success(()) } } } diff --git a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala index 1b9911c7f0..bcd446b0f4 100644 --- a/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala +++ b/src/main/scala/org/ergoplatform/nodeView/ErgoNodeViewHolder.scala @@ -383,8 +383,7 @@ abstract class ErgoNodeViewHolder[State <: ErgoState[State]](settings: ErgoSetti val constants = StateConstants(settings) restoreConsistentState(ErgoState.readOrGenerate(settings, constants).asInstanceOf[State], history) match { case Success(state) => - log.info(s"State database read, state synchronized, " + - s"eip27 supported == ${state.stateContext.eip27Supported}") + log.info(s"State database read, state synchronized") val wallet = ErgoWallet.readOrGenerate( history.getReader.asInstanceOf[ErgoHistoryReader], settings, diff --git a/src/main/scala/org/ergoplatform/nodeView/state/ErgoStateContext.scala b/src/main/scala/org/ergoplatform/nodeView/state/ErgoStateContext.scala index 12ec1498ec..aa8f6af706 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/ErgoStateContext.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/ErgoStateContext.scala @@ -32,10 +32,9 @@ case class UpcomingStateContext(override val lastHeaders: Seq[Header], override val genesisStateDigest: ADDigest, override val currentParameters: Parameters, override val validationSettings: ErgoValidationSettings, - override val votingData: VotingData, - override val eip27Supported: Boolean)(implicit ergoSettings: ErgoSettings) + override val votingData: VotingData)(implicit ergoSettings: ErgoSettings) extends ErgoStateContext(lastHeaders, lastExtensionOpt, genesisStateDigest, currentParameters, - validationSettings, votingData, eip27Supported)(ergoSettings) { + validationSettings, votingData)(ergoSettings) { override def sigmaPreHeader: special.sigma.PreHeader = PreHeader.toSigma(predictedHeader) @@ -55,15 +54,13 @@ case class UpcomingStateContext(override val lastHeaders: Seq[Header], * @param genesisStateDigest - genesis state digest (before the very first block) * @param currentParameters - parameters at the beginning of the current voting epoch * @param votingData - votes for parameters change within the current voting epoch - * @param eip27Supported - whether a voting epoch before indicated support for EIP-27 */ class ErgoStateContext(val lastHeaders: Seq[Header], val lastExtensionOpt: Option[Extension], val genesisStateDigest: ADDigest, val currentParameters: Parameters, val validationSettings: ErgoValidationSettings, - val votingData: VotingData, - val eip27Supported: Boolean) + val votingData: VotingData) (implicit val ergoSettings: ErgoSettings) extends ErgoLikeStateContext with BytesSerializable @@ -120,7 +117,7 @@ class ErgoStateContext(val lastHeaders: Seq[Header], val (calculatedParams, updated) = currentParameters.update(height, forkVote, votingData.epochVotes, proposedUpdate, votingSettings) val calculatedValidationSettings = validationSettings.updated(updated) UpcomingStateContext(lastHeaders, lastExtensionOpt, upcomingHeader, genesisStateDigest, calculatedParams, - calculatedValidationSettings, votingData, eip27Supported) + calculatedValidationSettings, votingData) } protected def checkForkVote(height: Height): Unit = { @@ -137,40 +134,6 @@ class ErgoStateContext(val lastHeaders: Seq[Header], } } - /** - * Helper method to decide whether enough support (at least ~90% mining hashpower support) for EIP-27 was expressed - * before. Called at the beginning of each voting epoch. - */ - private def updateEip27Supported(epochVotes: Seq[(Byte, Int)], - chainSettings: ChainSettings, - height: Height): Boolean = { - val votingSettings = chainSettings.voting - val eip27ActivationHeight = chainSettings.reemission.activationHeight - - if (this.eip27Supported) { - true - } else if (height < eip27ActivationHeight) { - // about 90% for large enough epochs, 888 for the mainnet. - val threshold = if (votingSettings.votingLength == 1024) { - 888 - } else if (votingSettings.votingLength < 10) { - // used in tests only - votingSettings.votingLength - } else { - votingSettings.votingLength / 10 * 9 - } - val eip27Votes = epochVotes.find(_._1 == ErgoStateContext.eip27Vote).map(_._2).getOrElse(0) - log.warn(s"Votes for EIP-27 collected: $eip27Votes , height: $height") - if (eip27Votes >= threshold) { - true - } else { - false - } - } else { - false // this.eip27Supported value - } - } - /** * Called at the beginning of the epoch. * Extracts parameters and validationSettings from `Extension` and compares them to locally calculated once. @@ -237,16 +200,15 @@ class ErgoStateContext(val lastHeaders: Seq[Header], val extractedValidationSettings = processed._2 val proposedVotes = votes.map(_ -> 1) val newVoting = VotingData(proposedVotes) - val eip27Supported = updateEip27Supported(votingData.epochVotes, ergoSettings.chainSettings, height) new ErgoStateContext(newHeaders, extensionOpt, genesisStateDigest, params, - extractedValidationSettings, newVoting, eip27Supported)(ergoSettings) + extractedValidationSettings, newVoting)(ergoSettings) } case _ => val newVotes = votes val newVotingResults = newVotes.foldLeft(votingData) { case (v, id) => v.update(id) } state.result.toTry.map { _ => new ErgoStateContext(newHeaders, extensionOpt, genesisStateDigest, currentParameters, validationSettings, - newVotingResults, eip27Supported)(ergoSettings) + newVotingResults)(ergoSettings) } } }.flatten @@ -357,7 +319,7 @@ object ErgoStateContext { */ def empty(genesisStateDigest: ADDigest, settings: ErgoSettings, parameters: Parameters): ErgoStateContext = { new ErgoStateContext(Seq.empty, None, genesisStateDigest, parameters, ErgoValidationSettings.initial, - VotingData.empty, eip27Supported = false)(settings) + VotingData.empty)(settings) } /** @@ -375,7 +337,7 @@ object ErgoStateContext { Parameters.parseExtension(currentHeader.height, extension).flatMap { params => ErgoValidationSettings.parseExtension(extension).map { validationSettings => new ErgoStateContext(lastHeaders.reverse, Some(extension), genesisStateDigest, params, - validationSettings, VotingData.empty, false)(settings) + validationSettings, VotingData.empty)(settings) } } } else { @@ -403,18 +365,9 @@ case class ErgoStateContextSerializer(ergoSettings: ErgoSettings) extends Scorex ParametersSerializer.serialize(esc.currentParameters, w) ErgoValidationSettingsSerializer.serialize(esc.validationSettings, w) - // serialization hack to encode EIP-27 support (lock-in) flag along with extension availability in a single byte - // to have the same serialization format for nodes before EIP-27 implementation and after - val eip27AndExtensionSize = { - val lastExtensionSize = esc.lastExtensionOpt.size // 0 or 1 - val eip27Support = if (esc.eip27Supported) { - Eip27SupportValue - } else { - 0 - } - eip27Support + lastExtensionSize - } - w.putUByte(eip27AndExtensionSize) + val lastExtensionSize = esc.lastExtensionOpt.size // 0 or 1 + + w.putUByte(lastExtensionSize) esc.lastExtensionOpt.foreach(e => ExtensionSerializer.serialize(e.toExtension(Header.GenesisParentId), w)) } @@ -427,11 +380,10 @@ case class ErgoStateContextSerializer(ergoSettings: ErgoSettings) extends Scorex val validationSettings = ErgoValidationSettingsSerializer.parse(r) var lastExtensionOpt: Option[Extension] = None - var eip27Supported = false var eip27AndExtensionSize = r.getUByte() if (eip27AndExtensionSize >= Eip27SupportValue) { - eip27Supported = true + // we do not store EIP-27 flag into db anymore, but we could read old db with it eip27AndExtensionSize = eip27AndExtensionSize - Eip27SupportValue } if (eip27AndExtensionSize == 1) { @@ -439,7 +391,7 @@ case class ErgoStateContextSerializer(ergoSettings: ErgoSettings) extends Scorex } new ErgoStateContext(lastHeaders, lastExtensionOpt, genesisDigest, params, validationSettings, - votingData, eip27Supported)(ergoSettings) + votingData)(ergoSettings) } } diff --git a/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala b/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala index 579c5da502..20c3f522ff 100644 --- a/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala +++ b/src/main/scala/org/ergoplatform/nodeView/state/UtxoStateReader.scala @@ -1,6 +1,7 @@ package org.ergoplatform.nodeView.state import org.ergoplatform.ErgoBox +import org.ergoplatform.mining.emission.EmissionRules import org.ergoplatform.modifiers.ErgoFullBlock import org.ergoplatform.modifiers.mempool.ErgoTransaction import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader @@ -79,21 +80,35 @@ trait UtxoStateReader extends ErgoStateReader with TransactionValidation { * @param fb - ergo full block * @return emission box from this block transactions */ - protected[state] def extractEmissionBox(fb: ErgoFullBlock): Option[ErgoBox] = emissionBoxIdOpt match { - case Some(id) => - fb.blockTransactions.txs.view.reverse.find(_.inputs.exists(t => java.util.Arrays.equals(t.boxId, id))) match { - case Some(tx) if tx.outputs.head.ergoTree == constants.settings.chainSettings.monetary.emissionBoxProposition => - tx.outputs.headOption - case Some(_) => - log.info(s"Last possible emission box consumed") - None - case None => - log.warn(s"Emission box not found in block ${fb.encodedId}") - boxById(id) - } - case None => - log.debug("No emission box: emission should be already finished before this block") - None + // todo: search by emission box NFT after EIP-27 activation height, https://github.com/ergoplatform/ergo/issues/1718 + protected[state] def extractEmissionBox(fb: ErgoFullBlock): Option[ErgoBox] = { + def fullSearch(fb: ErgoFullBlock): Option[ErgoBox] = { + fb.transactions + .find(_.outputs.head.ergoTree == constants.settings.chainSettings.monetary.emissionBoxProposition) + .map(_.outputs.head) + .filter(_.value > 100000 * EmissionRules.CoinsInOneErgo) // to filter out possible spam + } + + emissionBoxIdOpt match { + case Some(id) => + fb.blockTransactions.txs.view.reverse.find(_.inputs.exists(t => java.util.Arrays.equals(t.boxId, id))) match { + case Some(tx) if tx.outputs.head.ergoTree == constants.settings.chainSettings.monetary.emissionBoxProposition => + tx.outputs.headOption + case Some(_) => + log.info(s"Last possible emission box consumed") + None + case None => + log.warn(s"Emission box possibly not spent in block ${fb.encodedId}") + boxById(id) match { + case s: Some[ErgoBox] => s + case None => fullSearch(fb) + } + + } + case None => + log.debug("No emission box: emission should be already finished before this block") + fullSearch(fb) + } } protected def emissionBoxIdOpt: Option[ADKey] = store.get(UtxoState.EmissionBoxIdKey).map(s => ADKey @@ s) diff --git a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSupport.scala b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSupport.scala index 46e3a20784..23c77cf8f1 100644 --- a/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSupport.scala +++ b/src/main/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSupport.scala @@ -11,7 +11,7 @@ import org.ergoplatform.settings.ErgoSettings import org.ergoplatform.nodeView.wallet.requests._ import org.ergoplatform.settings.Parameters import org.ergoplatform.utils.BoxUtils -import org.ergoplatform.wallet.{AssetUtils, Constants} +import org.ergoplatform.wallet.{AssetUtils, Constants, TokensMap} import org.ergoplatform.wallet.interface4j.SecretString import org.ergoplatform.wallet.Constants.PaymentsScanId import org.ergoplatform.wallet.boxes.BoxSelector.BoxSelectionResult @@ -99,6 +99,30 @@ trait ErgoWalletSupport extends ScorexLogging { } } + // merge tokens from burn request with auto-burn mechanism + private def mergeBurnWhitelistTokens(state: ErgoWalletState, + inputBoxes: Seq[TrackedBox], + burnTokensRequestMap: TokensMap): TokensMap = { + val input = inputBoxes.flatMap(_.tokens) + state.walletVars.settings.walletSettings.tokensWhitelist match { + case Some(x: Seq[String]) if x.isEmpty => + AssetUtils.mergeAssets( + TransactionBuilder.collTokensToMap( + input.map { case (id, amt) => (IdUtils.decodedTokenId(id), amt) }.toColl + ), + burnTokensRequestMap) + case Some(x: Seq[String]) => AssetUtils.mergeAssets( + TransactionBuilder.collTokensToMap( + input + .filterNot(tMap => x.contains(tMap._1)) + .map { case (id, amt) => (IdUtils.decodedTokenId(id), amt) }.toColl + ), + burnTokensRequestMap) + case None => + burnTokensRequestMap + } + } + protected def processUnlock(state: ErgoWalletState, masterKey: ExtendedSecretKey, usePreEip3Derivation: Boolean): Try[ErgoWalletState] = { @@ -280,12 +304,14 @@ trait ErgoWalletSupport extends ScorexLogging { //filter burnTokens requests val (requestsWithBurnTokens, requestsWithoutBurnTokens) = requests.partition(_.isInstanceOf[BurnTokensRequest]) - val burnTokensMap = TransactionBuilder.collTokensToMap( + val burnTokensRequestMap = TransactionBuilder.collTokensToMap( requestsWithBurnTokens .map(_.asInstanceOf[BurnTokensRequest]) .flatMap(_.assetsToBurn) .toColl ) + //filter out tokens on whitelist from wallet and merge the rest with burnTokens from requests + val burnTokensMap = mergeBurnWhitelistTokens(state, inputBoxes, burnTokensRequestMap) //We're getting id of the first input, it will be used in case of asset issuance (asset id == first input id) requestsToBoxCandidates(requestsWithoutBurnTokens, inputBoxes.head.box.id, state.fullHeight, state.parameters, state.walletVars.publicKeyAddresses) diff --git a/src/main/scala/org/ergoplatform/settings/ChainSettings.scala b/src/main/scala/org/ergoplatform/settings/ChainSettings.scala index 7bfcc8d2cd..b07eaf4130 100644 --- a/src/main/scala/org/ergoplatform/settings/ChainSettings.scala +++ b/src/main/scala/org/ergoplatform/settings/ChainSettings.scala @@ -30,6 +30,8 @@ case class ChainSettings(protocolVersion: Byte, initialDifficultyHex: String, genesisId: Option[ModifierId] = None) { + val isMainnet: Boolean = addressPrefix == ErgoAddressEncoder.MainnetNetworkPrefix + val genesisStateDigest: ADDigest = Base16.decode(genesisStateDigestHex) .fold(_ => throw new Error(s"Failed to parse genesisStateDigestHex = $genesisStateDigestHex"), ADDigest @@ _) diff --git a/src/main/scala/org/ergoplatform/settings/ErgoSettings.scala b/src/main/scala/org/ergoplatform/settings/ErgoSettings.scala index 185c2ea294..c5a7f9728c 100644 --- a/src/main/scala/org/ergoplatform/settings/ErgoSettings.scala +++ b/src/main/scala/org/ergoplatform/settings/ErgoSettings.scala @@ -188,9 +188,10 @@ object ErgoSettings extends ScorexLogging } else if (desiredNetworkTypeOpt.exists(_ != settings.networkType)) { failWithError(s"Malformed network config. Desired networkType is `${desiredNetworkTypeOpt.get}`, " + s"but one declared in config is `${settings.networkType}`") - } else if(!settings.nodeSettings.stateType.holdsUtxoSet && - settings.chainSettings.reemission.checkReemissionRules) { - failWithError("EIP-27 rules can be checked only in UTXO mode") + } else if(settings.networkType.isMainNet && + settings.nodeSettings.mining && + !settings.chainSettings.reemission.checkReemissionRules) { + failWithError(s"Mining is enabled, but chain.reemission.checkReemissionRules = false , set it to true") } else { settings } diff --git a/src/main/scala/org/ergoplatform/settings/WalletSettings.scala b/src/main/scala/org/ergoplatform/settings/WalletSettings.scala index 3c431aa06e..391cdd10ca 100644 --- a/src/main/scala/org/ergoplatform/settings/WalletSettings.scala +++ b/src/main/scala/org/ergoplatform/settings/WalletSettings.scala @@ -2,7 +2,6 @@ package org.ergoplatform.settings import org.ergoplatform.wallet.settings.SecretStorageSettings - case class WalletSettings(secretStorage: SecretStorageSettings, seedStrengthBits: Int, mnemonicPhraseLanguage: String, @@ -14,4 +13,6 @@ case class WalletSettings(secretStorage: SecretStorageSettings, optimalInputs: Int = 3, testMnemonic: Option[String] = None, testKeysQty: Option[Int] = None, - checkEIP27: Boolean = false) + // Some(Seq(x)) burns all except x, Some(Seq.empty) burns all, None ignores that feature + tokensWhitelist: Option[Seq[String]] = None, + checkEIP27: Boolean = false) \ No newline at end of file diff --git a/src/main/scala/scorex/core/settings/SettingsReaders.scala b/src/main/scala/scorex/core/settings/SettingsReaders.scala index 4b5f9b0d1c..f2167b6c57 100644 --- a/src/main/scala/scorex/core/settings/SettingsReaders.scala +++ b/src/main/scala/scorex/core/settings/SettingsReaders.scala @@ -2,16 +2,14 @@ package scorex.core.settings import java.io.File import java.net.InetSocketAddress - import com.typesafe.config.Config import net.ceedubs.ficus.readers.ValueReader trait SettingsReaders { - implicit val fileReader: ValueReader[File] = (cfg, path) => new File(cfg.getString(path)) implicit val byteValueReader: ValueReader[Byte] = (cfg, path) => cfg.getInt(path).toByte implicit val inetSocketAddressReader: ValueReader[InetSocketAddress] = { (config: Config, path: String) => val split = config.getString(path).split(":") new InetSocketAddress(split(0), split(1).toInt) } -} \ No newline at end of file +} diff --git a/src/test/scala/org/ergoplatform/http/routes/InfoApiRoutesSpec.scala b/src/test/scala/org/ergoplatform/http/routes/InfoApiRoutesSpec.scala index 2d1655b9a6..774d6ae4e2 100644 --- a/src/test/scala/org/ergoplatform/http/routes/InfoApiRoutesSpec.scala +++ b/src/test/scala/org/ergoplatform/http/routes/InfoApiRoutesSpec.scala @@ -58,7 +58,7 @@ class InfoApiRoutesSpec extends AnyFlatSpec c.downField("stateType").as[String] shouldEqual Right(settings.nodeSettings.stateType.stateTypeName) c.downField("isMining").as[Boolean] shouldEqual Right(settings.nodeSettings.mining) c.downField("launchTime").as[Long] shouldEqual Right(fakeTimeProvider.time()) - c.downField("eip27Supported").as[Boolean] shouldEqual Right(false) + c.downField("eip27Supported").as[Boolean] shouldEqual Right(true) } } diff --git a/src/test/scala/org/ergoplatform/modifiers/mempool/ErgoTransactionSpec.scala b/src/test/scala/org/ergoplatform/modifiers/mempool/ErgoTransactionSpec.scala index e5b7426635..22bc0783e9 100644 --- a/src/test/scala/org/ergoplatform/modifiers/mempool/ErgoTransactionSpec.scala +++ b/src/test/scala/org/ergoplatform/modifiers/mempool/ErgoTransactionSpec.scala @@ -319,7 +319,7 @@ class ErgoTransactionSpec extends ErgoPropertyTest with ErgoTestConstants { import Parameters._ val ps = Parameters(0, DefaultParameters.updated(MaxBlockCostIncrease, Int.MaxValue), emptyVSUpdate) val sc = new ErgoStateContext(Seq.empty, None, genesisStateDigest, ps, ErgoValidationSettings.initial, - VotingData.empty, false)(settings) + VotingData.empty)(settings) .upcoming(org.ergoplatform.mining.group.generator, 0L, settings.chainSettings.initialNBits, diff --git a/src/test/scala/org/ergoplatform/modifiers/mempool/ExpirationSpecification.scala b/src/test/scala/org/ergoplatform/modifiers/mempool/ExpirationSpecification.scala index b3f5b8cd82..ae85bade3b 100644 --- a/src/test/scala/org/ergoplatform/modifiers/mempool/ExpirationSpecification.scala +++ b/src/test/scala/org/ergoplatform/modifiers/mempool/ExpirationSpecification.scala @@ -48,7 +48,7 @@ class ExpirationSpecification extends ErgoPropertyTest { val updContext = { val inContext = new ErgoStateContext(Seq(fakeHeader), None, genesisStateDigest, parameters, validationSettingsNoIl, - VotingData.empty, false)(settings) + VotingData.empty)(settings) inContext.appendFullBlock(fb).get } diff --git a/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala b/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala index 202338314a..9e037343f4 100644 --- a/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala +++ b/src/test/scala/org/ergoplatform/nodeView/wallet/ErgoWalletSpec.scala @@ -8,6 +8,7 @@ import org.ergoplatform.nodeView.wallet.persistence.{WalletDigest, WalletDigestS import org.ergoplatform.nodeView.wallet.requests.{AssetIssueRequest, BurnTokensRequest, ExternalSecret, PaymentRequest} import org.ergoplatform.settings.{Algos, Constants} import org.ergoplatform.utils._ +import org.ergoplatform.utils.fixtures.WalletFixture import org.ergoplatform.wallet.interpreter.{ErgoInterpreter, TransactionHintsBag} import scorex.util.encode.Base16 import sigmastate.eval._ @@ -18,6 +19,7 @@ import org.ergoplatform.wallet.boxes.ErgoBoxSerializer import org.ergoplatform.wallet.secrets.PrimitiveSecretKey import org.scalacheck.Gen import org.scalatest.concurrent.Eventually +import scorex.crypto.hash.Digest32 import scorex.util.ModifierId import sigmastate.{CAND, CTHRESHOLD} import sigmastate.basics.DLogProtocol.DLogProverInput @@ -30,7 +32,7 @@ class ErgoWalletSpec extends ErgoPropertyTest with WalletTestOps with Eventually property("assets in WalletDigest are deterministic against serialization") { forAll(Gen.listOfN(5, assetGen)) { preAssets => - val assets = preAssets.map{ case (id, amt) => ModifierId @@ Algos.encode(id) -> amt } + val assets = preAssets.map { case (id, amt) => ModifierId @@ Algos.encode(id) -> amt } val wd0 = WalletDigest(1, 0, assets) val bs = WalletDigestSerializer.toBytes(wd0) WalletDigestSerializer.parseBytes(bs).walletAssetBalances shouldBe wd0.walletAssetBalances @@ -57,7 +59,7 @@ class ErgoWalletSpec extends ErgoPropertyTest with WalletTestOps with Eventually log.info(s"Payment request $req") val tx = await(wallet.generateTransaction(req)).get log.info(s"Generated transaction $tx") - val context = new ErgoStateContext(Seq(genesisBlock.header), Some(genesisBlock.extension), startDigest, parameters, validationSettingsNoIl, VotingData.empty, false) + val context = new ErgoStateContext(Seq(genesisBlock.header), Some(genesisBlock.extension), startDigest, parameters, validationSettingsNoIl, VotingData.empty) val boxesToSpend = tx.inputs.map(i => genesisTx.outputs.find(o => java.util.Arrays.equals(o.id, i.boxId)).get) tx.statefulValidity(boxesToSpend, emptyDataBoxes, context) shouldBe 'success val block = makeNextBlock(getUtxoState, Seq(tx)) @@ -112,8 +114,7 @@ class ErgoWalletSpec extends ErgoPropertyTest with WalletTestOps with Eventually startDigest, parameters, validationSettingsNoIl, - VotingData.empty, - false) + VotingData.empty) val boxesToSpend = tx.inputs.map(i => genesisTx.outputs.find(o => java.util.Arrays.equals(o.id, i.boxId)).get) tx.statefulValidity(boxesToSpend, emptyDataBoxes, context) shouldBe 'success } @@ -212,7 +213,7 @@ class ErgoWalletSpec extends ErgoPropertyTest with WalletTestOps with Eventually implicit val patienceConfig: PatienceConfig = PatienceConfig(5.second, 300.millis) eventually { val confirmedBalance = getConfirmedBalances.walletBalance - + log.error(s"Confirmed balance $confirmedBalance") //pay out all the wallet balance: val assetToSpend = assetsByTokenId(boxesAvailable(genesisBlock, pubKey)).toSeq assetToSpend should not be empty @@ -238,6 +239,110 @@ class ErgoWalletSpec extends ErgoPropertyTest with WalletTestOps with Eventually } } + property("whitelist set, preserve tokens from auto-burn") { + val inputs = { + val x = IndexedSeq(new Input(genesisEmissionBox.id, emptyProverResult)) + Seq(encodedTokenId(Digest32 @@ x.head.boxId)) + } + + implicit val ww: WalletFixture = new WalletFixture(settings + .copy(walletSettings = settings + .walletSettings.copy(tokensWhitelist = Some(inputs))), parameters, getCurrentView(_).vault) + + val pubKey = getPublicKeys.head.pubkey + val genesisBlock = makeNextBlock(getUtxoState, Seq(makeGenesisTxWithAsset(pubKey, issueAsset = true))) + val initialBoxes = boxesAvailable(genesisBlock, pubKey) + val assetR = assetsByTokenId(initialBoxes).toSeq + Some(assetR.map(x => encodedTokenId(x._1))) shouldBe ww.settings.walletSettings.tokensWhitelist + + val boxesToUseEncoded = initialBoxes.map { box => + Base16.encode(ErgoBoxSerializer.toBytes(box)) + } + + applyBlock(genesisBlock) shouldBe 'success + implicit val patienceConfig: PatienceConfig = PatienceConfig(5.second, 300.millis) + eventually { + val confirmedBalance = getConfirmedBalances.walletBalance + log.error(s"Confirmed balance $confirmedBalance") + //pay out all the wallet balance: + val assetToSpend = assetsByTokenId(boxesAvailable(genesisBlock, pubKey)).toSeq + Some(assetToSpend.map(x => encodedTokenId(x._1))) shouldBe ww.settings.walletSettings.tokensWhitelist + assetToSpend should not be empty + + val req1 = PaymentRequest(Pay2SAddress(Constants.TrueLeaf), confirmedBalance / 2, Seq.empty, Map.empty) + + val tx1 = await(wallet.generateTransaction(Seq(req1), boxesToUseEncoded)).get + tx1.outputs.size shouldBe 2 + tx1.outputs.head.value shouldBe (confirmedBalance / 2) + tx1.outputs.head.additionalTokens.toArray shouldBe Seq.empty + toAssetMap(tx1.outputs(1).additionalTokens.toArray) shouldBe toAssetMap(assetToSpend) + } + } + + property("whitelist empty, auto-burn tokens on arbitrary tx") { + implicit val ww: WalletFixture = new WalletFixture(settings + .copy(walletSettings = settings + .walletSettings.copy(tokensWhitelist = Some(Seq.empty))), parameters, getCurrentView(_).vault) + + val pubKey = getPublicKeys.head.pubkey + val genesisBlock = makeNextBlock(getUtxoState, Seq(makeGenesisTxWithAsset(pubKey, issueAsset = true))) + val initialBoxes = boxesAvailable(genesisBlock, pubKey) + + val boxesToUseEncoded = initialBoxes.map { box => + Base16.encode(ErgoBoxSerializer.toBytes(box)) + } + + applyBlock(genesisBlock) shouldBe 'success + implicit val patienceConfig: PatienceConfig = PatienceConfig(5.second, 300.millis) + eventually { + val confirmedBalance = getConfirmedBalances.walletBalance + log.error(s"Confirmed balance $confirmedBalance") + //pay out all the wallet balance: + val assetToSpend = assetsByTokenId(boxesAvailable(genesisBlock, pubKey)).toSeq + assetToSpend should not be empty + + val req1 = PaymentRequest(Pay2SAddress(Constants.TrueLeaf), confirmedBalance / 2, Seq.empty, Map.empty) + + val tx1 = await(wallet.generateTransaction(Seq(req1), boxesToUseEncoded)).get + tx1.outputs.size shouldBe 2 + tx1.outputs.head.value shouldBe (confirmedBalance / 2) + tx1.outputs.head.additionalTokens.toArray shouldBe Seq.empty + toAssetMap(tx1.outputs(1).additionalTokens.toArray) shouldBe toAssetMap(Seq.empty) + } + } + + property("whitelist not set, ignore auto-burn") { + implicit val ww: WalletFixture = new WalletFixture(settings + .copy(walletSettings = settings + .walletSettings.copy(tokensWhitelist = None)), parameters, getCurrentView(_).vault) + + val pubKey = getPublicKeys.head.pubkey + val genesisBlock = makeNextBlock(getUtxoState, Seq(makeGenesisTxWithAsset(pubKey, issueAsset = true))) + val initialBoxes = boxesAvailable(genesisBlock, pubKey) + + val boxesToUseEncoded = initialBoxes.map { box => + Base16.encode(ErgoBoxSerializer.toBytes(box)) + } + + applyBlock(genesisBlock) shouldBe 'success + implicit val patienceConfig: PatienceConfig = PatienceConfig(5.second, 300.millis) + eventually { + val confirmedBalance = getConfirmedBalances.walletBalance + log.error(s"Confirmed balance $confirmedBalance") + //pay out all the wallet balance: + val assetToSpend = assetsByTokenId(boxesAvailable(genesisBlock, pubKey)).toSeq + assetToSpend should not be empty + + val req1 = PaymentRequest(Pay2SAddress(Constants.TrueLeaf), confirmedBalance / 2, Seq.empty, Map.empty) + + val tx1 = await(wallet.generateTransaction(Seq(req1), boxesToUseEncoded)).get + tx1.outputs.size shouldBe 2 + tx1.outputs.head.value shouldBe (confirmedBalance / 2) + tx1.outputs.head.additionalTokens.toArray shouldBe Seq.empty + toAssetMap(tx1.outputs(1).additionalTokens.toArray) shouldBe toAssetMap(assetToSpend) + } + } + property("Generate transaction with multiple inputs") { withFixture { implicit w => val addresses = getPublicKeys @@ -268,9 +373,7 @@ class ErgoWalletSpec extends ErgoPropertyTest with WalletTestOps with Eventually startDigest, parameters, validationSettingsNoIl, - VotingData.empty, - false - ) + VotingData.empty) val boxesToSpend = tx.inputs.map(i => genesisTx.outputs.find(o => java.util.Arrays.equals(o.id, i.boxId)).get) tx.statefulValidity(boxesToSpend, emptyDataBoxes, context) shouldBe 'success @@ -287,7 +390,7 @@ class ErgoWalletSpec extends ErgoPropertyTest with WalletTestOps with Eventually log.info(s"Payment requests 2 $req2") val tx2 = await(wallet.generateTransaction(req2)).get log.info(s"Generated transaction $tx2") - val context2 = new ErgoStateContext(Seq(block.header), Some(block.extension), startDigest, parameters, validationSettingsNoIl, VotingData.empty, false) + val context2 = new ErgoStateContext(Seq(block.header), Some(block.extension), startDigest, parameters, validationSettingsNoIl, VotingData.empty) val knownBoxes = tx.outputs ++ genesisTx.outputs val boxesToSpend2 = tx2.inputs.map(i => knownBoxes.find(o => java.util.Arrays.equals(o.id, i.boxId)).get) tx2.statefulValidity(boxesToSpend2, emptyDataBoxes, context2) shouldBe 'success diff --git a/src/test/scala/org/ergoplatform/serialization/SerializationTests.scala b/src/test/scala/org/ergoplatform/serialization/SerializationTests.scala index 0cd5582ba7..85c87ef3d2 100644 --- a/src/test/scala/org/ergoplatform/serialization/SerializationTests.scala +++ b/src/test/scala/org/ergoplatform/serialization/SerializationTests.scala @@ -15,7 +15,6 @@ import org.ergoplatform.utils.generators.WalletGenerators import org.scalacheck.Gen import org.scalatest.Assertion import scorex.core.serialization.ScorexSerializer -import org.ergoplatform.nodeView.state.ErgoStateContext class SerializationTests extends ErgoPropertyTest with WalletGenerators with scorex.testkit.SerializationTests { @@ -48,17 +47,6 @@ class SerializationTests extends ErgoPropertyTest with WalletGenerators with sco } } - property("ErgoStateContext serialization w. and w/out EIP-27 flag") { - val serializer = ErgoStateContextSerializer(settings) - val esc0 = ergoStateContextGen.sample.get - serializer.parseBytes(serializer.toBytes(esc0)).eip27Supported shouldBe false - - val esc = new ErgoStateContext(esc0.lastHeaders, esc0.lastExtensionOpt, esc0.genesisStateDigest, - esc0.currentParameters, esc0.validationSettings, esc0.votingData, true)(esc0.ergoSettings) - val recovered = serializer.parseBytes(serializer.toBytes(esc)) - recovered.eip27Supported shouldBe true - } - property("ErgoStateContext serialization") { val serializer = ErgoStateContextSerializer(settings) val b = ergoStateContextGen.sample.get diff --git a/src/test/scala/org/ergoplatform/settings/VotingSpecification.scala b/src/test/scala/org/ergoplatform/settings/VotingSpecification.scala index 9420d19dab..2923aa7b31 100644 --- a/src/test/scala/org/ergoplatform/settings/VotingSpecification.scala +++ b/src/test/scala/org/ergoplatform/settings/VotingSpecification.scala @@ -38,7 +38,7 @@ class VotingSpecification extends ErgoPropertyTest { Seq(VR.CheckDeserializedScriptType.id -> DisabledRule, VR.CheckValidOpCode.id -> ReplacedRule((VR.FirstRuleId + 11).toShort))) private val proposedUpdate2 = ErgoValidationSettingsUpdate(Seq(ValidationRules.fbOperationFailed), Seq()) val ctx: ErgoStateContext = { - new ErgoStateContext(Seq.empty, None, genesisStateDigest, parameters, validationSettingsNoIl, VotingData.empty, false)(updSettings) + new ErgoStateContext(Seq.empty, None, genesisStateDigest, parameters, validationSettingsNoIl, VotingData.empty)(updSettings) .upcoming(org.ergoplatform.mining.group.generator, 0L, settings.chainSettings.initialNBits, Array.fill(3)(0.toByte), emptyVSUpdate, 0.toByte) } val initialVs: ErgoValidationSettings = ctx.validationSettings @@ -100,7 +100,7 @@ class VotingSpecification extends ErgoPropertyTest { property("voting for non-existing parameter") { val p: Parameters = Parameters(2, Map(BlockVersion -> 0), proposedUpdate) val vr: VotingData = VotingData.empty - val esc = new ErgoStateContext(Seq(), None, ADDigest @@ Array.fill(33)(0: Byte), p, validationSettingsNoIl, vr, false)(updSettings) + val esc = new ErgoStateContext(Seq(), None, ADDigest @@ Array.fill(33)(0: Byte), p, validationSettingsNoIl, vr)(updSettings) val invalidVote = 100: Byte val votes = Array(invalidVote , NoParameter, NoParameter) @@ -119,7 +119,7 @@ class VotingSpecification extends ErgoPropertyTest { val p: Parameters = Parameters(2, Map(StorageFeeFactorIncrease -> kInit, BlockVersion -> 0), proposedUpdate) val vr: VotingData = VotingData.empty - val esc = new ErgoStateContext(Seq(), None, ADDigest @@ Array.fill(33)(0: Byte), p, validationSettingsNoIl, vr, false)(updSettings) + val esc = new ErgoStateContext(Seq(), None, ADDigest @@ Array.fill(33)(0: Byte), p, validationSettingsNoIl, vr)(updSettings) val votes = Array(StorageFeeFactorIncrease, NoParameter, NoParameter) val h = defaultHeaderGen.sample.get.copy(height = 2, votes = votes, version = 0: Byte) val esc2 = process(esc, p, h).get @@ -152,7 +152,7 @@ class VotingSpecification extends ErgoPropertyTest { val p: Parameters = Parameters(1, Map(BlockVersion -> 0), proposedUpdate) val vr: VotingData = VotingData.empty - val esc0 = new ErgoStateContext(Seq(), None, ADDigest @@ Array.fill(33)(0: Byte), p, validationSettings, vr, false)(updSettings) + val esc0 = new ErgoStateContext(Seq(), None, ADDigest @@ Array.fill(33)(0: Byte), p, validationSettings, vr)(updSettings) checkValidationSettings(esc0.validationSettings, emptyVSUpdate) val forkVote = Array(SoftFork, NoParameter, NoParameter) val emptyVotes = Array(NoParameter, NoParameter, NoParameter) @@ -259,7 +259,7 @@ class VotingSpecification extends ErgoPropertyTest { val forkVote = Array(SoftFork, NoParameter, NoParameter) val emptyVotes = Array(NoParameter, NoParameter, NoParameter) - val esc0 = new ErgoStateContext(Seq(), None, ADDigest @@ Array.fill(33)(0: Byte), p, validationSettingsNoIl, vr, false)(updSettings) + val esc0 = new ErgoStateContext(Seq(), None, ADDigest @@ Array.fill(33)(0: Byte), p, validationSettingsNoIl, vr)(updSettings) val h1 = defaultHeaderGen.sample.get.copy(votes = forkVote, version = 0: Byte, height = 1) val esc1 = process(esc0, p, h1).get @@ -317,7 +317,7 @@ class VotingSpecification extends ErgoPropertyTest { property("hardfork - v2 - activation") { val vr: VotingData = VotingData.empty - val esc0 = new ErgoStateContext(Seq(), None, ADDigest @@ Array.fill(33)(0: Byte), parameters, ErgoValidationSettings.initial, vr, false)(updSettings) + val esc0 = new ErgoStateContext(Seq(), None, ADDigest @@ Array.fill(33)(0: Byte), parameters, ErgoValidationSettings.initial, vr)(updSettings) val h1 = defaultHeaderGen.sample.get.copy(votes = Array.empty, version = 1: Byte, height = hfActivationHeight - 1) val expectedParameters1 = Parameters(hfActivationHeight - 1, DefaultParameters, ErgoValidationSettingsUpdate.empty) val esc1 = process(esc0, expectedParameters1, h1).get @@ -327,43 +327,6 @@ class VotingSpecification extends ErgoPropertyTest { esc2.currentParameters.blockVersion shouldBe 2 } - property("eip-27 support") { - val p: Parameters = Parameters(2, Map(OutputCostIncrease -> 100, BlockVersion -> 0), proposedUpdate) - val vr: VotingData = VotingData.empty - val votes = Array(ErgoStateContext.eip27Vote, NoParameter, NoParameter) - - val esc = new ErgoStateContext(Seq(), None, ADDigest @@ Array.fill(33)(0: Byte), p, validationSettingsNoIl, vr, false)(updSettings) - esc.eip27Supported shouldBe false - val h = defaultHeaderGen.sample.get.copy(height = 2, votes = votes, version = 0: Byte) - val esc2 = process(esc, p, h).get - esc2.eip27Supported shouldBe false - esc2.votingData.epochVotes.toMap.apply(ErgoStateContext.eip27Vote) shouldBe 1 - - // no quorum gathered - no EIP-27 support - val he = h.copy(votes = Array.fill(3)(NoParameter), height = 3) - val esc30 = process(esc2, p, he).get - val esc40 = process(esc30, p, he.copy(height = 4)).get - esc30.eip27Supported shouldBe false - esc40.eip27Supported shouldBe false - - // EIP-27 support shown - val esc31 = process(esc2, p, h.copy(height = 3)).get - esc31.votingData.epochVotes.toMap.apply(ErgoStateContext.eip27Vote) shouldBe 2 - esc31.eip27Supported shouldBe false - - // We use vote for parameter #8 to vote for EIP-27. The same parameter is used for voting for - // readjusting output cost - val p2: Parameters = Parameters(2, Map(OutputCostIncrease -> 101, BlockVersion -> 0), proposedUpdate) - val esc41 = process(esc31, p2, he.copy(height = 4)).get - esc41.eip27Supported shouldBe true - val esc51 = process(esc41, p2, he.copy(height = 5)).get - esc51.eip27Supported shouldBe true - val esc61 = process(esc51, p2, he.copy(height = 6)).get - esc61.eip27Supported shouldBe true - val esc71 = process(esc61, p2, he.copy(height = 7)).get - esc71.eip27Supported shouldBe true - } - private def checkValidationSettings(vs: ErgoValidationSettings, updated: ErgoValidationSettingsUpdate): Unit = { vs.rules.foreach { r => vs.isActive(r._1) shouldBe !updated.rulesToDisable.contains(r._1) diff --git a/src/test/scala/org/ergoplatform/utils/WalletTestOps.scala b/src/test/scala/org/ergoplatform/utils/WalletTestOps.scala index 032811a31c..2ad09180ad 100644 --- a/src/test/scala/org/ergoplatform/utils/WalletTestOps.scala +++ b/src/test/scala/org/ergoplatform/utils/WalletTestOps.scala @@ -75,6 +75,22 @@ trait WalletTestOps extends NodeViewBaseOps { makeNextBlock(getUtxoState, Seq(makeGenesisTx(script, assets))) } + def makeGenesisTxWithAsset(publicKey: ProveDlog, issueAsset: Boolean): ErgoTransaction = { + val inputs = IndexedSeq(new Input(genesisEmissionBox.id, emptyProverResult)) + val assets: Seq[(TokenId, Long)] = if (issueAsset) { + Seq((Digest32 @@ inputs.head.boxId) -> 1L) + } else { + Seq.empty + } + + CandidateGenerator.collectRewards(Some(genesisEmissionBox), + ErgoHistory.EmptyHistoryHeight, + Seq.empty, + publicKey, + emptyStateContext, + Colls.fromArray(assets.toArray)).head + } + def makeGenesisTx(publicKey: ProveDlog, assetsIn: Seq[(TokenId, Long)] = Seq.empty): ErgoTransaction = { val inputs = IndexedSeq(new Input(genesisEmissionBox.id, emptyProverResult)) val assets: Seq[(TokenId, Long)] = replaceNewAssetStub(assetsIn, inputs) diff --git a/src/test/scala/org/ergoplatform/utils/generators/ErgoTransactionGenerators.scala b/src/test/scala/org/ergoplatform/utils/generators/ErgoTransactionGenerators.scala index b05b362d5d..dc617fe090 100644 --- a/src/test/scala/org/ergoplatform/utils/generators/ErgoTransactionGenerators.scala +++ b/src/test/scala/org/ergoplatform/utils/generators/ErgoTransactionGenerators.scala @@ -327,7 +327,7 @@ trait ErgoTransactionGenerators extends ErgoGenerators with Generators { } yield { blocks match { case _ :: _ => - val sc = new ErgoStateContext(Seq(), None, startDigest, parameters, validationSettingsNoIl, VotingData.empty, false) + val sc = new ErgoStateContext(Seq(), None, startDigest, parameters, validationSettingsNoIl, VotingData.empty) blocks.foldLeft(sc -> 1) { case ((c, h), b) => val block = b.copy(header = b.header.copy(height = h, votes = votes(h - 1))) c.appendFullBlock(block).get -> (h + 1)