From 1520329582ef42e3408c50787ac461e197587b4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Perek?= Date: Thu, 11 Jun 2026 20:45:26 +0000 Subject: [PATCH 1/3] sort with backfill [ci] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Perek --- .../src/main/openapi/common-internal.yaml | 1 + .../SqlIndexInitializationTrigger.scala | 9 +++ .../store/DbVotesStoreQueryBuilder.scala | 7 ++- ...lIndexInitializationTriggerStoreTest.scala | 1 + .../splice/store/db/ScanStoreTest.scala | 60 +++++++++++++++++++ 5 files changed, 76 insertions(+), 2 deletions(-) diff --git a/apps/common/src/main/openapi/common-internal.yaml b/apps/common/src/main/openapi/common-internal.yaml index b1a860e09e..fa5d203a63 100644 --- a/apps/common/src/main/openapi/common-internal.yaml +++ b/apps/common/src/main/openapi/common-internal.yaml @@ -343,6 +343,7 @@ components: type: integer description: | Cursor for pagination. When requesting the next page of results, pass the `next_page_token` from the previous response. + Results are ordered by ledger record time, descending. ListDsoRulesVoteResultsResponse: type: object diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTrigger.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTrigger.scala index 0aa7411708..c5981e330f 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTrigger.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTrigger.scala @@ -255,6 +255,15 @@ object SqlIndexInitializationTrigger { where entry_type = 'vot' """, ), + IndexAction + .Create( + indexName = "scan_txlog_store_sid_rt_en_vot", + createAction = sqlu""" + create index concurrently if not exists scan_txlog_store_sid_rt_en_vot + on scan_txlog_store (store_id, record_time desc, entry_number desc) + where entry_type = 'vot' + """, + ), ) sealed trait Task extends Product with Serializable with PrettyPrinting diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/DbVotesStoreQueryBuilder.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/DbVotesStoreQueryBuilder.scala index f9e6c01559..9f482f448f 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/DbVotesStoreQueryBuilder.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/DbVotesStoreQueryBuilder.scala @@ -42,7 +42,10 @@ trait DbVotesTxLogStoreQueryBuilder[TXE] TxLogQueries.SelectFromTxLogTableResult ], TxLogQueries.SelectFromTxLogTableResult, Effect.Read] = { val afterCondition = after match { - case Some(a) => Some(sql"""entry_number < $a""") + case Some(a) => + Some( + sql"""(record_time, entry_number) < ((select record_time from #$txLogTableName where store_id = $txLogStoreId and entry_number = $a), $a)""" + ) case None => None } val actionNameCondition = actionName match { @@ -92,7 +95,7 @@ trait DbVotesTxLogStoreQueryBuilder[TXE] txLogTableName, txLogStoreId, where = whereClause.toActionBuilder, - orderLimit = sql"""order by entry_number desc limit ${sqlLimit(limit)}""", + orderLimit = sql"""order by record_time desc, entry_number desc limit ${sqlLimit(limit)}""", ) } } diff --git a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTriggerStoreTest.scala b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTriggerStoreTest.scala index 60493154ec..71b3900b36 100644 --- a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTriggerStoreTest.scala +++ b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTriggerStoreTest.scala @@ -60,6 +60,7 @@ class SqlIndexInitializationTriggerStoreTest "updt_hist_crea_hi_mi_ci_import_updates", "updt_hist_tran_hi_eth", "scan_txlog_store_sid_en_vot", + "scan_txlog_store_sid_rt_en_vot", ) } } diff --git a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/store/db/ScanStoreTest.scala b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/store/db/ScanStoreTest.scala index 8b696f1c20..bc1663b25c 100644 --- a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/store/db/ScanStoreTest.scala +++ b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/store/db/ScanStoreTest.scala @@ -476,6 +476,66 @@ abstract class ScanStoreTest val voteRequestContracts = mkVoteRequests() assertListOfAllPastVoteRequestResults(voteRequestContracts, store) } + + "return results in record time order, including backfilled entries" in { + // Simulates a txlog where backfilling inserted older entries after live ingestion + // started: backfilling walks backwards in time, so backfilled entries get entry + // numbers in reverse record time order. + val baseTime = Instant.now().truncatedTo(ChronoUnit.MICROS) + def recordTime(n: Int) = baseTime.plusSeconds(n.toLong) + val voteRequests = (1 to 4).map { n => + voteRequest( + requester = userParty(n), + votes = Seq( + new Vote(userParty(n).toProtoPrimitive, true, new Reason("", ""), Optional.empty()) + ), + ) + } + val results = voteRequests.map(mkVoteRequestResult(_)) + def closeVoteRequest(store: ScanStore, n: Int) = + dummyDomain.exercise( + contract = dsoRules(dsoParty), + interfaceId = Some(DsoRules.TEMPLATE_ID_WITH_PACKAGE_ID), + choiceName = DsoRulesCloseVoteRequest.choice.name, + choiceArgument = mkCloseVoteRequest(voteRequests(n - 1).contractId), + exerciseResult = results(n - 1).toValue, + recordTime = recordTime(n), + )(store.multiDomainAcsStore) + for { + store <- mkStore() + _ <- MonadUtil.sequentialTraverse(voteRequests)( + dummyDomain.create(_)(store.multiDomainAcsStore) + ) + // Live ingestion of results 3 and 4... + _ <- closeVoteRequest(store, 3) + _ <- closeVoteRequest(store, 4) + // ...then backfilling inserts the older results, walking backwards in time. + _ <- closeVoteRequest(store, 2) + _ <- closeVoteRequest(store, 1) + page1 <- store.listVoteRequestResults( + None, + None, + None, + None, + None, + PageLimit.tryCreate(3), + ) + page2 <- store.listVoteRequestResults( + None, + None, + None, + None, + None, + PageLimit.tryCreate(3), + page1.nextPageToken, + ) + } yield { + page1.resultsInPage.map(_.request.requester) shouldBe + Seq(4, 3, 2).map(userParty(_).toProtoPrimitive) + page2.resultsInPage.map(_.request.requester) shouldBe + Seq(1).map(userParty(_).toProtoPrimitive) + } + } } "listVoteRequestsByTrackingCid" should { From f3e55ce4ab5db6bd2973248a3feff244392a8191 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Perek?= Date: Fri, 12 Jun 2026 10:25:31 +0000 Subject: [PATCH 2/3] drop old index [ci] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Perek --- .../automation/SqlIndexInitializationTrigger.scala | 14 ++++---------- .../SqlIndexInitializationTriggerStoreTest.scala | 2 +- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTrigger.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTrigger.scala index c5981e330f..e583a5fce7 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTrigger.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTrigger.scala @@ -226,7 +226,7 @@ object SqlIndexInitializationTrigger { } /** Indexes managed by this trigger class */ - val defaultIndexActions: List[IndexAction] = List( + val defaultIndexActions: List[IndexAction] = List[IndexAction]( IndexAction .Create( indexName = "updt_hist_crea_hi_mi_ci_import_updates", @@ -246,15 +246,6 @@ object SqlIndexInitializationTrigger { where external_transaction_hash is not null """, ), - IndexAction - .Create( - indexName = "scan_txlog_store_sid_en_vot", - createAction = sqlu""" - create index concurrently if not exists scan_txlog_store_sid_en_vot - on scan_txlog_store (store_id, entry_number desc) - where entry_type = 'vot' - """, - ), IndexAction .Create( indexName = "scan_txlog_store_sid_rt_en_vot", @@ -264,6 +255,9 @@ object SqlIndexInitializationTrigger { where entry_type = 'vot' """, ), + // Superseded by scan_txlog_store_sid_rt_en_vot when vote results switched from + // entry_number to record time ordering. + IndexAction.Drop(indexName = "scan_txlog_store_sid_en_vot"), ) sealed trait Task extends Product with Serializable with PrettyPrinting diff --git a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTriggerStoreTest.scala b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTriggerStoreTest.scala index 71b3900b36..0570c22c0d 100644 --- a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTriggerStoreTest.scala +++ b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTriggerStoreTest.scala @@ -59,9 +59,9 @@ class SqlIndexInitializationTriggerStoreTest indexNames should contain allElementsOf Seq( "updt_hist_crea_hi_mi_ci_import_updates", "updt_hist_tran_hi_eth", - "scan_txlog_store_sid_en_vot", "scan_txlog_store_sid_rt_en_vot", ) + indexNames should not contain "scan_txlog_store_sid_en_vot" } } From 3d728481be31face552649412ad40cbe9243e1c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Perek?= Date: Fri, 12 Jun 2026 10:38:12 +0000 Subject: [PATCH 3/3] reset sort order [ci] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Perek --- apps/sv/frontend/src/routes/governance.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/sv/frontend/src/routes/governance.tsx b/apps/sv/frontend/src/routes/governance.tsx index 0d4086d2d9..4998664eeb 100644 --- a/apps/sv/frontend/src/routes/governance.tsx +++ b/apps/sv/frontend/src/routes/governance.tsx @@ -197,6 +197,7 @@ export const Governance: React.FC = () => { uniqueId="vote-history" showStatus showVoteStats + sortOrder="effectiveAtDesc" fetchNextPage={voteResultsInfiniteQuery.fetchNextPage} hasNextPage={voteResultsInfiniteQuery.hasNextPage} isFetchingNextPage={voteResultsInfiniteQuery.isFetchingNextPage}