diff --git a/src/test/scala/org/ergoplatform/modifiers/mempool/ErgoNodeTransactionSpec.scala b/src/test/scala/org/ergoplatform/modifiers/mempool/ErgoNodeTransactionSpec.scala index 7768778054..e1f8de0ab8 100644 --- a/src/test/scala/org/ergoplatform/modifiers/mempool/ErgoNodeTransactionSpec.scala +++ b/src/test/scala/org/ergoplatform/modifiers/mempool/ErgoNodeTransactionSpec.scala @@ -16,8 +16,6 @@ import org.ergoplatform.wallet.boxes.{ErgoBoxAssetExtractor, ErgoBoxSerializer} import org.ergoplatform.wallet.interpreter.TransactionHintsBag import org.ergoplatform.wallet.protocol.context.InputContext import org.scalacheck.Gen -import sigma.util.BenchmarkUtil -import scorex.crypto.hash.Blake2b256 import scorex.util.encode.Base16 import sigma.{Colls, VersionContext} import sigma.ast.ErgoTree.DefaultHeader @@ -326,34 +324,29 @@ class ErgoNodeTransactionSpec extends ErgoCorePropertyTest with ErgoCompilerHelp property("transaction with too many inputs should be rejected") { - //we assume that verifier must finish verification of any script in less time than 250K hash calculations - // (for the Blake2b256 hash function over a single block input) - val Timeout: Long = { - val hf = Blake2b256 - - //just in case to heat up JVM - (1 to 5000000).foreach(i => hf(s"$i-$i")) - - val t0 = System.currentTimeMillis() - (1 to 250000).foreach(i => hf(s"$i")) - val t = System.currentTimeMillis() - t - t0 - } + // This test used to calibrate a wall-clock budget by timing 250K Blake2b256 hashes and then + // assert that validation fits in it. On a loaded CI machine the calibration window and the + // measurement window get different shares of the CPU, so the assertions flipped at random + // (see issue #2095). What the node is actually protected by is the block cost limit, not the + // clock, so the assertions below are on cost, which is deterministic. val gen = validErgoTransactionGenTemplate(0, 0, 2000, trueLeafGen) val (from, tx) = gen.sample.get tx.statelessValidity().isSuccess shouldBe true - //check that spam transaction is being rejected quickly implicit val verifier: ErgoInterpreter = ErgoInterpreter(parameters) - val (validity, time0) = BenchmarkUtil.measureTime(tx.statefulValidity(from, IndexedSeq(), emptyStateContext)) + + // with the block cost limit in force, the spam transaction is rejected, and validation is + // aborted as soon as the accumulated cost passes the limit + val validity = tx.statefulValidity(from, IndexedSeq(), emptyStateContext) validity.isSuccess shouldBe false - assert(time0 <= Timeout) val cause = validity.failed.get.getMessage cause should startWith(ValidationRules.errorMessage(bsBlockTransactionsCost, "", emptyModifierId, ErgoTransaction.modifierTypeId).take(30)) - //check that spam transaction validation with no cost limit is indeed taking too much time + // with a cost limit high enough to let it through, the same transaction validates, and the cost + // it accumulates is far above the block limit - that gap is what makes it spam, and it does not + // depend on how fast the machine running the test is import Parameters._ val maxCost = (Int.MaxValue - 10) / 10 // cannot use Int.MaxValue directly due to overflow when it is converted to block cost val ps = Parameters(0, DefaultParameters.updated(MaxBlockCostIncrease, maxCost), emptyVSUpdate) @@ -365,11 +358,15 @@ class ErgoNodeTransactionSpec extends ErgoCorePropertyTest with ErgoCompilerHelp Array.fill(3)(0.toByte), ErgoValidationSettingsUpdate.empty, 0.toByte) - val (_, time) = BenchmarkUtil.measureTime( - tx.statefulValidity(from, IndexedSeq(), sc)(verifier) - ) - assert(time > Timeout) + val blockCostLimit = emptyStateContext.currentParameters.maxBlockCost + val fullCost = tx.statefulValidity(from, IndexedSeq(), sc)(verifier).get + // the generator produces exactly `maxInputs` inputs, so this ratio is stable (~4.4x as of today) + fullCost.toLong should be > (blockCostLimit.toLong * 2) + + // and it is rejected by any limit below its cost, one unit below included + val justBelow = stateContextWith(Parameters(0, DefaultParameters.updated(MaxBlockCostIncrease, fullCost - 1), emptyVSUpdate)) + tx.statefulValidity(from, IndexedSeq(), justBelow)(ErgoInterpreter(justBelow.currentParameters)) shouldBe 'failure } property("transaction cost") {