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-2531 Corrected validation order of sync payment balance #3892

Merged
merged 7 commits into from
Nov 30, 2023
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,8 @@ object TransactionDiffer {
InvokeRejectError(fte.message, fte.log)
case fte: FailedTransactionError if fte.isFailFree && blockchain.isFeatureActivated(RideV6) =>
ScriptExecutionError(fte.message, fte.log, fte.assetId)
case err => err
case err =>
err
}
.leftMap(TransactionValidationError(_, tx))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,11 @@ object InvokeScriptDiff {
_ = invocationRoot.setLog(log)
spentComplexity = remainingComplexity - scriptResult.unusedComplexity.max(0)

_ <- validateIntermediateBalances(blockchain, resultSnapshot, spentComplexity, log)
_ <-
if (blockchain.isFeatureActivated(LightNode))
traced(Right(()))
else
validateIntermediateBalances(blockchain, resultSnapshot, spentComplexity, log)

doProcessActions = (actions: List[CallableAction], unusedComplexity: Int) => {
val storingComplexity = complexityAfterPayments - unusedComplexity
Expand Down Expand Up @@ -350,7 +354,11 @@ object InvokeScriptDiff {
resultSnapshot <- traced(
(resultSnapshot.setScriptsComplexity(0) |+| actionsSnapshot.addScriptsComplexity(paymentsComplexity)).asRight
)
_ <- validateIntermediateBalances(blockchain, resultSnapshot, resultSnapshot.scriptsComplexity, log)
_ <-
if (blockchain.isFeatureActivated(LightNode))
traced(Right(()))
else
validateIntermediateBalances(blockchain, resultSnapshot, resultSnapshot.scriptsComplexity, log)
_ = invocationRoot.setResult(scriptResult)
} yield (
resultSnapshot,
Expand Down Expand Up @@ -423,30 +431,31 @@ object InvokeScriptDiff {
)
}

private def validateIntermediateBalances(blockchain: Blockchain, snapshot: StateSnapshot, spentComplexity: Long, log: Log[Id]) = traced(
if (blockchain.isFeatureActivated(BlockchainFeatures.RideV6)) {
BalanceDiffValidation(blockchain)(snapshot)
.leftMap { be => FailedTransactionError.dAppExecution(be.toString, spentComplexity, log) }
} else if (blockchain.height >= blockchain.settings.functionalitySettings.enforceTransferValidationAfter) {
// reject transaction if any balance is negative
snapshot.balances.view
.flatMap {
case ((address, asset), balance) if balance < 0 => Some(address -> asset)
case _ => None
}
.headOption
.fold[Either[ValidationError, Unit]](Right(())) { case (address, asset) =>
val msg = asset match {
case Waves =>
s"$address: Negative waves balance: old = ${blockchain.balance(address)}, new = ${snapshot.balances((address, Waves))}"
case ia: IssuedAsset =>
s"$address: Negative asset $ia balance: old = ${blockchain.balance(address, ia)}, new = ${snapshot.balances((address, ia))}"
def validateIntermediateBalances(blockchain: Blockchain, snapshot: StateSnapshot, spentComplexity: Long, log: Log[Id]): CoevalR[Any] =
traced(
if (blockchain.isFeatureActivated(BlockchainFeatures.RideV6)) {
BalanceDiffValidation(blockchain)(snapshot)
.leftMap { be => FailedTransactionError.dAppExecution(be.toString, spentComplexity, log) }
} else if (blockchain.height >= blockchain.settings.functionalitySettings.enforceTransferValidationAfter) {
// reject transaction if any balance is negative
snapshot.balances.view
.flatMap {
case ((address, asset), balance) if balance < 0 => Some(address -> asset)
case _ => None
}
.headOption
.fold[Either[ValidationError, Unit]](Right(())) { case (address, asset) =>
val msg = asset match {
case Waves =>
s"$address: Negative waves balance: old = ${blockchain.balance(address)}, new = ${snapshot.balances((address, Waves))}"
case ia: IssuedAsset =>
s"$address: Negative asset $ia balance: old = ${blockchain.balance(address, ia)}, new = ${snapshot.balances((address, ia))}"
}
Left(FailOrRejectError(msg))
}
Left(FailOrRejectError(msg))
}

} else Right(())
)
} else Right(())
)

private def ensurePaymentsAreNotNegative(blockchain: Blockchain, tx: InvokeScript, invoker: Address, dAppAddress: Address) = traced {
tx.payments.collectFirst {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.wavesplatform.lang.v1.traits.domain.Recipient.*
import com.wavesplatform.lang.{Global, ValidationError}
import com.wavesplatform.state.*
import com.wavesplatform.state.BlockRewardCalculator.CurrentBlockRewardPart
import com.wavesplatform.state.diffs.invoke.InvokeScriptDiff.validateIntermediateBalances
import com.wavesplatform.state.diffs.invoke.{InvokeScript, InvokeScriptDiff, InvokeScriptTransactionLike}
import com.wavesplatform.state.SnapshotBlockchain
import com.wavesplatform.transaction.Asset.*
Expand Down Expand Up @@ -475,6 +476,11 @@ class DAppEnvironment(
invocationTracker,
wrapDAppEnv
)(invoke)
_ <-
if (blockchain.isFeatureActivated(LightNode))
validateIntermediateBalances(blockchain, snapshot, totalComplexityLimit - availableComplexity, Nil)
else
traced(Right(()))
fixedSnapshot = snapshot
.setScriptResults(Map(txId -> InvokeScriptResult(invokes = Seq(invocation.copy(stateChanges = snapshot.scriptResults(txId))))))
} yield {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
package com.wavesplatform.transaction.smart.script.trace
import scala.util.Right

import com.wavesplatform.lang.ValidationError
import monix.eval.Coeval

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.wavesplatform.state.diffs.ci.sync

import com.wavesplatform.db.WithDomain
import com.wavesplatform.db.WithState.AddrWithBalance
import com.wavesplatform.lang.directives.values.V7
import com.wavesplatform.lang.v1.compiler.Terms.CONST_BOOLEAN
import com.wavesplatform.lang.v1.compiler.TestCompiler
import com.wavesplatform.test.DomainPresets.{BlockRewardDistribution, TransactionStateSnapshot}
import com.wavesplatform.test.{PropSpec, produce}
import com.wavesplatform.transaction.Asset.IssuedAsset
import com.wavesplatform.transaction.TxHelpers.*

class SyncInvokePaymentValidationOrderTest extends PropSpec with WithDomain {
private val issueTx = issue()
private val asset = IssuedAsset(issueTx.id())
private val dApp = TestCompiler(V7).compileContract(
s"""
| @Callable(i)
| func f1(bigComplexity: Boolean, error: Boolean) = {
| strict r = Address(base58'$defaultAddress').invoke("f2", [bigComplexity, error], [AttachedPayment(base58'$asset', 123)])
| []
| }
|
| @Callable(i)
| func f2(bigComplexity: Boolean, error: Boolean) = {
| strict c = if (bigComplexity) then ${(1 to 6).map(_ => "sigVerify(base58'', base58'', base58'')").mkString(" || ")} else 0
| strict e = if (error) then throw("custom error") else 0
| []
| }
""".stripMargin
)

property("sync invoke payment should be validated after calling dApp if light node isn't activated") {
withDomain(BlockRewardDistribution, AddrWithBalance.enoughBalances(defaultSigner, secondSigner)) { d =>
d.appendBlock(setScript(defaultSigner, dApp), setScript(secondSigner, dApp), issueTx)
d.appendAndAssertFailed(
invoke(invoker = secondSigner, func = Some("f1"), args = Seq(CONST_BOOLEAN(true), CONST_BOOLEAN(false))),
"negative asset balance"
)
d.appendBlockE(invoke(invoker = secondSigner, func = Some("f1"), args = Seq(CONST_BOOLEAN(false), CONST_BOOLEAN(false)))) should produce(
"negative asset balance"
)
}
}

property("sync invoke payment should be validated before calling dApp if light node is activated") {
withDomain(TransactionStateSnapshot, AddrWithBalance.enoughBalances(defaultSigner, secondSigner)) { d =>
d.appendBlock(setScript(defaultSigner, dApp), setScript(secondSigner, dApp), issueTx)
d.appendBlockE(invoke(invoker = secondSigner, func = Some("f1"), args = Seq(CONST_BOOLEAN(true), CONST_BOOLEAN(false)))) should produce(
"negative asset balance"
)
d.appendBlockE(invoke(invoker = secondSigner, func = Some("f1"), args = Seq(CONST_BOOLEAN(false), CONST_BOOLEAN(false)))) should produce(
"negative asset balance"
)
}
}

property("sync invoke should be correctly rejected and failed on enough balance and RIDE error if light node is activated") {
withDomain(TransactionStateSnapshot, AddrWithBalance.enoughBalances(defaultSigner, secondSigner)) { d =>
d.appendBlock(setScript(defaultSigner, dApp), setScript(secondSigner, dApp), issueTx)
d.appendBlock(transfer(asset = asset))
d.appendBlockE(invoke(invoker = secondSigner, func = Some("f1"), args = Seq(CONST_BOOLEAN(false), CONST_BOOLEAN(true)))) should produce(
"custom error"
)
d.appendAndAssertFailed(
invoke(invoker = secondSigner, func = Some("f1"), args = Seq(CONST_BOOLEAN(true), CONST_BOOLEAN(true))),
"custom error"
)
}
}
}
Loading