Skip to content

Commit

Permalink
Disallow negative miner balance and base target
Browse files Browse the repository at this point in the history
  • Loading branch information
Karasiq committed Sep 20, 2021
1 parent 8144b44 commit 2695b5a
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 27 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 @@ -156,7 +156,7 @@ object BlockDiffer extends ScorexLogging {
patch.lift(CompositeBlockchain(blockchain, prevDiff)).fold(prevDiff)(prevDiff |+| _)
}

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

for {
result <- result
_ <- CommonValidation.disallowNegativeBalances(blockchain, result.diff)
} yield result
}
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,32 @@
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.OverdraftValidationProvider._
import com.wavesplatform.features.{BlockchainFeature, BlockchainFeatures}
import com.wavesplatform.features.OverdraftValidationProvider._
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.Payment
import com.wavesplatform.transaction.smart.{InvokeScriptTransaction, SetScriptTransaction}
import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment
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 +108,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.invalid(tuple)
case _ => Validated.valid(())
}.map(_.toValidatedNel).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
14 changes: 8 additions & 6 deletions node/src/test/scala/com/wavesplatform/db/WithState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@ package com.wavesplatform.db
import java.nio.file.Files

import cats.Monoid
import com.wavesplatform.{NTPTime, TestHelpers}
import com.wavesplatform.account.Address
import com.wavesplatform.block.Block
import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.database.{LevelDBFactory, LevelDBWriter, TestStorageFactory, loadActiveLeases}
import com.wavesplatform.database.{loadActiveLeases, LevelDBFactory, LevelDBWriter, TestStorageFactory}
import com.wavesplatform.events.BlockchainUpdateTriggers
import com.wavesplatform.features.{BlockchainFeature, BlockchainFeatures}
import com.wavesplatform.history.Domain
import com.wavesplatform.lagonaki.mocks.TestBlock
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.mining.MiningConstraint
import com.wavesplatform.settings.{BlockchainSettings, FunctionalitySettings, TestSettings, WavesSettings, loadConfig, TestFunctionalitySettings => TFS}
import com.wavesplatform.state.diffs.{BlockDiffer, produce}
import com.wavesplatform.settings.{loadConfig, BlockchainSettings, FunctionalitySettings, TestSettings, WavesSettings, TestFunctionalitySettings => TFS}
import com.wavesplatform.state.{Blockchain, BlockchainUpdaterImpl, Diff}
import com.wavesplatform.state.diffs.{produce, BlockDiffer}
import com.wavesplatform.state.reader.CompositeBlockchain
import com.wavesplatform.state.utils.TestLevelDB
import com.wavesplatform.state.{Blockchain, BlockchainUpdaterImpl, Diff}
import com.wavesplatform.transaction.smart.script.trace.TracedResult
import com.wavesplatform.transaction.{Asset, Transaction}
import com.wavesplatform.{NTPTime, TestHelpers}
import com.wavesplatform.transaction.smart.script.trace.TracedResult
import monix.reactive.Observer
import monix.reactive.subjects.{PublishSubject, Subject}
import org.iq80.leveldb.{DB, Options}
Expand Down Expand Up @@ -193,6 +193,8 @@ trait WithDomain extends WithState { _: Suite =>
BlockchainFeatures.BlockV5
)

val RideV4WithRewards = RideV4.addFeatures(BlockchainFeatures.BlockReward)

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

Expand Down
11 changes: 11 additions & 0 deletions node/src/test/scala/com/wavesplatform/history/Domain.scala
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,17 @@ case class Domain(db: DB, blockchainUpdater: BlockchainUpdaterImpl, levelDBWrite

CommonBlocksApi(blockchainUpdater, loadBlockMetaAt(db, blockchainUpdater), loadBlockInfoAt(db, blockchainUpdater))
}

//noinspection ScalaStyle
object helpers {
def creditWaves(address: Address, amount: Long = 10_0000_0000): Unit = {
appendBlock(TxHelpers.genesis(address, amount))
}

def creditWavesToDefaultSigner(amount: Long = 10_0000_0000): Unit = {
creditWaves(TxHelpers.defaultAddress, amount)
}
}
}

object Domain {
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 2695b5a

Please sign in to comment.