Skip to content

Commit d816f69

Browse files
[ci] pre-calculating total amulet balance (#2407)
--------- Signed-off-by: Raymond Roestenburg <raymond.roestenburg@digitalasset.com>
1 parent d0062ca commit d816f69

File tree

10 files changed

+251
-188
lines changed

10 files changed

+251
-188
lines changed

apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanTimeBasedIntegrationTest.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,11 +364,11 @@ class ScanTimeBasedIntegrationTest
364364
val total0 =
365365
sv1ScanBackend
366366
.getTotalAmuletBalance(firstRound + 0)
367-
.valueOrFail("Amulet balance not yet computed")
367+
.getOrElse(BigDecimal(0))
368368
val total1 =
369369
sv1ScanBackend
370370
.getTotalAmuletBalance(firstRound + 1)
371-
.valueOrFail("Amulet balance not yet computed")
371+
.getOrElse(BigDecimal(0))
372372
val total2 =
373373
sv1ScanBackend
374374
.getTotalAmuletBalance(firstRound + 2)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-- aggregation result table for total amulet balance changes per store and closed round
2+
-- this table only contains rows for rounds where the sums changed
3+
create table round_total_amulet_balance
4+
(
5+
-- the store id of the scan store for which the totals are calculated
6+
store_id int not null,
7+
-- the closed round for which the sums are calculated.
8+
closed_round bigint not null,
9+
-- the sum of cumulative change_to_initial_amount_as_of_round_zero from round zero up to and including the round
10+
sum_cumulative_change_to_initial_amount_as_of_round_zero numeric not null,
11+
-- the sum of cumulative change_to_holding_fees_rate from round zero up to and including the round
12+
sum_cumulative_change_to_holding_fees_rate numeric not null,
13+
primary key (store_id, closed_round)
14+
);
15+
16+
-- round_total_amulet_balance does not have a sums for rounds when nothing changed.
17+
-- this index is for efficiently finding an earlier round and calculating the total balance as of a given round
18+
create index idx_round_total_amulet_balance_sid_cr_desc
19+
on round_total_amulet_balance (store_id, closed_round desc);

apps/common/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SpliceDbTest.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ trait SpliceDbTest extends DbTest with BeforeAndAfterAll { this: Suite =>
6262
for {
6363
_ <- debugPrintPgActivity()
6464
_ <- sql"""TRUNCATE
65+
active_parties,
6566
user_wallet_acs_store,
6667
user_wallet_acs_interface_views,
6768
user_wallet_txlog_store,
@@ -78,6 +79,7 @@ trait SpliceDbTest extends DbTest with BeforeAndAfterAll { this: Suite =>
7879
store_last_ingested_offsets,
7980
round_totals,
8081
round_party_totals,
82+
round_total_amulet_balance,
8183
update_history_descriptors,
8284
update_history_last_ingested_offsets,
8385
update_history_transactions,

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

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@ import org.lfdecentralizedtrust.splice.http.v0.definitions.{
3333
AcsRequest,
3434
BatchListVotesByVoteRequestsRequest,
3535
DamlValueEncoding,
36+
ErrorResponse,
37+
EventHistoryRequest,
3638
HoldingsStateRequest,
3739
HoldingsSummaryRequest,
3840
ListVoteResultsRequest,
3941
MaybeCachedContractWithState,
4042
UpdateHistoryItemV2,
4143
UpdateHistoryRequestV2,
42-
EventHistoryRequest,
4344
}
4445
import org.lfdecentralizedtrust.splice.http.v0.scan.ScanResource
4546
import org.lfdecentralizedtrust.splice.http.v0.{definitions, scan as v0}
@@ -49,7 +50,6 @@ import org.lfdecentralizedtrust.splice.scan.store.{
4950
ScanStore,
5051
TxLogEntry,
5152
}
52-
5353
import org.lfdecentralizedtrust.splice.util.{
5454
Codec,
5555
Contract,
@@ -413,8 +413,15 @@ class HttpScanHandler(
413413
HttpErrorHandler.onGrpcNotFound(s"Data for round ${asOfEndOfRound} not yet computed")
414414
)
415415
} yield {
416-
definitions.GetTotalAmuletBalanceResponse(
417-
Codec.encode(total)
416+
total.fold(
417+
v0.ScanResource.GetTotalAmuletBalanceResponse
418+
.NotFound(ErrorResponse(s"No total amulet balance found for round $asOfEndOfRound"))
419+
)(total =>
420+
v0.ScanResource.GetTotalAmuletBalanceResponse.OK(
421+
definitions.GetTotalAmuletBalanceResponse(
422+
Codec.encode(total)
423+
)
424+
)
418425
)
419426
}
420427
}
@@ -447,7 +454,7 @@ class HttpScanHandler(
447454
withSpan(s"$workflowId.getAmuletConfigForRound") { _ => _ =>
448455
store
449456
.getAmuletConfigForRound(round)
450-
.map(cfg => {
457+
.map { cfg =>
451458
val transferFee = cfg.transferFee.getOrElse(throw new RuntimeException("No transfer fee"))
452459
v0.ScanResource.GetAmuletConfigForRoundResponse.OK(
453460
definitions.GetAmuletConfigForRoundResponse(
@@ -464,7 +471,7 @@ class HttpScanHandler(
464471
),
465472
)
466473
)
467-
})
474+
}
468475
.transform(HttpErrorHandler.onGrpcNotFound(s"Round ${round} not found"))
469476
}
470477
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class HttpTokenStandardMetadataHandler(
7070
private def lookupTotalSupplyByLatestRound()(implicit ec: ExecutionContext, tc: TraceContext) =
7171
for {
7272
(latestRoundNr, effectiveAt) <- OptionT(store.lookupRoundOfLatestData())
73-
totalSupply <- OptionT.liftF(store.getTotalAmuletBalance(latestRoundNr))
73+
totalSupply <- OptionT(store.getTotalAmuletBalance(latestRoundNr))
7474
} yield TotalSupply(amount = totalSupply, asOfTimestamp = effectiveAt)
7575

7676
private def lookupTotalSupplyByLatestAcsSnapshot()(implicit tc: TraceContext) = {

apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/CachingScanStore.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class CachingScanStore(
7070
// TODO(#800) remove when amulet expiry works again
7171
override def getTotalAmuletBalance(
7272
asOfEndOfRound: Long
73-
)(implicit tc: TraceContext): Future[BigDecimal] = {
73+
)(implicit tc: TraceContext): Future[Option[BigDecimal]] = {
7474
getCache(
7575
"totalAmuletBalance",
7676
cacheConfig.totalAmuletBalance,

apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/ScanStore.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,9 @@ trait ScanStore
154154
tc: TraceContext
155155
): Future[Option[ContractWithState[splice.ans.AnsRules.ContractId, splice.ans.AnsRules]]]
156156

157-
def getTotalAmuletBalance(asOfEndOfRound: Long)(implicit tc: TraceContext): Future[BigDecimal]
157+
def getTotalAmuletBalance(asOfEndOfRound: Long)(implicit
158+
tc: TraceContext
159+
): Future[Option[BigDecimal]]
158160

159161
def getTotalRewardsCollectedEver()(implicit tc: TraceContext): Future[BigDecimal]
160162
def getRewardsCollectedInRound(round: Long)(implicit tc: TraceContext): Future[BigDecimal]

apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/db/DbScanStore.scala

Lines changed: 20 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -593,51 +593,30 @@ class DbScanStore(
593593

594594
override def getTotalAmuletBalance(asOfEndOfRound: Long)(implicit
595595
tc: TraceContext
596-
): Future[BigDecimal] =
596+
): Future[Option[BigDecimal]] =
597597
waitUntilAcsIngested {
598598
for {
599-
result <- ensureAggregated(asOfEndOfRound) { lastAggregatedRound =>
600-
// if the asOfEndOrRound is the latest aggregated round, we can use the active_parties for a faster query.
601-
if (lastAggregatedRound == asOfEndOfRound) {
602-
// using greatest(0, ...) to handle negative balances caused by amulets never expiring.
603-
storage.query(
604-
// TODO(#800) change to query from round_totals when amulet expiry works again
605-
sql"""
606-
select sum(greatest(0, rpt.cumulative_change_to_initial_amount_as_of_round_zero - rpt.cumulative_change_to_holding_fees_rate * ($asOfEndOfRound + 1)))
607-
from round_party_totals rpt
608-
join active_parties ap
609-
on rpt.store_id = ap.store_id
610-
and rpt.party = ap.party
611-
and rpt.closed_round = ap.closed_round
612-
where rpt.store_id = $roundTotalsStoreId;
613-
""".as[Option[BigDecimal]].headOption,
614-
"getTotalAmuletBalanceForLastAggregatedRound",
615-
)
616-
} else {
617-
// fall back if the requested round is not the latest aggregated round.
618-
storage.query(
619-
// TODO(#800) change to query from round_totals when amulet expiry works again
620-
sql"""
621-
with most_recent as (
622-
select max(closed_round) as closed_round,
623-
party
624-
from round_party_totals
625-
where store_id = $roundTotalsStoreId
626-
and closed_round <= $asOfEndOfRound
627-
group by party
599+
result <- ensureAggregated(asOfEndOfRound) { _ =>
600+
storage.query(
601+
// TODO(#800) change to query from round_totals when amulet expiry works again
602+
// the round_total_amulet_balance is sparse and might not have an entry for the requested round
603+
// which is why the first entry found <= asOfEndOfRound is used
604+
sql"""
605+
select greatest(
606+
0,
607+
sum_cumulative_change_to_initial_amount_as_of_round_zero -
608+
sum_cumulative_change_to_holding_fees_rate * ($asOfEndOfRound + 1)
628609
)
629-
select sum(greatest(0, rpt.cumulative_change_to_initial_amount_as_of_round_zero - rpt.cumulative_change_to_holding_fees_rate * ($asOfEndOfRound + 1)))
630-
from round_party_totals rpt,
631-
most_recent mr
632-
where rpt.store_id = $roundTotalsStoreId
633-
and rpt.party = mr.party
634-
and rpt.closed_round = mr.closed_round;
635-
""".as[Option[BigDecimal]].headOption,
636-
"getTotalAmuletBalanceForEarlierRound",
637-
)
638-
}
610+
from round_total_amulet_balance
611+
where store_id = $roundTotalsStoreId
612+
and closed_round <= $asOfEndOfRound
613+
order by closed_round desc
614+
limit 1;
615+
""".as[BigDecimal].headOption,
616+
"getTotalAmuletBalance",
617+
)
639618
}
640-
} yield result.flatten.getOrElse(0)
619+
} yield result
641620
}
642621

643622
override def getTotalRewardsCollectedEver()(implicit tc: TraceContext): Future[BigDecimal] =

0 commit comments

Comments
 (0)