Skip to content

Commit

Permalink
Disallow negative miner balance and base target
Browse files Browse the repository at this point in the history
Fix asserts
  • Loading branch information
Karasiq committed Nov 29, 2021
1 parent a5181ac commit 2da8b31
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.wavesplatform.consensus

import scala.util.Try

import com.wavesplatform.account.{PrivateKey, PublicKey}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.consensus.PoSCalculator.HitSize
Expand All @@ -20,8 +22,8 @@ trait PoSCalculator {
}

object PoSCalculator {
private[consensus] val HitSize: Int = 8
val MinBaseTarget: Long = 9
private[consensus] val HitSize: Int = 8
val MinBaseTarget: Long = 9

def generationSignature(signature: ByteStr, publicKey: PublicKey): Array[Byte] = {
val s = new Array[Byte](crypto.DigestLength * 2)
Expand Down Expand Up @@ -121,11 +123,12 @@ case class FairPoSCalculator(minBlockTime: Int, delayDelta: Int) extends PoSCalc
maybeGreatGrandParentTimestamp match {
case None =>
prevBaseTarget

case Some(ts) =>
val avg = (timestamp - ts) / 3 / 1000
val avg = (timestamp - ts) / 3 / 1000
val prevBaseTarget1pct = (prevBaseTarget / 100) max 1
if (avg > maxDelay) prevBaseTarget + prevBaseTarget1pct
else if (avg < minDelay) (prevBaseTarget - prevBaseTarget1pct) max 1
if (avg > maxDelay) Try(math.addExact(prevBaseTarget, prevBaseTarget1pct)).getOrElse(Long.MaxValue)
else if (avg < minDelay) math.subtractExact(prevBaseTarget, prevBaseTarget1pct) max 1
else prevBaseTarget
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ object DiffToStateApplier {
val bs = Map.newBuilder[Asset, Long]

if (portfolioDiff.balance != 0) {
bs += Waves -> (blockchain.balance(address, Waves) + portfolioDiff.balance)
bs += Waves -> math.addExact(blockchain.balance(address, Waves), portfolioDiff.balance).ensuring(_ >= 0)
}

portfolioDiff.assets.collect {
case (asset, balanceDiff) if balanceDiff != 0 =>
bs += asset -> (blockchain.balance(address, asset) + balanceDiff)
case (asset, delta) if delta != 0 =>
bs += asset -> math.addExact(blockchain.balance(address, asset), delta).ensuring(_ >= 0)
}

balances += address -> bs.result()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import com.wavesplatform.state.patch._
import com.wavesplatform.state.reader.CompositeBlockchain
import com.wavesplatform.transaction.{Asset, Transaction}
import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves}
import com.wavesplatform.transaction.TxValidationError.{ActivationError, _}
import com.wavesplatform.transaction.TxValidationError._
import com.wavesplatform.transaction.smart.script.trace.TracedResult
import com.wavesplatform.utils.ScorexLogging

Expand Down Expand Up @@ -152,7 +152,7 @@ object BlockDiffer extends ScorexLogging {
val txDiffer = TransactionDiffer(prevBlockTimestamp, timestamp, verify) _
val hasSponsorship = currentBlockHeight >= Sponsorship.sponsoredFeesSwitchHeight(blockchain)

txs
val result = txs
.foldLeft(TracedResult(Result(initDiff, 0L, 0L, initConstraint, DetailedDiff(initDiff, Nil)).asRight[ValidationError])) {
case (acc @ TracedResult(Left(_), _, _), _) => acc
case (TracedResult(Right(Result(currDiff, carryFee, currTotalFee, currConstraint, DetailedDiff(parentDiff, txDiffs))), _, _), tx) =>
Expand Down Expand Up @@ -189,6 +189,11 @@ object BlockDiffer extends ScorexLogging {
}
}
}

for {
result <- result
_ <- CommonValidation.disallowNegativeBalances(blockchain, result.diff)
} yield result
}

private def patchesDiff(blockchain: Blockchain): Diff = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
package com.wavesplatform.state.diffs

import scala.util.{Left, Right}

import cats._
import cats.data.Validated
import cats.instances.lazyList._
import cats.syntax.traverse._
import com.wavesplatform.account.{Address, AddressScheme}
import com.wavesplatform.features.{BlockchainFeature, BlockchainFeatures}
import com.wavesplatform.features.OverdraftValidationProvider._
import com.wavesplatform.features.{BlockchainFeature, BlockchainFeatures, RideVersionProvider}
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.lang.directives.values._
import com.wavesplatform.lang.script.{ContractScript, Script}
import com.wavesplatform.lang.script.ContractScript.ContractScriptImpl
import com.wavesplatform.lang.script.v1.ExprScript
import com.wavesplatform.lang.script.{ContractScript, Script}
import com.wavesplatform.settings.FunctionalitySettings
import com.wavesplatform.state._
import com.wavesplatform.transaction._
import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves}
import com.wavesplatform.transaction.TxValidationError._
import com.wavesplatform.transaction._
import com.wavesplatform.transaction.assets._
import com.wavesplatform.transaction.assets.exchange._
import com.wavesplatform.transaction.lease._
import com.wavesplatform.transaction.smart.{InvokeScriptTransaction, SetScriptTransaction}
import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment
import com.wavesplatform.transaction.smart.{InvokeExpressionTransaction, InvokeScriptTransaction, SetScriptTransaction}
import com.wavesplatform.transaction.transfer._

import scala.util.{Left, Right}

object CommonValidation {

def disallowSendingGreaterThanBalance[T <: Transaction](blockchain: Blockchain, blockTime: Long, tx: T): Either[ValidationError, T] =
if (blockTime >= blockchain.settings.functionalitySettings.allowTemporaryNegativeUntil) {
def checkTransfer(
Expand Down Expand Up @@ -106,6 +110,22 @@ object CommonValidation {
}
} else Right(tx)

def disallowNegativeBalances(blockchain: Blockchain, diff: Diff): Either[ValidationError, Diff] = {
val resultBalances = for {
(address, pf) <- diff.portfolios.to(LazyList)
(asset, delta) <- pf.assets ++ Map(Waves -> pf.balance)
newBalance = safeSum(blockchain.balance(address, asset), delta)
} yield (address, asset, newBalance)

resultBalances.collect {
case tuple @ (_, _, balance) if balance < 0 => Validated.invalidNel(tuple)
case _ => Validated.validNel(())
}.sequence match {
case Validated.Valid(_) => Right(diff)
case Validated.Invalid(tuples) => Left(GenericError(s"Diff contains negative applied balances: ${tuples.toList.mkString("[", ", ", "]")}"))
}
}

def disallowDuplicateIds[T <: Transaction](blockchain: Blockchain, tx: T): Either[ValidationError, T] = tx match {
case _: PaymentTransaction => Right(tx)
case _ =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package com.wavesplatform.consensus

import scala.io.Source
import scala.util.Random

import cats.data.NonEmptyList
import com.wavesplatform.account.{KeyPair, PrivateKey, PublicKey}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.{Base58, EitherExt2}
import com.wavesplatform.crypto
import com.wavesplatform.test.PropSpec

import scala.io.Source
import scala.util.Random

class FairPoSCalculatorTest extends PropSpec {
import FairPoSCalculatorTest._
import PoSCalculator._
Expand All @@ -33,6 +33,11 @@ class FairPoSCalculatorTest extends PropSpec {
baseTarget shouldBe 1
}

property("Base target should not be overflowed") {
val baseTarget = FairPoSCalculator(60, 0).calculateBaseTarget(60, 1, Long.MaxValue, 2, Some(1), 3e10.toLong)
baseTarget shouldBe Long.MaxValue
}

property("Correct consensus parameters distribution of blocks generated with FairPoS") {

val miners = mkMiners
Expand Down Expand Up @@ -133,8 +138,8 @@ class FairPoSCalculatorTest extends PropSpec {

object FairPoSCalculatorTest {
import play.api.libs.functional.syntax._
import play.api.libs.json.Reads._
import play.api.libs.json._
import play.api.libs.json.Reads._

case class Input(
privateKey: PrivateKey,
Expand Down
2 changes: 2 additions & 0 deletions node/src/test/scala/com/wavesplatform/db/WithState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,8 @@ trait WithDomain extends WithState { _: Suite =>
BlockchainFeatures.BlockV5
)

val RideV4WithRewards = RideV4.addFeatures(BlockchainFeatures.BlockReward)

val RideV5 = RideV4.addFeatures(BlockchainFeatures.SynchronousCalls)
val RideV6 = RideV5.addFeatures(BlockchainFeatures.RideV6)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.wavesplatform.mining

import com.wavesplatform.db.WithDomain
import com.wavesplatform.test.FlatSpec
import com.wavesplatform.transaction.TxHelpers
import org.scalatest.matchers.should.Matchers

class MinerBalanceOverflowTest extends FlatSpec with Matchers with WithDomain {
"Miner balance" should "not overflow" in withDomain(DomainPresets.RideV4WithRewards) { d =>
d.helpers.creditWavesToDefaultSigner(Long.MaxValue - 6_0000_0000L)
for (_ <- 1 to 10) intercept[RuntimeException](d.appendBlock()).toString should include("Diff contains negative applied balances")
val minerBalance = d.blockchain.balance(TxHelpers.defaultAddress)
minerBalance shouldBe >(0L)
}
}

0 comments on commit 2da8b31

Please sign in to comment.