diff --git a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbScanAppRewardsStoreTest.scala b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbScanAppRewardsStoreTest.scala index bdfd83565e..64ade0d6c8 100644 --- a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbScanAppRewardsStoreTest.scala +++ b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbScanAppRewardsStoreTest.scala @@ -38,8 +38,6 @@ class DbScanAppRewardsStoreTest "DbScanAppRewardsStore" should { - // -- Test 1: Insert and read back a single row per table ---------------- - "insert and read back app_activity_party_totals" in { for { (store, historyId) <- newStore() @@ -203,8 +201,6 @@ class DbScanAppRewardsStoreTest } } - // -- Test 2: Batch inserts ---------------------------------------------- - "batch insert multiple app_activity_party_totals and spot-check" in { for { (store, historyId) <- newStore() @@ -250,8 +246,6 @@ class DbScanAppRewardsStoreTest } } - // -- Test 5: Duplicate key handling (reject) ---------------------------- - "reject duplicate app_activity_party_totals on PK conflict" in { for { (store, historyId) <- newStore() @@ -307,10 +301,11 @@ class DbScanAppRewardsStoreTest result.getMessage should (include("unique constraint") or include("duplicate key")) } } + } - // -- Aggregation tests --------------------------------------------------- + "aggregateActivityTotals" should { - "aggregateActivityTotals — single round, single party" in { + "single round, single party" in { for { (store, historyId) <- newStore() _ <- markRoundComplete(historyId, roundNumber) @@ -330,7 +325,7 @@ class DbScanAppRewardsStoreTest } } - "aggregateActivityTotals — multiple parties with correct GROUP BY and seq_nums" in { + "multiple parties with correct GROUP BY and seq_nums" in { for { (store, historyId) <- newStore() _ <- markRoundComplete(historyId, roundNumber) @@ -371,7 +366,7 @@ class DbScanAppRewardsStoreTest } } - "aggregateActivityTotals — empty round produces zero totals" in { + "empty round produces zero totals" in { for { (store, historyId) <- newStore() _ <- markRoundComplete(historyId, roundNumber) @@ -387,7 +382,7 @@ class DbScanAppRewardsStoreTest } } - "aggregateActivityTotals — only aggregates records from own history_id" in { + "only aggregates records from own history_id" in { for { (store1, historyId1) <- newStore() (_, historyId2) <- newStore() @@ -409,7 +404,7 @@ class DbScanAppRewardsStoreTest } } - "aggregateActivityTotals — re-run for same round raises error" in { + "re-run for same round raises error" in { for { (store, historyId) <- newStore() _ <- markRoundComplete(historyId, roundNumber) @@ -421,7 +416,7 @@ class DbScanAppRewardsStoreTest } } - "aggregateActivityTotals — rejects the first ingested round (possibly partial)" in { + "rejects the first ingested round (possibly partial)" in { for { (store, historyId) <- newStore() // roundNumber is the first ingested round, which may be partial @@ -438,7 +433,7 @@ class DbScanAppRewardsStoreTest } } - "aggregateActivityTotals — rejects round whose OpenMiningRound is not yet archived" in { + "rejects round whose OpenMiningRound is not yet archived" in { for { (store, historyId) <- newStore() _ <- insertActivityMeta( @@ -454,7 +449,7 @@ class DbScanAppRewardsStoreTest } } - "aggregateActivityTotals — succeeds for round 0 when marked complete" in { + "succeeds for round 0 when marked complete" in { for { (store, historyId) <- newStore() _ <- markRoundComplete(historyId, 0L) @@ -468,7 +463,7 @@ class DbScanAppRewardsStoreTest } } - "aggregateActivityTotals — rejects round 0 when not marked complete" in { + "rejects round 0 when not marked complete" in { for { (store, historyId) <- newStore() _ <- insertActivityRecord(historyId, 0L, Seq("alice::provider"), Seq(500L)) @@ -478,212 +473,267 @@ class DbScanAppRewardsStoreTest result.getMessage should include("Incomplete app activity for round 0") } } + } - "roundsWithComputedRewards" should { + "roundsWithComputedRewards" should { - "returns empty set for empty input" in { - for { - (store, _) <- newStore() - result <- store.roundsWithComputedRewards(Seq.empty) - } yield { - result shouldBe Set.empty - } + "returns empty set for empty input" in { + for { + (store, _) <- newStore() + result <- store.roundsWithComputedRewards(Seq.empty) + } yield { + result shouldBe Set.empty } + } - "returns correct subset" in { - for { - (store, historyId) <- newStore() - _ <- store.insertAppRewardRootHashes( - Seq( - AppRewardRootHashT(historyId, 10L, RewardHash(Array[Byte](1, 2, 3, 4))), - AppRewardRootHashT(historyId, 20L, RewardHash(Array[Byte](5, 6, 7, 8))), - AppRewardRootHashT(historyId, 30L, RewardHash(Array[Byte](9, 10, 11, 12))), - ) + "returns correct subset" in { + for { + (store, historyId) <- newStore() + _ <- store.insertAppRewardRootHashes( + Seq( + AppRewardRootHashT(historyId, 10L, RewardHash(Array[Byte](1, 2, 3, 4))), + AppRewardRootHashT(historyId, 20L, RewardHash(Array[Byte](5, 6, 7, 8))), + AppRewardRootHashT(historyId, 30L, RewardHash(Array[Byte](9, 10, 11, 12))), ) - result <- store.roundsWithComputedRewards(Seq(10L, 15L, 20L, 25L)) - } yield { - result shouldBe Set(10L, 20L) - } + ) + result <- store.roundsWithComputedRewards(Seq(10L, 15L, 20L, 25L)) + } yield { + result shouldBe Set(10L, 20L) } + } - "returns empty set when no matches" in { - for { - (store, historyId) <- newStore() - _ <- store.insertAppRewardRootHashes( - Seq( - AppRewardRootHashT(historyId, 10L, RewardHash(Array[Byte](1, 2, 3, 4))) - ) + "returns empty set when no matches" in { + for { + (store, historyId) <- newStore() + _ <- store.insertAppRewardRootHashes( + Seq( + AppRewardRootHashT(historyId, 10L, RewardHash(Array[Byte](1, 2, 3, 4))) ) - result <- store.roundsWithComputedRewards(Seq(20L, 30L)) - } yield { - result shouldBe Set.empty - } + ) + result <- store.roundsWithComputedRewards(Seq(20L, 30L)) + } yield { + result shouldBe Set.empty } + } + } + + "computeAndStoreRewards" should { + "returns correct summary counts" in { + for { + (store, historyId) <- newStore() + _ <- markRoundComplete(historyId, roundNumber) + // 3 activity records, 2 parties (alice in 2 records, bob in 2) + _ <- insertActivityRecord( + historyId, + roundNumber, + Seq("alice::provider", "bob::provider"), + Seq(3000000L, 2000000L), + ) + _ <- insertActivityRecord( + historyId, + roundNumber, + Seq("alice::provider"), + Seq(1000000L), + ) + _ <- insertActivityRecord( + historyId, + roundNumber, + Seq("bob::provider"), + Seq(500000L), + ) + summary <- store.computeAndStoreRewards( + roundNumber, + batchSize = 100, + testInputs, + ) + } yield { + summary.activePartiesCount shouldBe 2L + summary.activityRecordsCount shouldBe 4L // sum of per-party counts: alice=2 + bob=2 + summary.rewardedPartiesCount shouldBe 2L + summary.batchesCreatedCount should be >= 1L + } } - "computeAndStoreRewards" should { + "non-zero threshold excludes low-activity parties from rewards" in { + for { + (store, historyId) <- newStore() + _ <- markRoundComplete(historyId, roundNumber) + // alice has high activity, bob has low activity + _ <- insertActivityRecord( + historyId, + roundNumber, + Seq("alice::provider", "bob::provider"), + Seq(5000000L, 50000L), + ) + // totalIssuanceForFeaturedAppRewards is 0.45, and alice gets almost all of it. Only alice will therefore be above the threshold of 0.4 + nonZeroThresholdInputs = testInputs.copy( + appRewardCouponThreshold = RewardComputationInputs.fromBigDecimal(BigDecimal("0.4")) + ) + summary <- store.computeAndStoreRewards( + roundNumber, + batchSize = 100, + nonZeroThresholdInputs, + ) + rewardPartyTotals <- store.getAppRewardPartyTotalsByRound(roundNumber) + } yield { + summary.activePartiesCount shouldBe 2L + summary.rewardedPartiesCount shouldBe 1L // only alice above threshold + rewardPartyTotals should have size 1 + rewardPartyTotals.head.appProviderParty shouldBe "alice::provider" + } + } - "returns correct summary counts" in { - for { - (store, historyId) <- newStore() - _ <- markRoundComplete(historyId, roundNumber) - // 3 activity records, 2 parties (alice in 2 records, bob in 2) - _ <- insertActivityRecord( - historyId, - roundNumber, - Seq("alice::provider", "bob::provider"), - Seq(3000000L, 2000000L), - ) - _ <- insertActivityRecord( - historyId, - roundNumber, - Seq("alice::provider"), - Seq(1000000L), - ) - _ <- insertActivityRecord( - historyId, - roundNumber, - Seq("bob::provider"), - Seq(500000L), - ) - summary <- store.computeAndStoreRewards( - roundNumber, - batchSize = 100, - testInputs, - ) - } yield { - summary.activePartiesCount shouldBe 2L - summary.activityRecordsCount shouldBe 4L // sum of per-party counts: alice=2 + bob=2 - summary.rewardedPartiesCount shouldBe 2L - summary.batchesCreatedCount should be >= 1L - } + "empty round returns zero counts" in { + for { + (store, historyId) <- newStore() + _ <- markRoundComplete(historyId, roundNumber) + summary <- store.computeAndStoreRewards( + roundNumber, + batchSize = 100, + testInputs, + ) + } yield { + summary.activePartiesCount shouldBe 0L + summary.activityRecordsCount shouldBe 0L + summary.rewardedPartiesCount shouldBe 0L + summary.batchesCreatedCount shouldBe 1L // empty root batch } + } - "non-zero threshold excludes low-activity parties from rewards" in { - for { - (store, historyId) <- newStore() - _ <- markRoundComplete(historyId, roundNumber) - // alice has high activity, bob has low activity - _ <- insertActivityRecord( - historyId, - roundNumber, - Seq("alice::provider", "bob::provider"), - Seq(5000000L, 50000L), - ) - // totalIssuanceForFeaturedAppRewards is 0.45, and alice gets almost all of it. Only alice will therefore be above the threshold of 0.4 - nonZeroThresholdInputs = testInputs.copy( - appRewardCouponThreshold = RewardComputationInputs.fromBigDecimal(BigDecimal("0.4")) - ) - summary <- store.computeAndStoreRewards( - roundNumber, - batchSize = 100, - nonZeroThresholdInputs, - ) - rewardPartyTotals <- store.getAppRewardPartyTotalsByRound(roundNumber) - } yield { - summary.activePartiesCount shouldBe 2L - summary.rewardedPartiesCount shouldBe 1L // only alice above threshold - rewardPartyTotals should have size 1 - rewardPartyTotals.head.appProviderParty shouldBe "alice::provider" - } + "rejects incomplete activity" in { + for { + (store, historyId) <- newStore() + // Activity in roundNumber but no meta row marking the round complete + _ <- insertActivityRecord(historyId, roundNumber, Seq("alice::provider"), Seq(500L)) + result <- store + .computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) + .failed + } yield { + result.getMessage should include("Incomplete app activity") } + } - "empty round returns zero counts" in { - for { - (store, historyId) <- newStore() - _ <- markRoundComplete(historyId, roundNumber) - summary <- store.computeAndStoreRewards( - roundNumber, - batchSize = 100, - testInputs, - ) - } yield { - summary.activePartiesCount shouldBe 0L - summary.activityRecordsCount shouldBe 0L - summary.rewardedPartiesCount shouldBe 0L - summary.batchesCreatedCount shouldBe 1L // empty root batch - } + "produces root hash for complete round" in { + for { + (store, historyId) <- newStore() + _ <- markRoundComplete(historyId, roundNumber) + _ <- insertActivityRecord(historyId, roundNumber, Seq("alice::provider"), Seq(5000000L)) + _ <- store.computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) + rootHash <- store.getAppRewardRootHashByRound(roundNumber) + activityTotals <- store.getAppActivityRoundTotalByRound(roundNumber) + } yield { + // Pipeline completed: activity aggregated and root hash produced + rootHash shouldBe defined + rootHash.value.rootHash.size shouldBe 32 + activityTotals shouldBe defined + activityTotals.value.totalRoundAppActivityWeight shouldBe 5000000L } + } + "re-run for same round raises error" in { + for { + (store, historyId) <- newStore() + _ <- markRoundComplete(historyId, roundNumber) + _ <- insertActivityRecord(historyId, roundNumber, Seq("alice::provider"), Seq(5000000L)) + _ <- store.computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) + result <- store + .computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) + .failed + } yield { + result shouldBe a[Exception] + } } - // -- assertMintingAllowanceWithinMintingCurve tests -------------------------------- - // Tested directly with fake round totals because normal computation - // cannot trigger the assertion — the tranche formula guarantees - // totalReward <= totalIssuance. This check is a safety net for bugs. + "rolls back when reward exceeds issuance" in { + for { + // Use a negative tolerance so any positive reward triggers the assertion + (store, historyId) <- newStore(rewardMintingAllowanceTolerance = BigDecimal(-1.0)) + _ <- markRoundComplete(historyId, roundNumber) + _ <- insertActivityRecord(historyId, roundNumber, Seq("alice::provider"), Seq(5000000L)) + result <- store + .computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) + .failed + } yield { + result.getMessage should include("exceeds minting curve allowance") + } + } + } - "assertMintingAllowanceWithinMintingCurve" should { + // Tested directly with fake round totals because normal computation + // cannot trigger the assertion — the tranche formula guarantees + // totalReward <= totalIssuance. This check is a safety net for bugs. + "assertMintingAllowanceWithinMintingCurve" should { + + def mkParams(totalIssuance: BigDecimal): RewardIssuanceParams = + RewardIssuanceParams( + issuancePerFeaturedAppTraffic_CCperMB = BigDecimal(0), + threshold_CC = BigDecimal(0), + totalIssuanceForFeaturedAppRewards = totalIssuance, + unclaimedAppRewardAmount = BigDecimal(0), + ) - def mkParams(totalIssuance: BigDecimal): RewardIssuanceParams = - RewardIssuanceParams( - issuancePerFeaturedAppTraffic_CCperMB = BigDecimal(0), - threshold_CC = BigDecimal(0), - totalIssuanceForFeaturedAppRewards = totalIssuance, - unclaimedAppRewardAmount = BigDecimal(0), + def insertRoundTotal(historyId: Long, round: Long, amount: BigDecimal): Future[Unit] = + futureUnlessShutdownToFuture( + storage.underlying.queryAndUpdate( + sqlu"""insert into app_reward_round_totals + (history_id, round_number, total_app_reward_minting_allowance, + total_app_reward_thresholded, total_app_reward_unclaimed, + rewarded_app_provider_parties_count) + values ($historyId, $round, $amount, 0, 0, 1)""".map(_ => ()), + "test.insertRoundTotal", ) + ) - def insertRoundTotal(historyId: Long, round: Long, amount: BigDecimal): Future[Unit] = - futureUnlessShutdownToFuture( + "pass when reward amount is within issuance" in { + for { + (store, historyId) <- newStore() + _ <- insertRoundTotal(historyId, roundNumber, BigDecimal(10.0)) + _ <- futureUnlessShutdownToFuture( storage.underlying.queryAndUpdate( - sqlu"""insert into app_reward_round_totals - (history_id, round_number, total_app_reward_minting_allowance, - total_app_reward_thresholded, total_app_reward_unclaimed, - rewarded_app_provider_parties_count) - values ($historyId, $round, $amount, 0, 0, 1)""".map(_ => ()), - "test.insertRoundTotal", + store + .assertMintingAllowanceWithinMintingCurve(roundNumber, mkParams(BigDecimal(10.0))), + "test.assertMintingAllowanceWithinMintingCurve", ) ) + } yield succeed + } - "pass when reward amount is within issuance" in { - for { - (store, historyId) <- newStore() - _ <- insertRoundTotal(historyId, roundNumber, BigDecimal(10.0)) - _ <- futureUnlessShutdownToFuture( - storage.underlying.queryAndUpdate( - store - .assertMintingAllowanceWithinMintingCurve(roundNumber, mkParams(BigDecimal(10.0))), - "test.assertMintingAllowanceWithinMintingCurve", - ) + "fail when reward amount exceeds issuance by more than tolerance" in { + for { + (store, historyId) <- newStore() + // Reward exceeds issuance by 2x tolerance (0.002 > 0.001) + _ <- insertRoundTotal(historyId, roundNumber, BigDecimal(10.002)) + result <- futureUnlessShutdownToFuture( + storage.underlying.queryAndUpdate( + store.assertMintingAllowanceWithinMintingCurve( + roundNumber, + mkParams(BigDecimal(10.0)), + ), + "test.assertMintingAllowanceWithinMintingCurve", ) - } yield succeed - } - - "fail when reward amount exceeds issuance by more than tolerance" in { - for { - (store, historyId) <- newStore() - // Reward exceeds issuance by 2x tolerance (0.002 > 0.001) - _ <- insertRoundTotal(historyId, roundNumber, BigDecimal(10.002)) - result <- futureUnlessShutdownToFuture( - storage.underlying.queryAndUpdate( - store.assertMintingAllowanceWithinMintingCurve( - roundNumber, - mkParams(BigDecimal(10.0)), - ), - "test.assertMintingAllowanceWithinMintingCurve", - ) - ).failed - } yield { - result.getMessage should include("exceeds minting curve allowance") - } + ).failed + } yield { + result.getMessage should include("exceeds minting curve allowance") } + } - "pass when reward amount exceeds issuance within tolerance" in { - for { - (store, historyId) <- newStore() - _ <- insertRoundTotal(historyId, roundNumber, BigDecimal(10.0005)) - _ <- futureUnlessShutdownToFuture( - storage.underlying.queryAndUpdate( - store - .assertMintingAllowanceWithinMintingCurve(roundNumber, mkParams(BigDecimal(10.0))), - "test.assertMintingAllowanceWithinMintingCurve", - ) + "pass when reward amount exceeds issuance within tolerance" in { + for { + (store, historyId) <- newStore() + _ <- insertRoundTotal(historyId, roundNumber, BigDecimal(10.0005)) + _ <- futureUnlessShutdownToFuture( + storage.underlying.queryAndUpdate( + store + .assertMintingAllowanceWithinMintingCurve(roundNumber, mkParams(BigDecimal(10.0))), + "test.assertMintingAllowanceWithinMintingCurve", ) - } yield succeed - } + ) + } yield succeed } + } - // -- computeRewardTotals tests ------------------------------------------- + "computeRewardTotals" should { val rewardTotalsTestCases = Seq( // 5_000_000 / 1_000_000 * 2.0 = 10.0 @@ -808,74 +858,15 @@ class DbScanAppRewardsStoreTest ) rewardTotalsTestCases.foreach { tc => - s"computeRewardTotals — ${tc.description}" in { + tc.description in { RewardTotalsTests.run(tc) } } + } - // -- computeAndStoreRewards tests ------------------------------------------ - - "computeAndStoreRewards — rejects incomplete activity" in { - for { - (store, historyId) <- newStore() - // Activity in roundNumber but no meta row marking the round complete - _ <- insertActivityRecord(historyId, roundNumber, Seq("alice::provider"), Seq(500L)) - result <- store - .computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) - .failed - } yield { - result.getMessage should include("Incomplete app activity") - } - } - - "computeAndStoreRewards — produces root hash for complete round" in { - for { - (store, historyId) <- newStore() - _ <- markRoundComplete(historyId, roundNumber) - _ <- insertActivityRecord(historyId, roundNumber, Seq("alice::provider"), Seq(5000000L)) - _ <- store.computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) - rootHash <- store.getAppRewardRootHashByRound(roundNumber) - activityTotals <- store.getAppActivityRoundTotalByRound(roundNumber) - } yield { - // Pipeline completed: activity aggregated and root hash produced - rootHash shouldBe defined - rootHash.value.rootHash.size shouldBe 32 - activityTotals shouldBe defined - activityTotals.value.totalRoundAppActivityWeight shouldBe 5000000L - } - } - - "computeAndStoreRewards — re-run for same round raises error" in { - for { - (store, historyId) <- newStore() - _ <- markRoundComplete(historyId, roundNumber) - _ <- insertActivityRecord(historyId, roundNumber, Seq("alice::provider"), Seq(5000000L)) - _ <- store.computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) - result <- store - .computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) - .failed - } yield { - result shouldBe a[Exception] - } - } - - "computeAndStoreRewards — rolls back when reward exceeds issuance" in { - for { - // Use a negative tolerance so any positive reward triggers the assertion - (store, historyId) <- newStore(rewardMintingAllowanceTolerance = BigDecimal(-1.0)) - _ <- markRoundComplete(historyId, roundNumber) - _ <- insertActivityRecord(historyId, roundNumber, Seq("alice::provider"), Seq(5000000L)) - result <- store - .computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) - .failed - } yield { - result.getMessage should include("exceeds minting curve allowance") - } - } - - // -- computeRewardHashes tests -------------------------------------------- + "computeRewardHashes" should { - "computeRewardHashes — 3 activity parties, 2 rewarded, batchSize=2" in { + "3 activity parties, 2 rewarded, batchSize=2" in { // 2 rewarded parties fit in 1 batch of size 2 for { (_, batchHashes, _) <- setupAndComputeHashes( @@ -891,7 +882,7 @@ class DbScanAppRewardsStoreTest } } - "computeRewardHashes — single party produces single leaf batch" in { + "single party produces single leaf batch" in { for { (_, batchHashes, _) <- setupAndComputeHashes(partyCount = 1, batchSize = 100) } yield { @@ -901,7 +892,7 @@ class DbScanAppRewardsStoreTest } } - "computeRewardHashes — 3 levels: 9 parties, batchSize=2" in { + "3 levels: 9 parties, batchSize=2" in { // batchSize=2 → level 0: 5 batches, level 1: 3, level 2: 2 for { (_, batchHashes, _) <- setupAndComputeHashes(partyCount = 9, batchSize = 2) @@ -918,7 +909,7 @@ class DbScanAppRewardsStoreTest } } - "computeRewardHashes — exact boundary: 4 parties, batchSize=2" in { + "exact boundary: 4 parties, batchSize=2" in { // batchSize=2 → level 0: 2 batches, level 1: 1 batch (aggregation stops) for { (_, batchHashes, _) <- setupAndComputeHashes(partyCount = 4, batchSize = 2) @@ -932,7 +923,7 @@ class DbScanAppRewardsStoreTest } } - "computeRewardHashes — all parties fit in one batch, no aggregation" in { + "all parties fit in one batch, no aggregation" in { // 3 parties, batchSize=100 → single leaf batch, no aggregation for { (_, batchHashes, rootHash) <- setupAndComputeHashes(partyCount = 3, batchSize = 100) @@ -947,7 +938,7 @@ class DbScanAppRewardsStoreTest } } - "computeRewardHashes — root hash exists after multi-level aggregation" in { + "root hash exists after multi-level aggregation" in { for { (store, _, _) <- setupAndComputeHashes(partyCount = 5, batchSize = 2) computed <- store.roundsWithComputedRewards(Seq(roundNumber)) @@ -960,7 +951,7 @@ class DbScanAppRewardsStoreTest ("no activity parties", 0, -1), ("activity but no rewarded parties", 1, 0), ).foreach { case (desc, parties, rewarded) => - s"computeRewardHashes — $desc produces empty root hash" in { + s"$desc produces empty root hash" in { for { (store, batchHashes, rootHash) <- setupAndComputeHashes( partyCount = parties, @@ -982,7 +973,7 @@ class DbScanAppRewardsStoreTest } } - "computeRewardHashes — re-run for same round raises error" in { + "re-run for same round raises error" in { for { (store, _, _) <- setupAndComputeHashes(partyCount = 1, batchSize = 100) result <- store.computeRewardHashes(roundNumber, batchSize = 100).failed @@ -990,10 +981,11 @@ class DbScanAppRewardsStoreTest result shouldBe a[Exception] } } + } - // -- lookupBatchByHash tests ---------------------------------------------- + "lookupBatchByHash" should { - "lookupBatchByHash — returns None for non-existent hash" in { + "returns None for non-existent hash" in { for { (store, _) <- newStore() result <- store.lookupBatchByHash( @@ -1005,7 +997,7 @@ class DbScanAppRewardsStoreTest } } - "lookupBatchByHash — leaf batch returns MintingAllowances" in { + "leaf batch returns MintingAllowances" in { for { (store, historyId) <- newStore() _ <- store.insertAppActivityPartyTotals( @@ -1037,7 +1029,7 @@ class DbScanAppRewardsStoreTest } } - "lookupBatchByHash — internal batch returns BatchOfBatches" in { + "internal batch returns BatchOfBatches" in { for { (store, historyId) <- newStore() // 4 parties / batchSize=2 → 2 level-0 batches → 1 level-1 batch @@ -1078,7 +1070,6 @@ class DbScanAppRewardsStoreTest childHashes shouldBe level0Hashes } } - } private val verdictCounter = new java.util.concurrent.atomic.AtomicLong(1) @@ -1344,7 +1335,7 @@ object DbScanAppRewardsStoreTest { * trafficPrice/amuletPrice = 1.0 so 1 MB of traffic = 1 CC of reward. */ val testInputs: RewardComputationInputs = { - import RewardComputationInputs.{fromBigDecimal as n} + import RewardComputationInputs.fromBigDecimal as n val tickDurationMicros = 600L * 1000000L val microsPerYear = 365L * 24 * 3600 * 1000000L val roundsPerYear = BigDecimal(microsPerYear) / BigDecimal(tickDurationMicros)