Skip to content

Commit 46c0f58

Browse files
[ci] Zero balance related fields in round aggregates so that they always agree (BFT) (hyperledger-labs#3204)
* [ci] Zero holding fee and initial amount values in responses so that they always agree. --------- Signed-off-by: Raymond Roestenburg <[email protected]> Signed-off-by: Raymond Roestenburg <[email protected]> Co-authored-by: Oriol Muñoz <[email protected]>
1 parent 0a2c4f3 commit 46c0f58

File tree

4 files changed

+206
-111
lines changed

4 files changed

+206
-111
lines changed

apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/SingleScanConnection.scala

Lines changed: 62 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ class SingleScanConnection private[client] (
9999
with ScanConnection
100100
with BackfillingScanConnection
101101
with HasUrl {
102+
import ScanRoundAggregatesDecoder.*
103+
102104
def url = config.adminApi.url
103105

104106
// cached DSO reference. Never changes.
@@ -392,76 +394,6 @@ class SingleScanConnection private[client] (
392394
}
393395
}
394396

395-
private def decodeRoundTotal(
396-
rt: org.lfdecentralizedtrust.splice.http.v0.definitions.RoundTotals
397-
): Either[String, ScanAggregator.RoundTotals] = {
398-
(for {
399-
closedRoundEffectiveAt <- CantonTimestamp.fromInstant(rt.closedRoundEffectiveAt.toInstant)
400-
appRewards <- Codec.decode(Codec.BigDecimal)(rt.appRewards)
401-
validatorRewards <- Codec.decode(Codec.BigDecimal)(rt.validatorRewards)
402-
changeToInitialAmountAsOfRoundZero <- Codec
403-
.decode(Codec.BigDecimal)(rt.changeToInitialAmountAsOfRoundZero)
404-
changeToHoldingFeesRate <- Codec.decode(Codec.BigDecimal)(rt.changeToHoldingFeesRate)
405-
cumulativeAppRewards <- Codec.decode(Codec.BigDecimal)(rt.cumulativeAppRewards)
406-
cumulativeValidatorRewards <- Codec
407-
.decode(Codec.BigDecimal)(rt.cumulativeValidatorRewards)
408-
cumulativeChangeToInitialAmountAsOfRoundZero <- Codec
409-
.decode(Codec.BigDecimal)(rt.cumulativeChangeToInitialAmountAsOfRoundZero)
410-
cumulativeChangeToHoldingFeesRate <- Codec
411-
.decode(Codec.BigDecimal)(rt.cumulativeChangeToHoldingFeesRate)
412-
totalAmuletBalance <- Codec.decode(Codec.BigDecimal)(rt.totalAmuletBalance)
413-
} yield {
414-
ScanAggregator.RoundTotals(
415-
closedRound = rt.closedRound,
416-
closedRoundEffectiveAt = closedRoundEffectiveAt,
417-
appRewards = appRewards,
418-
validatorRewards = validatorRewards,
419-
changeToInitialAmountAsOfRoundZero = changeToInitialAmountAsOfRoundZero,
420-
changeToHoldingFeesRate = changeToHoldingFeesRate,
421-
cumulativeAppRewards = cumulativeAppRewards,
422-
cumulativeValidatorRewards = cumulativeValidatorRewards,
423-
cumulativeChangeToInitialAmountAsOfRoundZero = cumulativeChangeToInitialAmountAsOfRoundZero,
424-
cumulativeChangeToHoldingFeesRate = cumulativeChangeToHoldingFeesRate,
425-
totalAmuletBalance = totalAmuletBalance,
426-
)
427-
})
428-
}
429-
430-
private def decodeRoundPartyTotals(
431-
rt: org.lfdecentralizedtrust.splice.http.v0.definitions.RoundPartyTotals
432-
): Either[String, ScanAggregator.RoundPartyTotals] = {
433-
(for {
434-
appRewards <- Codec.decode(Codec.BigDecimal)(rt.appRewards)
435-
validatorRewards <- Codec.decode(Codec.BigDecimal)(rt.validatorRewards)
436-
trafficPurchasedCcSpent <- Codec.decode(Codec.BigDecimal)(rt.trafficPurchasedCcSpent)
437-
cumulativeAppRewards <- Codec.decode(Codec.BigDecimal)(rt.cumulativeAppRewards)
438-
cumulativeValidatorRewards <- Codec.decode(Codec.BigDecimal)(rt.cumulativeValidatorRewards)
439-
cumulativeChangeToInitialAmountAsOfRoundZero <- Codec
440-
.decode(Codec.BigDecimal)(rt.cumulativeChangeToInitialAmountAsOfRoundZero)
441-
cumulativeChangeToHoldingFeesRate <- Codec
442-
.decode(Codec.BigDecimal)(rt.cumulativeChangeToHoldingFeesRate)
443-
cumulativeTrafficPurchasedCcSpent <- Codec
444-
.decode(Codec.BigDecimal)(rt.cumulativeTrafficPurchasedCcSpent)
445-
} yield {
446-
ScanAggregator.RoundPartyTotals(
447-
closedRound = rt.closedRound,
448-
party = rt.party,
449-
appRewards = appRewards,
450-
validatorRewards = validatorRewards,
451-
trafficPurchased = rt.trafficPurchased,
452-
trafficPurchasedCcSpent = trafficPurchasedCcSpent,
453-
trafficNumPurchases = rt.trafficNumPurchases,
454-
cumulativeAppRewards = cumulativeAppRewards,
455-
cumulativeValidatorRewards = cumulativeValidatorRewards,
456-
cumulativeChangeToInitialAmountAsOfRoundZero = cumulativeChangeToInitialAmountAsOfRoundZero,
457-
cumulativeChangeToHoldingFeesRate = cumulativeChangeToHoldingFeesRate,
458-
cumulativeTrafficPurchased = rt.cumulativeTrafficPurchased,
459-
cumulativeTrafficPurchasedCcSpent = cumulativeTrafficPurchasedCcSpent,
460-
cumulativeTrafficNumPurchases = rt.cumulativeTrafficNumPurchases,
461-
)
462-
})
463-
}
464-
465397
override def getMigrationSchedule()(implicit
466398
ec: ExecutionContext,
467399
tc: TraceContext,
@@ -878,3 +810,63 @@ class CachedScanConnection private[client] (
878810
)
879811
)
880812
}
813+
814+
object ScanRoundAggregatesDecoder {
815+
def decodeRoundTotal(
816+
rt: org.lfdecentralizedtrust.splice.http.v0.definitions.RoundTotals
817+
): Either[String, ScanAggregator.RoundTotals] = {
818+
(for {
819+
closedRoundEffectiveAt <- CantonTimestamp.fromInstant(rt.closedRoundEffectiveAt.toInstant)
820+
appRewards <- Codec.decode(Codec.BigDecimal)(rt.appRewards)
821+
validatorRewards <- Codec.decode(Codec.BigDecimal)(rt.validatorRewards)
822+
cumulativeAppRewards <- Codec.decode(Codec.BigDecimal)(rt.cumulativeAppRewards)
823+
cumulativeValidatorRewards <- Codec
824+
.decode(Codec.BigDecimal)(rt.cumulativeValidatorRewards)
825+
} yield {
826+
// changeToInitialAmountAsOfRoundZero, changeToHoldingFeesRate, cumulativeChangeToInitialAmountAsOfRoundZero,
827+
// cumulativeChangeToHoldingFeesRate and totalAmuletBalance are intentionally left out
828+
// since these do not match up anymore because amulet expires are attributed to the closed round at a later stage
829+
// in scan_txlog_store, at a time that can easily differ between SVs.
830+
ScanAggregator.RoundTotals(
831+
closedRound = rt.closedRound,
832+
closedRoundEffectiveAt = closedRoundEffectiveAt,
833+
appRewards = appRewards,
834+
validatorRewards = validatorRewards,
835+
cumulativeAppRewards = cumulativeAppRewards,
836+
cumulativeValidatorRewards = cumulativeValidatorRewards,
837+
)
838+
})
839+
}
840+
841+
def decodeRoundPartyTotals(
842+
rt: org.lfdecentralizedtrust.splice.http.v0.definitions.RoundPartyTotals
843+
): Either[String, ScanAggregator.RoundPartyTotals] = {
844+
(for {
845+
appRewards <- Codec.decode(Codec.BigDecimal)(rt.appRewards)
846+
validatorRewards <- Codec.decode(Codec.BigDecimal)(rt.validatorRewards)
847+
trafficPurchasedCcSpent <- Codec.decode(Codec.BigDecimal)(rt.trafficPurchasedCcSpent)
848+
cumulativeAppRewards <- Codec.decode(Codec.BigDecimal)(rt.cumulativeAppRewards)
849+
cumulativeValidatorRewards <- Codec.decode(Codec.BigDecimal)(rt.cumulativeValidatorRewards)
850+
cumulativeTrafficPurchasedCcSpent <- Codec
851+
.decode(Codec.BigDecimal)(rt.cumulativeTrafficPurchasedCcSpent)
852+
} yield {
853+
// cumulativeChangeToInitialAmountAsOfRoundZero and cumulativeChangeToHoldingFeesRate are intentionally left out
854+
// since these do not match up anymore because amulet expires are attributed to the closed round at a later stage
855+
// in scan_txlog_store, at a time that can easily differ between SVs.
856+
ScanAggregator.RoundPartyTotals(
857+
closedRound = rt.closedRound,
858+
party = rt.party,
859+
appRewards = appRewards,
860+
validatorRewards = validatorRewards,
861+
trafficPurchased = rt.trafficPurchased,
862+
trafficPurchasedCcSpent = trafficPurchasedCcSpent,
863+
trafficNumPurchases = rt.trafficNumPurchases,
864+
cumulativeAppRewards = cumulativeAppRewards,
865+
cumulativeValidatorRewards = cumulativeValidatorRewards,
866+
cumulativeTrafficPurchased = rt.cumulativeTrafficPurchased,
867+
cumulativeTrafficPurchasedCcSpent = cumulativeTrafficPurchasedCcSpent,
868+
cumulativeTrafficNumPurchases = rt.cumulativeTrafficNumPurchases,
869+
)
870+
})
871+
}
872+
}

apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/HttpScanHandler.scala

Lines changed: 46 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ import com.digitalasset.canton.util.ErrorUtil
9595
import org.lfdecentralizedtrust.splice.environment.TopologyAdminConnection.TopologyTransactionType.AuthorizedState
9696
import org.lfdecentralizedtrust.splice.scan.config.BftSequencerConfig
9797
import org.lfdecentralizedtrust.splice.scan.store.AcsSnapshotStore.QueryAcsSnapshotResult
98+
import org.lfdecentralizedtrust.splice.scan.store.db.ScanAggregator.{RoundPartyTotals, RoundTotals}
9899
import org.lfdecentralizedtrust.splice.store.MultiDomainAcsStore.TxLogBackfillingState
99100
import org.lfdecentralizedtrust.splice.store.UpdateHistory.BackfillingState
100101
import org.lfdecentralizedtrust.splice.store.UpdateHistory
@@ -125,7 +126,7 @@ class HttpScanHandler(
125126
with HttpVotesHandler
126127
with HttpValidatorLicensesHandler
127128
with HttpFeatureSupportHandler {
128-
129+
import HttpScanHandler.*
129130
override protected val workflowId: String = this.getClass.getSimpleName
130131
override protected val votesStore: VotesStore = store
131132
override protected val validatorLicensesStore: AppStore = store
@@ -1853,25 +1854,7 @@ class HttpScanHandler(
18531854
ensureValidRange(request.startRound, request.endRound, 200) {
18541855
for {
18551856
roundTotals <- store.getRoundTotals(request.startRound, request.endRound)
1856-
entries = roundTotals.map { roundTotal =>
1857-
definitions.RoundTotals(
1858-
closedRound = roundTotal.closedRound,
1859-
closedRoundEffectiveAt = java.time.OffsetDateTime
1860-
.ofInstant(roundTotal.closedRoundEffectiveAt.toInstant, ZoneOffset.UTC),
1861-
appRewards = Codec.encode(roundTotal.appRewards),
1862-
validatorRewards = Codec.encode(roundTotal.validatorRewards),
1863-
changeToInitialAmountAsOfRoundZero =
1864-
Codec.encode(roundTotal.changeToInitialAmountAsOfRoundZero),
1865-
changeToHoldingFeesRate = Codec.encode(roundTotal.changeToHoldingFeesRate),
1866-
cumulativeAppRewards = Codec.encode(roundTotal.cumulativeAppRewards),
1867-
cumulativeValidatorRewards = Codec.encode(roundTotal.cumulativeValidatorRewards),
1868-
cumulativeChangeToInitialAmountAsOfRoundZero =
1869-
Codec.encode(roundTotal.cumulativeChangeToInitialAmountAsOfRoundZero),
1870-
cumulativeChangeToHoldingFeesRate =
1871-
Codec.encode(roundTotal.cumulativeChangeToHoldingFeesRate),
1872-
totalAmuletBalance = Codec.encode(roundTotal.totalAmuletBalance),
1873-
)
1874-
}
1857+
entries = roundTotals.map(encodeRoundTotals)
18751858
} yield v0.ScanResource.ListRoundTotalsResponse.OK(
18761859
definitions.ListRoundTotalsResponse(entries.toVector)
18771860
)
@@ -1888,27 +1871,7 @@ class HttpScanHandler(
18881871
ensureValidRange(request.startRound, request.endRound, 50) {
18891872
for {
18901873
roundPartyTotals <- store.getRoundPartyTotals(request.startRound, request.endRound)
1891-
entries = roundPartyTotals.map { roundPartyTotal =>
1892-
definitions.RoundPartyTotals(
1893-
closedRound = roundPartyTotal.closedRound,
1894-
party = roundPartyTotal.party,
1895-
appRewards = Codec.encode(roundPartyTotal.appRewards),
1896-
validatorRewards = Codec.encode(roundPartyTotal.validatorRewards),
1897-
trafficPurchased = roundPartyTotal.trafficPurchased,
1898-
trafficPurchasedCcSpent = Codec.encode(roundPartyTotal.trafficPurchasedCcSpent),
1899-
trafficNumPurchases = roundPartyTotal.trafficNumPurchases,
1900-
cumulativeAppRewards = Codec.encode(roundPartyTotal.cumulativeAppRewards),
1901-
cumulativeValidatorRewards = Codec.encode(roundPartyTotal.cumulativeValidatorRewards),
1902-
cumulativeChangeToInitialAmountAsOfRoundZero =
1903-
Codec.encode(roundPartyTotal.cumulativeChangeToInitialAmountAsOfRoundZero),
1904-
cumulativeChangeToHoldingFeesRate =
1905-
Codec.encode(roundPartyTotal.cumulativeChangeToHoldingFeesRate),
1906-
cumulativeTrafficPurchased = roundPartyTotal.cumulativeTrafficPurchased,
1907-
cumulativeTrafficPurchasedCcSpent =
1908-
Codec.encode(roundPartyTotal.cumulativeTrafficPurchasedCcSpent),
1909-
cumulativeTrafficNumPurchases = roundPartyTotal.cumulativeTrafficNumPurchases,
1910-
)
1911-
}
1874+
entries = roundPartyTotals.map(encodeRoundPartyTotals)
19121875
} yield v0.ScanResource.ListRoundPartyTotalsResponse.OK(
19131876
definitions.ListRoundPartyTotalsResponse(entries.toVector)
19141877
)
@@ -2303,4 +2266,46 @@ object HttpScanHandler {
23032266
// We expect a handful at most but want to somewhat guard against attacks
23042267
// so we just hardcode a limit of 100.
23052268
private val MAX_TRANSFER_COMMAND_CONTRACTS: Int = 100
2269+
2270+
def encodeRoundTotals(roundTotal: RoundTotals): definitions.RoundTotals = {
2271+
definitions.RoundTotals(
2272+
closedRound = roundTotal.closedRound,
2273+
closedRoundEffectiveAt = java.time.OffsetDateTime
2274+
.ofInstant(roundTotal.closedRoundEffectiveAt.toInstant, ZoneOffset.UTC),
2275+
appRewards = Codec.encode(roundTotal.appRewards),
2276+
validatorRewards = Codec.encode(roundTotal.validatorRewards),
2277+
changeToInitialAmountAsOfRoundZero =
2278+
Codec.encode(roundTotal.changeToInitialAmountAsOfRoundZero),
2279+
changeToHoldingFeesRate = Codec.encode(roundTotal.changeToHoldingFeesRate),
2280+
cumulativeAppRewards = Codec.encode(roundTotal.cumulativeAppRewards),
2281+
cumulativeValidatorRewards = Codec.encode(roundTotal.cumulativeValidatorRewards),
2282+
cumulativeChangeToInitialAmountAsOfRoundZero =
2283+
Codec.encode(roundTotal.cumulativeChangeToInitialAmountAsOfRoundZero),
2284+
cumulativeChangeToHoldingFeesRate =
2285+
Codec.encode(roundTotal.cumulativeChangeToHoldingFeesRate),
2286+
totalAmuletBalance = Codec.encode(roundTotal.totalAmuletBalance),
2287+
)
2288+
}
2289+
2290+
def encodeRoundPartyTotals(roundPartyTotal: RoundPartyTotals): definitions.RoundPartyTotals = {
2291+
definitions.RoundPartyTotals(
2292+
closedRound = roundPartyTotal.closedRound,
2293+
party = roundPartyTotal.party,
2294+
appRewards = Codec.encode(roundPartyTotal.appRewards),
2295+
validatorRewards = Codec.encode(roundPartyTotal.validatorRewards),
2296+
trafficPurchased = roundPartyTotal.trafficPurchased,
2297+
trafficPurchasedCcSpent = Codec.encode(roundPartyTotal.trafficPurchasedCcSpent),
2298+
trafficNumPurchases = roundPartyTotal.trafficNumPurchases,
2299+
cumulativeAppRewards = Codec.encode(roundPartyTotal.cumulativeAppRewards),
2300+
cumulativeValidatorRewards = Codec.encode(roundPartyTotal.cumulativeValidatorRewards),
2301+
cumulativeChangeToInitialAmountAsOfRoundZero =
2302+
Codec.encode(roundPartyTotal.cumulativeChangeToInitialAmountAsOfRoundZero),
2303+
cumulativeChangeToHoldingFeesRate =
2304+
Codec.encode(roundPartyTotal.cumulativeChangeToHoldingFeesRate),
2305+
cumulativeTrafficPurchased = roundPartyTotal.cumulativeTrafficPurchased,
2306+
cumulativeTrafficPurchasedCcSpent =
2307+
Codec.encode(roundPartyTotal.cumulativeTrafficPurchasedCcSpent),
2308+
cumulativeTrafficNumPurchases = roundPartyTotal.cumulativeTrafficNumPurchases,
2309+
)
2310+
}
23062311
}

apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/BftScanConnectionTest.scala

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,13 @@ import org.lfdecentralizedtrust.splice.environment.{
3030
SpliceLedgerClient,
3131
}
3232
import org.lfdecentralizedtrust.splice.http.v0.definitions.ErrorResponse
33+
3334
import org.lfdecentralizedtrust.splice.scan.admin.api.client.BftScanConnection.Bft
3435
import org.lfdecentralizedtrust.splice.scan.admin.api.client.commands.HttpScanAppClient.{
3536
DomainScans,
3637
DsoScan,
3738
}
39+
import org.lfdecentralizedtrust.splice.scan.admin.http.HttpScanHandler
3840
import org.lfdecentralizedtrust.splice.scan.config.ScanAppClientConfig
3941
import org.lfdecentralizedtrust.splice.store.HistoryBackfilling.SourceMigrationInfo
4042
import org.lfdecentralizedtrust.splice.store.MultiDomainAcsStore.ContractState
@@ -54,6 +56,7 @@ import org.slf4j.event.Level
5456
import java.time.{Duration, Instant}
5557
import java.util.concurrent.atomic.AtomicInteger
5658
import scala.concurrent.{ExecutionContext, Future}
59+
import scala.util.Random
5760

5861
// mock verification triggers this
5962
@SuppressWarnings(Array("com.digitalasset.canton.DiscardedFuture"))
@@ -922,6 +925,93 @@ class BftScanConnectionTest
922925
result shouldBe Some(roundAggregate)
923926
}
924927

928+
"get BFT round aggregates from scans, ignoring balance fields" in {
929+
val round = 0L
930+
def randomValue = BigDecimal(Random.nextInt(50) + 1)
931+
def mkRoundTotals() = RoundTotals(
932+
closedRound = round,
933+
closedRoundEffectiveAt = CantonTimestamp.MinValue,
934+
appRewards = BigDecimal(100),
935+
validatorRewards = BigDecimal(150),
936+
changeToInitialAmountAsOfRoundZero = randomValue,
937+
changeToHoldingFeesRate = randomValue,
938+
cumulativeAppRewards = BigDecimal(1100),
939+
cumulativeValidatorRewards = BigDecimal(1150),
940+
cumulativeChangeToInitialAmountAsOfRoundZero = randomValue,
941+
cumulativeChangeToHoldingFeesRate = randomValue,
942+
totalAmuletBalance = randomValue,
943+
)
944+
def mkRoundPartyTotals() = RoundPartyTotals(
945+
closedRound = round,
946+
party = "party-id",
947+
appRewards = BigDecimal(10),
948+
validatorRewards = BigDecimal(20),
949+
trafficPurchased = 10L,
950+
trafficPurchasedCcSpent = BigDecimal(30),
951+
trafficNumPurchases = 30L,
952+
cumulativeAppRewards = BigDecimal(40),
953+
cumulativeValidatorRewards = BigDecimal(50),
954+
cumulativeChangeToInitialAmountAsOfRoundZero = randomValue,
955+
cumulativeChangeToHoldingFeesRate = randomValue,
956+
cumulativeTrafficPurchased = 50L,
957+
cumulativeTrafficPurchasedCcSpent = BigDecimal(70),
958+
cumulativeTrafficNumPurchases = 70L,
959+
)
960+
def mkRoundAggregateUsingDecoder() = RoundAggregate(
961+
ScanRoundAggregatesDecoder
962+
.decodeRoundTotal(HttpScanHandler.encodeRoundTotals(mkRoundTotals()))
963+
.value,
964+
Vector(
965+
ScanRoundAggregatesDecoder
966+
.decodeRoundPartyTotals(HttpScanHandler.encodeRoundPartyTotals(mkRoundPartyTotals()))
967+
.value
968+
),
969+
)
970+
def mkRoundAggregateWithoutDecoder() = RoundAggregate(
971+
mkRoundTotals(),
972+
Vector(mkRoundPartyTotals()),
973+
)
974+
val roundAggregateZeroBalanceValues = mkRoundAggregateWithoutDecoder().copy(
975+
roundTotals = mkRoundAggregateWithoutDecoder().roundTotals.copy(
976+
changeToInitialAmountAsOfRoundZero = zero,
977+
changeToHoldingFeesRate = zero,
978+
cumulativeChangeToInitialAmountAsOfRoundZero = zero,
979+
cumulativeChangeToHoldingFeesRate = zero,
980+
totalAmuletBalance = zero,
981+
),
982+
roundPartyTotals = mkRoundAggregateWithoutDecoder().roundPartyTotals.map(
983+
_.copy(
984+
cumulativeChangeToInitialAmountAsOfRoundZero = zero,
985+
cumulativeChangeToHoldingFeesRate = zero,
986+
)
987+
),
988+
)
989+
990+
def getConnections(roundAggregateResponse: () => RoundAggregate) = {
991+
val connections = getMockedConnections(n = 10)
992+
connections.foreach { mock =>
993+
when(mock.getAggregatedRounds())
994+
.thenReturn(Future.successful(Some(RoundRange(round, round))))
995+
when(mock.getRoundAggregate(round))
996+
.thenReturn(Future.successful(Some(roundAggregateResponse())))
997+
}
998+
connections
999+
}
1000+
1001+
val bft = getBft(getConnections(() => mkRoundAggregateUsingDecoder()))
1002+
val con =
1003+
new ScanAggregatesConnection(bft, retryProvider, retryProvider.loggerFactory)
1004+
val result = con.getRoundAggregate(round).futureValue
1005+
result shouldBe Some(roundAggregateZeroBalanceValues)
1006+
1007+
// not using the decoder should fail on the randomized balance values.
1008+
val bftFail = getBft(getConnections(() => mkRoundAggregateWithoutDecoder()))
1009+
val conFail =
1010+
new ScanAggregatesConnection(bftFail, retryProvider, retryProvider.loggerFactory)
1011+
val resultFail = conFail.getRoundAggregate(round).failed.futureValue
1012+
resultFail shouldBe an[BftScanConnection.ConsensusNotReached]
1013+
}
1014+
9251015
"Not get round aggregates from scans that report having the round aggregate if too many fail" in {
9261016
val connections = getMockedConnections(n = 10)
9271017
connections.zipWithIndex.foreach { case (mock, index) =>

0 commit comments

Comments
 (0)