Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NODE-2616 Refactored validation of transaction version #3906

Merged
merged 20 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@ import com.wavesplatform.api.http.ApiError.StateCheckFailed
import com.wavesplatform.features.BlockchainFeatures.ContinuationTransaction
import com.wavesplatform.it.NodeConfigs
import com.wavesplatform.it.NodeConfigs.Default
import com.wavesplatform.it.api.{PutDataResponse, StateChangesDetails, Transaction, TransactionInfo}
import com.wavesplatform.it.api.SyncHttpApi.*
import com.wavesplatform.it.api.{PutDataResponse, StateChangesDetails, Transaction, TransactionInfo}
import com.wavesplatform.it.sync.invokeExpressionFee
import com.wavesplatform.it.transactions.BaseTransactionSuite
import com.wavesplatform.lang.directives.values.V6
import com.wavesplatform.lang.script.v1.ExprScript
import com.wavesplatform.lang.v1.compiler.TestCompiler
import com.wavesplatform.transaction.smart.InvokeExpressionTransaction
import org.scalatest.{Assertion, CancelAfterFailure}

class InvokeExpressionSuite extends BaseTransactionSuite with CancelAfterFailure {
Expand Down Expand Up @@ -55,7 +54,7 @@ class InvokeExpressionSuite extends BaseTransactionSuite with CancelAfterFailure
}

test("reject on illegal fields") {
val unsupportedVersion = InvokeExpressionTransaction.supportedVersions.max + 1
val unsupportedVersion = 4
assertApiError(
sender.invokeExpression(firstKeyPair, expr, version = unsupportedVersion.toByte),
AssertiveApiError(StateCheckFailed.Id, s"Transaction version $unsupportedVersion has not been activated yet", matchMessage = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ final case class TransactionJsonSerializer(blockchain: Blockchain, commonApi: Co
tx.assetFee._1.maybeBase58Repr.foreach(gen.writeStringField("feeAssetId", _))
gen.writeNumberField("timestamp", tx.timestamp, numbersAsString)
gen.writeNumberField("version", tx.version, numbersAsString)
if (tx.asInstanceOf[PBSince].isProtobufVersion) gen.writeNumberField("chainId", tx.chainId, numbersAsString)
if (PBSince.affects(tx)) gen.writeNumberField("chainId", tx.chainId, numbersAsString)
gen.writeStringField("sender", tx.sender.toAddress(tx.chainId).toString)
gen.writeStringField("senderPublicKey", tx.sender.toString)
gen.writeArrayField("proofs")(gen => tx.proofs.proofs.foreach(p => gen.writeString(p.toString)))
Expand Down Expand Up @@ -312,7 +312,7 @@ final case class TransactionJsonSerializer(blockchain: Blockchain, commonApi: Co
tx.assetFee._1.maybeBase58Repr.foreach(gen.writeStringField("feeAssetId", _))
gen.writeNumberField("timestamp", tx.timestamp, numbersAsString)
gen.writeNumberField("version", tx.version, numbersAsString)
if (tx.isProtobufVersion) gen.writeNumberField("chainId", tx.chainId, numbersAsString)
if (PBSince.affects(tx)) gen.writeNumberField("chainId", tx.chainId, numbersAsString)
gen.writeStringField("bytes", EthEncoding.toHexString(tx.bytes()))
gen.writeStringField("sender", tx.senderAddress().toString)
gen.writeStringField("senderPublicKey", tx.signerPublicKey().toString)
Expand Down
26 changes: 9 additions & 17 deletions node/src/main/scala/com/wavesplatform/database/package.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package com.wavesplatform

import java.nio.ByteBuffer
import java.util
import java.util.Map as JMap
import com.google.common.base.Charsets.UTF_8
import com.google.common.collect.{Interners, Maps}
import com.google.common.io.ByteStreams.{newDataInput, newDataOutput}
Expand All @@ -28,22 +25,17 @@ import com.wavesplatform.state.StateHash.SectionId
import com.wavesplatform.state.reader.LeaseDetails
import com.wavesplatform.transaction.Asset.IssuedAsset
import com.wavesplatform.transaction.lease.LeaseTransaction
import com.wavesplatform.transaction.{
EthereumTransaction,
GenesisTransaction,
PBSince,
PaymentTransaction,
Transaction,
TransactionParsers,
TxValidationError
}
import com.wavesplatform.transaction.{EthereumTransaction, GenesisTransaction, PBSince, PaymentTransaction, Transaction, TransactionParsers, TxValidationError, VersionedTransaction}
import com.wavesplatform.utils.*
import monix.eval.Task
import monix.reactive.Observable
import org.rocksdb.*
import sun.nio.ch.Util
import supertagged.TaggedType

import java.nio.ByteBuffer
import java.util
import java.util.Map as JMap
import scala.annotation.tailrec
import scala.collection.mutable.ArrayBuffer
import scala.collection.{View, mutable}
Expand Down Expand Up @@ -645,11 +637,11 @@ package object database {
def writeTransaction(v: (TxMeta, Transaction)): Array[Byte] = {
val (m, tx) = v
val ptx = tx match {
case lps: PBSince if !lps.isProtobufVersion => TD.LegacyBytes(ByteString.copyFrom(tx.bytes()))
case _: GenesisTransaction => TD.LegacyBytes(ByteString.copyFrom(tx.bytes()))
case _: PaymentTransaction => TD.LegacyBytes(ByteString.copyFrom(tx.bytes()))
case et: EthereumTransaction => TD.EthereumTransaction(ByteString.copyFrom(et.bytes()))
case _ => TD.WavesTransaction(PBTransactions.protobuf(tx))
case lps: PBSince with VersionedTransaction if !PBSince.affects(lps) => TD.LegacyBytes(ByteString.copyFrom(tx.bytes()))
case _: GenesisTransaction => TD.LegacyBytes(ByteString.copyFrom(tx.bytes()))
case _: PaymentTransaction => TD.LegacyBytes(ByteString.copyFrom(tx.bytes()))
case et: EthereumTransaction => TD.EthereumTransaction(ByteString.copyFrom(et.bytes()))
case _ => TD.WavesTransaction(PBTransactions.protobuf(tx))
vsuharnikov marked this conversation as resolved.
Show resolved Hide resolved
}
pb.TransactionData(ptx, m.status.protobuf, m.spentComplexity).toByteArray
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.wavesplatform.state.*
import com.wavesplatform.transaction.*
import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves}
import com.wavesplatform.transaction.TxValidationError.*
import com.wavesplatform.transaction.VersionedTransaction.{ConstV1, ToV2, ToV3}
import com.wavesplatform.transaction.assets.*
import com.wavesplatform.transaction.assets.exchange.*
import com.wavesplatform.transaction.lease.*
Expand Down Expand Up @@ -171,14 +172,23 @@ object CommonValidation {
else Right(tx)
}

def versionIsCorrect(tx: VersionedTransaction): Boolean = {
val maxVersion = tx match {
case _: ConstV1 => TxVersion.V1
case _: ToV2 => TxVersion.V2
case _: ToV3 => TxVersion.V3
}
tx.version > 0 && tx.version <= maxVersion
}

val versionsBarrier = tx match {
case v: VersionedTransaction if !TransactionParsers.versionIsCorrect(v) && blockchain.isFeatureActivated(LightNode) =>
case v: VersionedTransaction if !versionIsCorrect(v) && blockchain.isFeatureActivated(LightNode) =>
Left(UnsupportedTypeAndVersion(v.tpe.id.toByte, v.version))

case p: PBSince if p.isProtobufVersion =>
case p: PBSince with VersionedTransaction if PBSince.affects(p) =>
activationBarrier(BlockchainFeatures.BlockV5)

case v: VersionedTransaction if !TransactionParsers.versionIsCorrect(v) =>
case v: VersionedTransaction if !versionIsCorrect(v) =>
Left(UnsupportedTypeAndVersion(v.tpe.id.toByte, v.version))

case _ =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ object FeeValidation {
case tx: DataTransaction =>
val payloadLength =
if (blockchain.isFeatureActivated(BlockchainFeatures.RideV6)) DataTxValidator.realUserPayloadSize(tx.data)
else if (tx.isProtobufVersion) tx.protoDataPayload.length
else if (PBSince.affects(tx)) tx.protoDataPayload.length
else if (blockchain.isFeatureActivated(BlockchainFeatures.SmartAccounts)) tx.bodyBytes().length
else tx.bytes().length

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ object InvokeDiffsCommon {
tx.enableEmptyKeys || dataEntries.forall(_.key.nonEmpty),
(), {
val versionInfo = tx.root match {
case s: PBSince => s" in tx version >= ${s.protobufVersion}"
case s: PBSince => s" in tx version >= ${PBSince.version(s)}"
case _ => ""
}
s"Empty keys aren't allowed$versionInfo"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ final case class CreateAliasTransaction(
chainId: Byte
) extends Transaction(TransactionType.CreateAlias)
with SigProofsSwitch
with VersionedTransaction
with VersionedTransaction.ToV3
with TxWithFee.InWaves
with PBSince.V3 {

Expand All @@ -44,8 +44,7 @@ final case class CreateAliasTransaction(
object CreateAliasTransaction extends TransactionParser {
type TransactionT = CreateAliasTransaction

val supportedVersions: Set[TxVersion] = Set(1, 2, 3)
val typeId: TxType = 10: Byte
val typeId: TxType = 10: Byte

implicit val validator = CreateAliasTxValidator

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package com.wavesplatform.transaction

import scala.util.Try

import com.wavesplatform.account.{AddressScheme, KeyPair, PrivateKey, PublicKey}
import com.wavesplatform.crypto
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.protobuf.transaction.PBTransactions
import com.wavesplatform.state._
import com.wavesplatform.state.*
import com.wavesplatform.transaction.serialization.impl.DataTxSerializer
import com.wavesplatform.transaction.validation.TxValidator
import com.wavesplatform.transaction.validation.impl.DataTxValidator
import monix.eval.Coeval
import play.api.libs.json._
import play.api.libs.json.*

import scala.util.Try

case class DataTransaction(
version: TxVersion,
Expand All @@ -23,7 +23,7 @@ case class DataTransaction(
chainId: Byte
) extends Transaction(TransactionType.Data)
with ProvenTransaction
with VersionedTransaction
with VersionedTransaction.ToV2
with TxWithFee.InWaves
with FastHashId
with PBSince.V2 {
Expand All @@ -38,13 +38,12 @@ case class DataTransaction(
object DataTransaction extends TransactionParser {
type TransactionT = DataTransaction

val MaxBytes: Int = 150 * 1024 // uses for RIDE CONST_STRING and CONST_BYTESTR
val MaxProtoBytes: Int = 165890 // uses for RIDE CONST_BYTESTR
val MaxRideV6Bytes: Int = 165835 // (DataEntry.MaxPBKeySize + DataEntry.MaxValueSize) * 5
val MaxEntryCount: Int = 100
val MaxBytes: Int = 150 * 1024 // uses for RIDE CONST_STRING and CONST_BYTESTR
val MaxProtoBytes: Int = 165890 // uses for RIDE CONST_BYTESTR
val MaxRideV6Bytes: Int = 165835 // (DataEntry.MaxPBKeySize + DataEntry.MaxValueSize) * 5
val MaxEntryCount: Int = 100

override val typeId: TxType = 12: Byte
override val supportedVersions: Set[TxVersion] = Set(1, 2)
override val typeId: TxType = 12: Byte

implicit val validator: TxValidator[DataTransaction] = DataTxValidator

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import play.api.libs.json.JsObject
import scala.util.Try

case class GenesisTransaction(recipient: Address, amount: TxNonNegativeAmount, timestamp: TxTimestamp, signature: ByteStr, chainId: Byte)
extends Transaction(TransactionType.Genesis) {
extends Transaction(TransactionType.Genesis)
with VersionedTransaction.ConstV1 {
override val assetFee: (Asset, Long) = (Waves, 0)
override val id: Coeval[ByteStr] = Coeval.evalOnce(signature)

Expand All @@ -26,8 +27,7 @@ case class GenesisTransaction(recipient: Address, amount: TxNonNegativeAmount, t
object GenesisTransaction extends TransactionParser {
type TransactionT = GenesisTransaction

override val typeId: TxType = 1: Byte
override val supportedVersions: Set[TxVersion] = Set(1)
override val typeId: TxType = 1: Byte

override def parseBytes(bytes: Array[TxVersion]): Try[GenesisTransaction] =
GenesisTxSerializer.parseBytes(bytes)
Expand Down
30 changes: 12 additions & 18 deletions node/src/main/scala/com/wavesplatform/transaction/PBSince.scala
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
package com.wavesplatform.transaction

import com.wavesplatform.protobuf.transaction.PBTransactions

trait PBSince { self: Transaction with VersionedTransaction =>
def protobufVersion: TxVersion
final def isProtobufVersion: Boolean = self.version >= protobufVersion

override def bytesSize: Int =
if (isProtobufVersion) PBTransactions.protobuf(self).serializedSize else bytes().length
}
sealed trait PBSince

object PBSince {
trait V1 extends PBSince { self: Transaction with VersionedTransaction =>
override def protobufVersion: TxVersion = TxVersion.V1
}
trait V1 extends PBSince
trait V2 extends PBSince
trait V3 extends PBSince

trait V2 extends PBSince { self: Transaction with VersionedTransaction =>
override def protobufVersion: TxVersion = TxVersion.V2
}
def version(tx: PBSince): TxVersion =
tx match {
case _: V1 => TxVersion.V1
case _: V2 => TxVersion.V2
case _: V3 => TxVersion.V3
}

trait V3 extends PBSince { self: Transaction with VersionedTransaction =>
override def protobufVersion: TxVersion = TxVersion.V3
}
def affects(tx: PBSince & VersionedTransaction): Boolean =
tx.version >= version(tx)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.wavesplatform.transaction

import scala.util.Try
import com.wavesplatform.account.{Address, KeyPair, PublicKey}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.crypto
Expand All @@ -11,6 +10,8 @@ import com.wavesplatform.transaction.validation.impl.PaymentTxValidator
import monix.eval.Coeval
import play.api.libs.json.JsObject

import scala.util.Try

case class PaymentTransaction(
sender: PublicKey,
recipient: Address,
Expand All @@ -21,6 +22,7 @@ case class PaymentTransaction(
chainId: Byte
) extends Transaction(TransactionType.Payment)
with ProvenTransaction
with VersionedTransaction.ConstV1
with TxWithFee.InWaves {

val bodyBytes: Coeval[Array[Byte]] = Coeval.evalOnce(PaymentTxSerializer.bodyBytes(this))
Expand All @@ -36,8 +38,7 @@ case class PaymentTransaction(
object PaymentTransaction extends TransactionParser {
type TransactionT = PaymentTransaction

override val typeId: TxType = 2: Byte
override val supportedVersions: Set[TxVersion] = Set(1)
override val typeId: TxType = 2: Byte

override def parseBytes(bytes: Array[TxVersion]): Try[PaymentTransaction] =
PaymentTxSerializer.parseBytes(bytes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ import com.wavesplatform.transaction.validation.TxValidator
import scala.util.Try

trait TransactionParser {
type TransactionT <: Transaction
type TransactionT <: Transaction with VersionedTransaction

def typeId: TxType

def supportedVersions: Set[TxVersion]

def parseBytes(bytes: Array[Byte]): Try[TransactionT]

implicit def validator: TxValidator[TransactionT]
Expand Down
Loading