diff --git a/node/src/main/scala/com/wavesplatform/protobuf/transaction/PBTransactions.scala b/node/src/main/scala/com/wavesplatform/protobuf/transaction/PBTransactions.scala index 93c395c198..e7a2631fc4 100644 --- a/node/src/main/scala/com/wavesplatform/protobuf/transaction/PBTransactions.scala +++ b/node/src/main/scala/com/wavesplatform/protobuf/transaction/PBTransactions.scala @@ -668,7 +668,7 @@ object PBTransactions { val data = Data.InvokeScript(toPBInvokeScriptData(dappAddress, fcOpt, payment)) PBTransactions.create(sender, chainId, fee.value, feeAssetId, timestamp, version, proofs, data) - case tx @ vt.assets.UpdateAssetInfoTransaction(version, sender, assetId, name, description, timestamp, fee, feeAssetId, proofs, chainId) => + case vt.assets.UpdateAssetInfoTransaction(version, sender, assetId, name, description, timestamp, fee, feeAssetId, proofs, chainId) => val data = UpdateAssetInfoTransactionData() .withAssetId(assetId.id.toByteString) .withName(name) diff --git a/node/src/main/scala/com/wavesplatform/state/diffs/CommonValidation.scala b/node/src/main/scala/com/wavesplatform/state/diffs/CommonValidation.scala index 5e59323091..39313b0ea6 100644 --- a/node/src/main/scala/com/wavesplatform/state/diffs/CommonValidation.scala +++ b/node/src/main/scala/com/wavesplatform/state/diffs/CommonValidation.scala @@ -172,10 +172,13 @@ object CommonValidation { } val versionsBarrier = tx match { + case v: VersionedTransaction if !TransactionParsers.versionIsCorrect(v) && blockchain.isFeatureActivated(TransactionStateSnapshot) => + Left(UnsupportedTypeAndVersion(v.tpe.id.toByte, v.version)) + case p: PBSince if p.isProtobufVersion => activationBarrier(BlockchainFeatures.BlockV5) - case v: VersionedTransaction if !TransactionParsers.all.contains((v.tpe.id.toByte, v.version)) => + case v: VersionedTransaction if !TransactionParsers.versionIsCorrect(v) => Left(UnsupportedTypeAndVersion(v.tpe.id.toByte, v.version)) case _ => diff --git a/node/src/main/scala/com/wavesplatform/transaction/TransactionParsers.scala b/node/src/main/scala/com/wavesplatform/transaction/TransactionParsers.scala index db494b1f19..afeb7cd4ef 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/TransactionParsers.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/TransactionParsers.scala @@ -39,7 +39,8 @@ object TransactionParsers { SetAssetScriptTransaction, InvokeScriptTransaction, TransferTransaction, - InvokeExpressionTransaction + InvokeExpressionTransaction, + UpdateAssetInfoTransaction ).flatMap { x => x.supportedVersions.map { version => ((x.typeId, version), x) @@ -79,4 +80,7 @@ object TransactionParsers { tx <- if (bytes(0) == 0) modernParseBytes else oldParseBytes } yield tx } + + def versionIsCorrect(tx: Transaction & VersionedTransaction): Boolean = + TransactionParsers.all.contains((tx.tpe.id.toByte, tx.version)) } diff --git a/node/src/main/scala/com/wavesplatform/transaction/assets/UpdateAssetInfoTransaction.scala b/node/src/main/scala/com/wavesplatform/transaction/assets/UpdateAssetInfoTransaction.scala index 3b5ee705c6..dacd992375 100644 --- a/node/src/main/scala/com/wavesplatform/transaction/assets/UpdateAssetInfoTransaction.scala +++ b/node/src/main/scala/com/wavesplatform/transaction/assets/UpdateAssetInfoTransaction.scala @@ -4,14 +4,16 @@ import com.wavesplatform.account.{AddressScheme, KeyPair, PrivateKey, PublicKey} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.crypto import com.wavesplatform.lang.ValidationError +import com.wavesplatform.transaction.* import com.wavesplatform.transaction.Asset.IssuedAsset -import com.wavesplatform.transaction._ import com.wavesplatform.transaction.serialization.impl.{BaseTxJson, PBTransactionSerializer} -import com.wavesplatform.transaction.validation._ +import com.wavesplatform.transaction.validation.* import com.wavesplatform.transaction.validation.impl.UpdateAssetInfoTxValidator import monix.eval.Coeval import play.api.libs.json.{JsObject, Json} +import scala.util.{Failure, Success, Try} + case class UpdateAssetInfoTransaction( version: TxVersion, sender: PublicKey, @@ -45,8 +47,11 @@ case class UpdateAssetInfoTransaction( ) } -object UpdateAssetInfoTransaction { - val supportedVersions: Set[TxVersion] = Set(1) +object UpdateAssetInfoTransaction extends TransactionParser { + type TransactionT = UpdateAssetInfoTransaction + + override val typeId: TxType = 17: Byte + override val supportedVersions: Set[TxVersion] = Set(1) implicit def sign(tx: UpdateAssetInfoTransaction, privateKey: PrivateKey): UpdateAssetInfoTransaction = tx.copy(proofs = Proofs(crypto.sign(privateKey, tx.bodyBytes()))) @@ -94,4 +99,12 @@ object UpdateAssetInfoTransaction { ): Either[ValidationError, UpdateAssetInfoTransaction] = create(version, sender.publicKey, assetId, name, description, timestamp, feeAmount, feeAsset, Proofs.empty, chainId) .map(_.signWith(sender.privateKey)) + + override def parseBytes(bytes: Array[Byte]): Try[UpdateAssetInfoTransaction] = + PBTransactionSerializer + .parseBytes(bytes) + .flatMap { + case tx: UpdateAssetInfoTransaction => Success(tx) + case tx: Transaction => Failure(UnexpectedTransaction(typeId, tx.tpe.id.toByte)) + } } diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/TransactionVersionValidationTest.scala b/node/src/test/scala/com/wavesplatform/state/diffs/TransactionVersionValidationTest.scala new file mode 100644 index 0000000000..5b4a9eb4b2 --- /dev/null +++ b/node/src/test/scala/com/wavesplatform/state/diffs/TransactionVersionValidationTest.scala @@ -0,0 +1,103 @@ +package com.wavesplatform.state.diffs + +import com.wavesplatform.db.WithDomain +import com.wavesplatform.db.WithState.AddrWithBalance +import com.wavesplatform.lang.directives.values.V6 +import com.wavesplatform.lang.v1.compiler.TestCompiler +import com.wavesplatform.test.DomainPresets.* +import com.wavesplatform.test.{PropSpec, produce} +import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} +import com.wavesplatform.transaction.TxHelpers.* +import com.wavesplatform.transaction.TxVersion.* +import com.wavesplatform.transaction.assets.exchange.OrderType.* +import com.wavesplatform.transaction.{Transaction, TxVersion} + +class TransactionVersionValidationTest extends PropSpec with WithDomain { + private val script = TestCompiler(V6).compileExpression("true") + private val issueTx1 = issue(script = Some(script)) + private val issueTx2 = issue() + private val leaseTx = lease() + private val setDApp = setScript( + secondSigner, + TestCompiler(V6).compileContract( + """ + | @Callable(i) + | func default() = [] + """.stripMargin + ) + ) + private val preconditions = Seq(issueTx1, issueTx2, leaseTx, setDApp) + + private val asset = IssuedAsset(issueTx1.id()) + private val asset2 = IssuedAsset(issueTx2.id()) + private val order1 = order(BUY, asset, Waves, price = 123456789, version = V1) + private val order2 = order(SELL, asset, Waves, price = 123456789, version = V1) + + private val txsByMaxVersion: Seq[(TxVersion, TxVersion => Transaction)] = + Seq( + (V3, v => transfer(version = v)), + (V3, v => issue(version = v)), + (V3, v => reissue(asset, version = v)), + (V3, v => burn(asset, version = v)), + (V3, v => lease(version = v)), + (V3, v => leaseCancel(leaseTx.id(), version = v)), + (V3, v => createAlias("alias", version = v)), + (V3, v => exchange(order1, order2, version = v)), + (V2, v => data(defaultSigner, Seq(), version = v)), + (V2, v => invoke(version = v)), + (V2, v => massTransfer(version = v, fee = 200_000)), + (V2, v => setAssetScript(defaultSigner, asset, script, version = v)), + (V2, v => setScript(defaultSigner, script, version = v)), + (V2, v => sponsor(asset2, version = v)), + (V1, v => updateAssetInfo(asset.id, version = v)) + ) + + property("zero and negative tx versions are forbidden before and after snapshot activation") { + Seq(BlockRewardDistribution, TransactionStateSnapshot).foreach(settings => + withDomain( + settings.configure(_.copy(minAssetInfoUpdateInterval = 1)), + AddrWithBalance.enoughBalances(defaultSigner, secondSigner) + ) { d => + d.appendBlock(preconditions*) + txsByMaxVersion.foreach { case (_, tx) => + d.appendBlockE(tx(0: Byte)) should produce("Bad transaction") + d.appendBlockE(tx(-1: Byte)) should produce("Bad transaction") + } + } + ) + } + + property("max tx version is available before and after snapshot activation") { + Seq(BlockRewardDistribution, TransactionStateSnapshot).foreach(settings => + withDomain( + settings.configure(_.copy(minAssetInfoUpdateInterval = 1)), + AddrWithBalance.enoughBalances(defaultSigner, secondSigner) + ) { d => + d.appendBlock(preconditions*) + d.appendAndAssertSucceed(txsByMaxVersion.map { case (maxVersion, tx) => tx(maxVersion) }*) + } + ) + } + + property("more than max tx version is available before snapshot activation") { + withDomain( + BlockRewardDistribution.configure(_.copy(minAssetInfoUpdateInterval = 1)), + AddrWithBalance.enoughBalances(defaultSigner, secondSigner) + ) { d => + d.appendBlock(preconditions*) + d.appendAndAssertSucceed(txsByMaxVersion.map { case (maxVersion, tx) => tx((maxVersion + 1).toByte) }*) + } + } + + property("more than max tx version is forbidden after snapshot activation") { + withDomain( + TransactionStateSnapshot.configure(_.copy(minAssetInfoUpdateInterval = 1)), + AddrWithBalance.enoughBalances(defaultSigner, secondSigner) + ) { d => + d.appendBlock(preconditions*) + txsByMaxVersion.foreach { case (maxVersion, tx) => + d.appendBlockE(tx((maxVersion + 1).toByte)) should produce("Bad transaction") + } + } + } +}