Skip to content

Commit

Permalink
NODE-2616 Refactored validation of transaction version (#3906)
Browse files Browse the repository at this point in the history
  • Loading branch information
xrtm000 authored Nov 29, 2023
1 parent b94b9e7 commit c087618
Show file tree
Hide file tree
Showing 52 changed files with 223 additions and 266 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ object AsyncNetworkApi {
s"it-client-to-${node.name}",
nonce
)
sender.connect(node.networkAddress).map { ch =>
if (ch.isActive) sender.send(ch, messages*).map(_ => sender.close()) else sender.close()
sender.connect(node.networkAddress).flatMap { ch =>
if (ch.isActive) sender.send(ch, messages*).map(_ => sender.close()) else {
sender.close()
Future.failed(new Exception("Channel is inactive"))
}
}
}
}
Expand Down
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 @@ -262,7 +262,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 @@ -290,8 +290,8 @@ final case class TransactionJsonSerializer(blockchain: Blockchain, commonApi: Co
gen.writeNumberField("fee", tx.assetFee._2, numbersAsString)
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)
gen.writeNumberField("version", 1, numbersAsString)
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
20 changes: 5 additions & 15 deletions node/src/main/scala/com/wavesplatform/database/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,8 @@ import com.wavesplatform.protobuf.{ByteStringExt, PBSnapshots}
import com.wavesplatform.state.*
import com.wavesplatform.state.StateHash.SectionId
import com.wavesplatform.transaction.Asset.IssuedAsset
import com.wavesplatform.transaction.{
EthereumTransaction,
GenesisTransaction,
PBSince,
PaymentTransaction,
Transaction,
TransactionParsers,
TxPositiveAmount,
TxValidationError
}

import com.wavesplatform.transaction.{EthereumTransaction, PBSince, Transaction, TransactionParsers,TxPositiveAmount, TxValidationError, Versioned}
import com.wavesplatform.utils.*
import monix.eval.Task
import monix.reactive.Observable
Expand Down Expand Up @@ -628,11 +620,9 @@ 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 Versioned if PBSince.affects(lps) => TD.WavesTransaction(PBTransactions.protobuf(tx))
case et: EthereumTransaction => TD.EthereumTransaction(ByteString.copyFrom(et.bytes()))
case _ => TD.LegacyBytes(ByteString.copyFrom(tx.bytes()))
}
pb.TransactionData(ptx, m.status.protobuf, m.spentComplexity).toByteArray
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,20 +171,23 @@ object CommonValidation {

}

def generic1or2Barrier(t: VersionedTransaction): Either[ActivationError, T] = {
def generic1or2Barrier(t: Versioned): Either[ActivationError, T] = {
if (t.version == 1.toByte) Right(tx)
else if (t.version == 2.toByte) activationBarrier(BlockchainFeatures.SmartAccounts)
else Right(tx)
}

def versionIsCorrect(tx: Versioned): Boolean =
tx.version > 0 && tx.version <= Versioned.maxVersion(tx)

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

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

case v: VersionedTransaction if !TransactionParsers.versionIsCorrect(v) =>
case v: Versioned 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 @@ -377,7 +377,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 @@ -24,7 +24,7 @@ final case class CreateAliasTransaction(
chainId: Byte
) extends Transaction(TransactionType.CreateAlias)
with SigProofsSwitch
with VersionedTransaction
with Versioned.ToV3
with TxWithFee.InWaves
with PBSince.V3 {

Expand All @@ -45,8 +45,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: TxValidator[CreateAliasTransaction] = 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 Versioned.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 @@ -39,7 +39,6 @@ final case class EthereumTransaction(
override val chainId: Byte
) extends Transaction(TransactionType.Ethereum)
with Authorized
with VersionedTransaction.ConstV1
with PBSince.V1 { self =>
import EthereumTransaction.*

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,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 & Versioned): 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 @@ -36,8 +37,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
@@ -1,6 +1,6 @@
package com.wavesplatform.transaction

trait SigProofsSwitch extends ProvenTransaction { self: Transaction with VersionedTransaction =>
trait SigProofsSwitch extends ProvenTransaction { self: Transaction with Versioned =>
def usesLegacySignature: Boolean =
self.version == Transaction.V1
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ trait TransactionParser {

def typeId: TxType

def supportedVersions: Set[TxVersion]

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

implicit def validator: TxValidator[TransactionT]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ package com.wavesplatform.transaction
import com.wavesplatform.transaction.assets.*
import com.wavesplatform.transaction.assets.exchange.ExchangeTransaction
import com.wavesplatform.transaction.lease.{LeaseCancelTransaction, LeaseTransaction}
import com.wavesplatform.transaction.smart.{InvokeExpressionTransaction, InvokeScriptTransaction, SetScriptTransaction}
import com.wavesplatform.transaction.smart.{InvokeScriptTransaction, SetScriptTransaction}
import com.wavesplatform.transaction.transfer.*

import scala.util.{Failure, Try}

object TransactionParsers {
private[this] val old: Map[Byte, TransactionParser] = Seq[TransactionParser](
private[this] val old: Map[TxType, TransactionParser] = Seq[TransactionParser](
GenesisTransaction,
PaymentTransaction,
IssueTransaction,
Expand All @@ -21,11 +21,9 @@ object TransactionParsers {
CreateAliasTransaction,
MassTransferTransaction,
TransferTransaction
).map { x =>
x.typeId -> x
}.toMap
).map { x => x.typeId -> x }.toMap

private[this] val modern: Map[(Byte, Byte), TransactionParser] = Seq[TransactionParser](
private[this] val modern: Map[TxType, TransactionParser] = Seq[TransactionParser](
DataTransaction,
SetScriptTransaction,
IssueTransaction,
Expand All @@ -38,22 +36,10 @@ object TransactionParsers {
SponsorFeeTransaction,
SetAssetScriptTransaction,
InvokeScriptTransaction,
TransferTransaction,
InvokeExpressionTransaction,
UpdateAssetInfoTransaction
).flatMap { x =>
x.supportedVersions.map { version =>
((x.typeId, version), x)
}
}.toMap

val all: Map[(Byte, Byte), TransactionParser] = old.flatMap { case (typeId, builder) =>
builder.supportedVersions.map { version =>
((typeId, version), builder)
}
} ++ modern
TransferTransaction
).map { x => (x.typeId, x) }.toMap

def by(typeId: Byte, version: TxVersion): Option[TransactionParser] = all.get((typeId, version))
val all: Map[TxType, TransactionParser] = old ++ modern

def parseBytes(bytes: Array[Byte]): Try[Transaction] = {
def validate(parser: TransactionParser)(tx: parser.TransactionT): Try[Transaction] = {
Expand All @@ -63,7 +49,7 @@ object TransactionParsers {
def modernParseBytes: Try[Transaction] = {
val typeId = bytes(1)
val version = bytes(2)
modern.get((typeId, version)) match {
modern.get(typeId) match {
case Some(parser) => parser.parseBytes(bytes).flatMap(validate(parser))
case None => Failure[Transaction](UnknownTypeAndVersion(typeId, version))
}
Expand All @@ -74,13 +60,9 @@ object TransactionParsers {
case None => Failure[Transaction](UnknownType(bytes(0)))
}
}

for {
_ <- Either.cond(bytes.length > 2, (), BufferUnderflow).toTry
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))
}
Loading

0 comments on commit c087618

Please sign in to comment.