@@ -29,7 +29,11 @@ import org.lfdecentralizedtrust.splice.environment.{
2929 RetryProvider ,
3030 SpliceLedgerClient ,
3131}
32- import org .lfdecentralizedtrust .splice .http .v0 .definitions .ErrorResponse
32+ import org .lfdecentralizedtrust .splice .http .v0 .definitions .{
33+ ErrorResponse ,
34+ RoundPartyTotals as HttpRoundPartyTotals ,
35+ RoundTotals as HttpRoundTotals ,
36+ }
3337import org .lfdecentralizedtrust .splice .scan .admin .api .client .BftScanConnection .Bft
3438import org .lfdecentralizedtrust .splice .scan .admin .api .client .commands .HttpScanAppClient .{
3539 DomainScans ,
@@ -40,6 +44,7 @@ import org.lfdecentralizedtrust.splice.store.HistoryBackfilling.SourceMigrationI
4044import org .lfdecentralizedtrust .splice .store .MultiDomainAcsStore .ContractState
4145import org .lfdecentralizedtrust .splice .store .UpdateHistory .UpdateHistoryResponse
4246import org .lfdecentralizedtrust .splice .util .{
47+ Codec ,
4348 Contract ,
4449 ContractWithState ,
4550 DomainRecordTimeRange ,
@@ -54,6 +59,7 @@ import org.slf4j.event.Level
5459import java .time .{Duration , Instant }
5560import java .util .concurrent .atomic .AtomicInteger
5661import scala .concurrent .{ExecutionContext , Future }
62+ import scala .util .Random
5763
5864// mock verification triggers this
5965@ SuppressWarnings (Array (" com.digitalasset.canton.DiscardedFuture" ))
@@ -922,6 +928,126 @@ class BftScanConnectionTest
922928 result shouldBe Some (roundAggregate)
923929 }
924930
931+ " get BFT round aggregates from scans that report having the round aggregate ignoring balance fields" in {
932+ val round = 0L
933+ def randomValue = BigDecimal (Random .nextInt(50 ) + 1 )
934+ def mkRoundTotals () = RoundTotals (
935+ closedRound = round,
936+ closedRoundEffectiveAt = CantonTimestamp .MinValue ,
937+ appRewards = BigDecimal (100 ),
938+ validatorRewards = BigDecimal (150 ),
939+ changeToInitialAmountAsOfRoundZero = randomValue,
940+ changeToHoldingFeesRate = randomValue,
941+ cumulativeAppRewards = BigDecimal (1100 ),
942+ cumulativeValidatorRewards = BigDecimal (1150 ),
943+ cumulativeChangeToInitialAmountAsOfRoundZero = randomValue,
944+ cumulativeChangeToHoldingFeesRate = randomValue,
945+ totalAmuletBalance = randomValue,
946+ )
947+ def encodeRoundTotals (rt : RoundTotals ) = {
948+ HttpRoundTotals (
949+ closedRound = rt.closedRound,
950+ closedRoundEffectiveAt = Codec .OffsetDateTime .instance.encode(rt.closedRoundEffectiveAt),
951+ appRewards = Codec .encode(rt.appRewards),
952+ validatorRewards = Codec .encode(rt.validatorRewards),
953+ changeToInitialAmountAsOfRoundZero = Codec .encode(rt.changeToInitialAmountAsOfRoundZero),
954+ changeToHoldingFeesRate = Codec .encode(rt.changeToHoldingFeesRate),
955+ cumulativeAppRewards = Codec .encode(rt.cumulativeAppRewards),
956+ cumulativeValidatorRewards = Codec .encode(rt.cumulativeValidatorRewards),
957+ cumulativeChangeToInitialAmountAsOfRoundZero =
958+ Codec .encode(rt.cumulativeChangeToInitialAmountAsOfRoundZero),
959+ cumulativeChangeToHoldingFeesRate = Codec .encode(rt.cumulativeChangeToHoldingFeesRate),
960+ totalAmuletBalance = Codec .encode(rt.totalAmuletBalance),
961+ )
962+ }
963+ def encodeRoundPartyTotals (rpt : RoundPartyTotals ) = {
964+ HttpRoundPartyTotals (
965+ closedRound = rpt.closedRound,
966+ party = rpt.party,
967+ appRewards = Codec .encode(rpt.appRewards),
968+ validatorRewards = Codec .encode(rpt.validatorRewards),
969+ trafficPurchased = rpt.trafficPurchased,
970+ trafficPurchasedCcSpent = Codec .encode(rpt.trafficPurchasedCcSpent),
971+ trafficNumPurchases = rpt.trafficNumPurchases,
972+ cumulativeAppRewards = Codec .encode(rpt.cumulativeAppRewards),
973+ cumulativeValidatorRewards = Codec .encode(rpt.cumulativeValidatorRewards),
974+ cumulativeChangeToInitialAmountAsOfRoundZero =
975+ Codec .encode(rpt.cumulativeChangeToInitialAmountAsOfRoundZero),
976+ cumulativeChangeToHoldingFeesRate = Codec .encode(rpt.cumulativeChangeToHoldingFeesRate),
977+ cumulativeTrafficPurchased = rpt.cumulativeTrafficPurchased,
978+ cumulativeTrafficPurchasedCcSpent = Codec .encode(rpt.cumulativeTrafficPurchasedCcSpent),
979+ cumulativeTrafficNumPurchases = rpt.cumulativeTrafficNumPurchases,
980+ )
981+ }
982+ def mkRoundPartyTotals () = RoundPartyTotals (
983+ closedRound = round,
984+ party = " party-id" ,
985+ appRewards = BigDecimal (10 ),
986+ validatorRewards = BigDecimal (20 ),
987+ trafficPurchased = 10L ,
988+ trafficPurchasedCcSpent = BigDecimal (30 ),
989+ trafficNumPurchases = 30L ,
990+ cumulativeAppRewards = BigDecimal (40 ),
991+ cumulativeValidatorRewards = BigDecimal (50 ),
992+ cumulativeChangeToInitialAmountAsOfRoundZero = randomValue,
993+ cumulativeChangeToHoldingFeesRate = randomValue,
994+ cumulativeTrafficPurchased = 50L ,
995+ cumulativeTrafficPurchasedCcSpent = BigDecimal (70 ),
996+ cumulativeTrafficNumPurchases = 70L ,
997+ )
998+ def mkRoundAggregateUsingDecoder () = RoundAggregate (
999+ ScanRoundAggregatesDecoder .decodeRoundTotal(encodeRoundTotals(mkRoundTotals())).value,
1000+ Vector (
1001+ ScanRoundAggregatesDecoder
1002+ .decodeRoundPartyTotals(encodeRoundPartyTotals(mkRoundPartyTotals()))
1003+ .value
1004+ ),
1005+ )
1006+ def mkRoundAggregateWithoutDecoder () = RoundAggregate (
1007+ mkRoundTotals(),
1008+ Vector (mkRoundPartyTotals()),
1009+ )
1010+ val roundAggregateZeroBalanceValues = mkRoundAggregateWithoutDecoder().copy(
1011+ roundTotals = mkRoundAggregateWithoutDecoder().roundTotals.copy(
1012+ changeToInitialAmountAsOfRoundZero = zero,
1013+ changeToHoldingFeesRate = zero,
1014+ cumulativeChangeToInitialAmountAsOfRoundZero = zero,
1015+ cumulativeChangeToHoldingFeesRate = zero,
1016+ totalAmuletBalance = zero,
1017+ ),
1018+ roundPartyTotals = mkRoundAggregateWithoutDecoder().roundPartyTotals.map(
1019+ _.copy(
1020+ cumulativeChangeToInitialAmountAsOfRoundZero = zero,
1021+ cumulativeChangeToHoldingFeesRate = zero,
1022+ )
1023+ ),
1024+ )
1025+
1026+ def getConnections (roundAggregateResponse : () => RoundAggregate ) = {
1027+ val connections = getMockedConnections(n = 10 )
1028+ connections.foreach { mock =>
1029+ when(mock.getAggregatedRounds())
1030+ .thenReturn(Future .successful(Some (RoundRange (round, round))))
1031+ when(mock.getRoundAggregate(round))
1032+ .thenReturn(Future .successful(Some (roundAggregateResponse())))
1033+ }
1034+ connections
1035+ }
1036+
1037+ val bft = getBft(getConnections(() => mkRoundAggregateUsingDecoder()))
1038+ val con =
1039+ new ScanAggregatesConnection (bft, retryProvider, retryProvider.loggerFactory)
1040+ val result = con.getRoundAggregate(round).futureValue
1041+ result shouldBe Some (roundAggregateZeroBalanceValues)
1042+
1043+ // not using the decoder should fail on the randomized balance values.
1044+ val bftFail = getBft(getConnections(() => mkRoundAggregateWithoutDecoder()))
1045+ val conFail =
1046+ new ScanAggregatesConnection (bftFail, retryProvider, retryProvider.loggerFactory)
1047+ val resultFail = conFail.getRoundAggregate(round).failed.futureValue
1048+ resultFail shouldBe an[BftScanConnection .ConsensusNotReached ]
1049+ }
1050+
9251051 " Not get round aggregates from scans that report having the round aggregate if too many fail" in {
9261052 val connections = getMockedConnections(n = 10 )
9271053 connections.zipWithIndex.foreach { case (mock, index) =>
0 commit comments