Skip to content

Commit

Permalink
SC-798 Fix total data entry size limit
Browse files Browse the repository at this point in the history
  • Loading branch information
xrtm000 authored and Sergey Nazarov committed Sep 20, 2021
1 parent f256b2b commit bb46509
Show file tree
Hide file tree
Showing 11 changed files with 276 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ object ContractLimits {
val MaxWriteSetSizeInBytes = 5 * 1024
def MaxWriteSetSize(v: StdLibVersion) = 100

val MaxTotalWriteSetSizeInBytes = 15 * 1024

// should conform DataEntry limits
val MaxKeySizeInBytesByVersion: StdLibVersion => Int =
v => if (v >= V4) 400 else 100
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ object UtilsApiRoute {
remainingCalls = ContractLimits.MaxSyncDAppCalls(script.stdLibVersion),
availableActions = ContractLimits.MaxCallableActionsAmount(script.stdLibVersion),
availableData = ContractLimits.MaxWriteSetSize(script.stdLibVersion),
availableDataSize = ContractLimits.MaxTotalWriteSetSizeInBytes,
currentDiff = Diff.empty,
invocationRoot = DAppEnvironment.InvocationTreeTracker(DAppEnvironment.DAppInvocation(address, null, Nil))
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ case class FunctionalitySettings(
minAssetInfoUpdateInterval: Int = 100000,
minBlockTime: FiniteDuration = 15.seconds,
delayDelta: Int = 8,
syncDAppCheckPaymentsHeight: Int = 0
syncDAppCheckPaymentsHeight: Int = 0,
checkTotalDataEntriesBytesHeight: Int = 0
) {
val allowLeasedBalanceTransferUntilHeight: Int = blockVersion3AfterHeight
val allowTemporaryNegativeUntil = lastTimeBasedForkParameter
Expand Down Expand Up @@ -116,7 +117,8 @@ object FunctionalitySettings {
blockVersion3AfterHeight = 795000,
doubleFeaturesPeriodsAfterHeight = 810000,
estimatorPreCheckHeight = 1847610,
syncDAppCheckPaymentsHeight = 2746200
syncDAppCheckPaymentsHeight = 2746200,
checkTotalDataEntriesBytesHeight = 2771954
)

val TESTNET = apply(
Expand All @@ -126,7 +128,8 @@ object FunctionalitySettings {
doubleFeaturesPeriodsAfterHeight = Int.MaxValue,
lastTimeBasedForkParameter = 1492560000000L,
estimatorPreCheckHeight = 817380,
syncDAppCheckPaymentsHeight = 1682350
syncDAppCheckPaymentsHeight = 1682350,
checkTotalDataEntriesBytesHeight = 1711600
)

val STAGENET = apply(
Expand All @@ -135,7 +138,8 @@ object FunctionalitySettings {
doubleFeaturesPeriodsAfterHeight = 1000000000,
preActivatedFeatures = (1 to 13).map(_.toShort -> 0).toMap,
minAssetInfoUpdateInterval = 10,
syncDAppCheckPaymentsHeight = 967300
syncDAppCheckPaymentsHeight = 967300,
checkTotalDataEntriesBytesHeight = 991912
)

val configPath = "waves.blockchain.custom.functionality"
Expand Down
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.wavesplatform.state.diffs.invoke

import cats.instances.map._
import cats.syntax.either._
import cats.Id
import cats.syntax.semigroup._
import com.google.common.base.Throwables
import com.google.protobuf.ByteString
Expand All @@ -18,6 +19,7 @@ import com.wavesplatform.lang.directives.values._
import com.wavesplatform.lang.script.Script
import com.wavesplatform.lang.v1.ContractLimits
import com.wavesplatform.lang.v1.compiler.Terms.{FUNCTION_CALL, _}
import com.wavesplatform.lang.v1.evaluator.Log
import com.wavesplatform.lang.v1.traits.Environment
import com.wavesplatform.lang.v1.traits.domain.Tx.{BurnPseudoTx, ReissuePseudoTx, ScriptTransfer, SponsorFeePseudoTx}
import com.wavesplatform.lang.v1.traits.domain.{AssetTransfer, _}
Expand Down Expand Up @@ -289,7 +291,7 @@ object InvokeDiffsCommon {
.foldLeft(Map[Address, Portfolio]())(_ |+| _)
)

private def dataItemToEntry(item: DataOp): DataEntry[_] =
def dataItemToEntry(item: DataOp): DataEntry[_] =
item match {
case DataItem.Bool(k, b) => BooleanDataEntry(k, b)
case DataItem.Str(k, b) => StringDataEntry(k, b)
Expand Down Expand Up @@ -619,9 +621,30 @@ object InvokeDiffsCommon {
case Success(s) => s
}

case class StepInfo(
feeInWaves: Long,
feeInAttachedAsset: Long,
scriptsRun: Int
)
def checkCallResultLimits(
blockchain: Blockchain,
usedComplexity: Long,
log: Log[Id],
actionsCount: Int,
dataCount: Int,
dataSize: Int,
availableActions: Int,
availableData: Int,
availableDataSize: Int
): TracedResult[ValidationError, Unit] = {
def error(message: String) = TracedResult(Left(FailedTransactionError.dAppExecution(message, usedComplexity, log)))
val checkSizeHeight = blockchain.settings.functionalitySettings.checkTotalDataEntriesBytesHeight

if (dataCount > availableData)
error("Stored data count limit is exceeded")
else if (dataSize > availableDataSize && blockchain.height >= checkSizeHeight) {
val limit = ContractLimits.MaxTotalWriteSetSizeInBytes
val actual = limit + dataSize - availableDataSize
error(s"Storing data size should not exceed $limit, actual: $actual bytes")
}
else if (actionsCount > availableActions)
error("Actions count limit is exceeded")
else
TracedResult(Right(()))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,12 @@ object InvokeScriptDiff {
remainingCalls: Int,
remainingActions: Int,
remainingData: Int,
remainingDataSize: Int,
calledAddresses: Set[Address],
invocationRoot: DAppEnvironment.InvocationTreeTracker
)(
tx: InvokeScript
): CoevalR[(Diff, EVALUATED, Int, Int)] = {
): CoevalR[(Diff, EVALUATED, Int, Int, Int)] = {
val dAppAddress = tx.dAppAddress
val invoker = tx.senderDApp

Expand Down Expand Up @@ -149,7 +150,7 @@ object InvokeScriptDiff {
)

result <- for {
(diff, (scriptResult, log), availableActions, availableData) <- {
(diff, (scriptResult, log), availableActions, availableData, availableDataSize) <- {
stats.invokedScriptExecution.measureForType(InvokeScriptTransaction.typeId)({
val height = blockchain.height
val invocation = ContractEvaluator.Invocation(
Expand Down Expand Up @@ -181,6 +182,7 @@ object InvokeScriptDiff {
remainingCalls - 1,
remainingActions,
remainingData,
remainingDataSize,
paymentsPartInsideDApp,
invocationRoot
)
Expand All @@ -195,7 +197,13 @@ object InvokeScriptDiff {
complexityAfterPayments,
remainingComplexity
).map(TracedResult(_))
).map(result => (environment.currentDiff |+| paymentsPartToResolve, result, environment.availableActions, environment.availableData))
).map(
result =>
(environment.currentDiff |+| paymentsPartToResolve,
result,
environment.availableActions,
environment.availableData,
environment.availableDataSize))
})
}
_ = invocationRoot.setLog(log)
Expand Down Expand Up @@ -232,39 +240,45 @@ object InvokeScriptDiff {
)
}

process = { (actions: List[CallableAction], unusedComplexity: Int, actionsCount: Int, dataCount: Int, ret: EVALUATED) =>
if (dataCount > availableData) {
val usedComplexity = remainingComplexity - unusedComplexity
val error = FailedTransactionError.dAppExecution("Stored data count limit is exceeded", usedComplexity, log)
traced(error.asLeft[(Diff, EVALUATED, Int, Int)])
} else {
if (actionsCount > availableActions) {
val usedComplexity = remainingComplexity - unusedComplexity
val error = FailedTransactionError.dAppExecution("Actions count limit is exceeded", usedComplexity, log)
traced(error.asLeft[(Diff, EVALUATED, Int, Int)])
} else {
doProcessActions(actions, unusedComplexity).map((_, ret, availableActions - actionsCount, availableData - dataCount))
}
}
process = { (actions: List[CallableAction], unusedComplexity: Int, actionsCount: Int, dataCount: Int, dataSize: Int, ret: EVALUATED) =>
for {
_ <- CoevalR(Coeval(InvokeDiffsCommon.checkCallResultLimits(
blockchain,
remainingComplexity - unusedComplexity,
log,
actionsCount,
dataCount,
dataSize,
availableActions,
availableData,
availableDataSize
)))
diff <- doProcessActions(actions, unusedComplexity)
} yield (diff, ret, availableActions - actionsCount, availableData - dataCount, availableDataSize - dataSize)
}
(actionsDiff, evaluated, remainingActions1, remainingData1) <- scriptResult match {

(actionsDiff, evaluated, remainingActions1, remainingData1, remainingDataSize1) <- scriptResult match {
case ScriptResultV3(dataItems, transfers, unusedComplexity) =>
val dataSize = dataItems.map(d => InvokeDiffsCommon.dataItemToEntry(d).toBytes.length).sum
val dataCount = dataItems.length
val actionsCount = transfers.length
process(dataItems ::: transfers, unusedComplexity, actionsCount, dataCount, unit)
process(dataItems ::: transfers, unusedComplexity, actionsCount, dataCount, dataSize, unit)
case ScriptResultV4(actions, unusedComplexity, ret) =>
val dataCount = actions.count(_.isInstanceOf[DataOp])
val dataItems = actions.collect { case d: DataOp => InvokeDiffsCommon.dataItemToEntry(d) }
val dataCount = dataItems.length
val dataSize = dataItems.map(_.toBytes.length).sum
val actionsCount = actions.length - dataCount
process(actions, unusedComplexity, actionsCount, dataCount, ret)
case _: IncompleteResult if limitedExecution => doProcessActions(Nil, 0).map((_, unit, availableActions, availableData))
process(actions, unusedComplexity, actionsCount, dataCount, dataSize, ret)
case _: IncompleteResult if limitedExecution =>
doProcessActions(Nil, 0).map((_, unit, availableActions, availableData, availableDataSize))
case r: IncompleteResult =>
val usedComplexity = remainingComplexity - r.unusedComplexity
val error = FailedTransactionError.dAppExecution(s"Invoke complexity limit = $totalComplexityLimit is exceeded", usedComplexity, log)
traced(error.asLeft[(Diff, EVALUATED, Int, Int)])
traced(error.asLeft[(Diff, EVALUATED, Int, Int, Int)])
}
resultDiff = diff.copy(scriptsComplexity = 0) |+| actionsDiff |+| Diff.empty.copy(scriptsComplexity = paymentsComplexity)
_ = invocationRoot.setResult(scriptResult)
} yield (resultDiff, evaluated, remainingActions1, remainingData1)
} yield (resultDiff, evaluated, remainingActions1, remainingData1, remainingDataSize1)
} yield result

case _ => traced(Left(GenericError(s"No contract at address ${tx.dAppAddress}")))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ object InvokeScriptTransactionDiff {
log: Log[Id],
availableActions: Int,
availableData: Int,
availableDataSize: Int,
limit: Int
)

Expand Down Expand Up @@ -103,14 +104,16 @@ object InvokeScriptTransactionDiff {
paymentsComplexity,
blockchain
)
} yield MainScriptResult(
environment.currentDiff,
result,
log,
environment.availableActions,
environment.availableData,
fullLimit - paymentsComplexity
)
} yield
MainScriptResult(
environment.currentDiff,
result,
log,
environment.availableActions,
environment.availableData,
environment.availableDataSize,
fullLimit - paymentsComplexity
)
}

TracedResult(
Expand All @@ -129,7 +132,7 @@ object InvokeScriptTransactionDiff {
}

for {
MainScriptResult(invocationDiff, scriptResult, log, availableActions, availableData, limit) <- executeMainScript()
MainScriptResult(invocationDiff, scriptResult, log, availableActions, availableData, availableDataSize, limit) <- executeMainScript()
otherIssues = invocationDiff.scriptResults.get(tx.id()).fold(Seq.empty[Issue])(allIssues)

doProcessActions = InvokeDiffsCommon.processActions(
Expand All @@ -149,17 +152,26 @@ object InvokeScriptTransactionDiff {

process = (actions: List[CallableAction], unusedComplexity: Long) => {
val storingComplexity = if (blockchain.storeEvaluatedComplexity) limit - unusedComplexity else fixedInvocationComplexity
val dataCount = actions.count(_.isInstanceOf[DataOp])
if (dataCount > availableData) {
TracedResult(Left(FailedTransactionError.dAppExecution("Stored data count limit is exceeded", storingComplexity, log)))
} else {
val actionsCount = actions.length - dataCount
if (actionsCount > availableActions) {
TracedResult(Left(FailedTransactionError.dAppExecution("Actions count limit is exceeded", storingComplexity, log)))
} else {
doProcessActions(actions, storingComplexity.toInt)
}
}

val dataItems = actions.collect { case d: DataOp => InvokeDiffsCommon.dataItemToEntry(d) }
val dataCount = dataItems.length
val dataSize = dataItems.map(_.toBytes.length).sum
val actionsCount = actions.length - dataCount

for {
_ <- InvokeDiffsCommon.checkCallResultLimits(
blockchain,
storingComplexity,
log,
actionsCount,
dataCount,
dataSize,
availableActions,
availableData,
availableDataSize
)
diff <- doProcessActions(actions, storingComplexity.toInt)
} yield diff
}

resultDiff <- scriptResult match {
Expand Down Expand Up @@ -248,6 +260,7 @@ object InvokeScriptTransactionDiff {
ContractLimits.MaxSyncDAppCalls(version),
ContractLimits.MaxCallableActionsAmount(version),
ContractLimits.MaxWriteSetSize(version),
ContractLimits.MaxTotalWriteSetSizeInBytes,
if (version < V5) Diff.empty else InvokeDiffsCommon.paymentsPart(tx, dAppAddress, Map()),
invocationTracker
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ class DAppEnvironment(
var remainingCalls: Int,
var availableActions: Int,
var availableData: Int,
var availableDataSize: Int,
var currentDiff: Diff,
val invocationRoot: DAppEnvironment.InvocationTreeTracker
) extends WavesEnvironment(nByte, in, h, blockchain, tthis, ds, tx.map(_.id()).getOrElse(ByteStr.empty)) {
Expand Down Expand Up @@ -356,7 +357,7 @@ class DAppEnvironment(
val invocation = DAppEnvironment.DAppInvocation(invoke.dAppAddress, invoke.funcCall, invoke.payments)
invocationRoot.record(invocation)
}
(diff, evaluated, remainingActions, remainingData) <- InvokeScriptDiff( // This is a recursive call
(diff, evaluated, remainingActions, remainingData, remainingDataSize) <- InvokeScriptDiff( // This is a recursive call
mutableBlockchain,
blockchain.settings.functionalitySettings.allowInvalidReissueInSameBlockUntilTimestamp + 1,
limitedExecution,
Expand All @@ -365,6 +366,7 @@ class DAppEnvironment(
remainingCalls,
availableActions,
availableData,
availableDataSize,
if (reentrant) calledAddresses else calledAddresses + invoke.senderAddress,
invocationTracker
)(invoke)
Expand All @@ -378,6 +380,7 @@ class DAppEnvironment(
remainingCalls = remainingCalls - 1
availableActions = remainingActions
availableData = remainingData
availableDataSize = remainingDataSize
(evaluated, diff.scriptsComplexity.toInt)
}

Expand Down
Loading

0 comments on commit bb46509

Please sign in to comment.