From 265ccf7f9bf6b44149141efcbda9d5051ed0f475 Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Wed, 12 Aug 2026 16:28:08 +0000 Subject: [PATCH 01/11] Public Sequencer: Add a DevNet only HTTP endpoint to SV App, that creates free MemberTraffic contracts for the provided participantId, using its own test CC Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- apps/sv/src/main/openapi/sv-internal.yaml | 33 +++++++++++++++++++ .../splice/sv/config/SvAppConfig.scala | 2 ++ 2 files changed, 35 insertions(+) diff --git a/apps/sv/src/main/openapi/sv-internal.yaml b/apps/sv/src/main/openapi/sv-internal.yaml index f20abd07ef..34da6d045e 100644 --- a/apps/sv/src/main/openapi/sv-internal.yaml +++ b/apps/sv/src/main/openapi/sv-internal.yaml @@ -493,6 +493,31 @@ paths: "$ref": "#/components/schemas/OnboardSvSequencerResponse" "400": $ref: "../../../../common/src/main/openapi/common-external.yaml#/components/responses/400" + /v0/devnet/onboard/validator/buy/traffic: + post: + tags: [sv] + x-jvm-package: sv_public + description: "faucet for validator candidates to buy member traffic" + operationId: "devNetBuyMemberTraffic" + requestBody: + required: true + content: + application/json: + schema: + "$ref": "#/components/schemas/DevNetBuyMemberTrafficRequest" + responses: + "200": + description: ok + content: + text/plain: + schema: + type: string + "400": + $ref: "../../../../common/src/main/openapi/common-external.yaml#/components/responses/400" + "500": + $ref: "../../../../common/src/main/openapi/common-external.yaml#/components/responses/500" + "501": + $ref: "../../../../common/src/main/openapi/common-external.yaml#/components/responses/501" /v0/devnet/onboard/validator/prepare: post: tags: [sv] @@ -721,6 +746,14 @@ components: Human-readable alias of the validator party decoded from the stored secret. type: string + DevNetBuyMemberTrafficRequest: + type: object + required: + - participant_id + properties: + participant_id: + type: string + PrepareValidatorOnboardingRequest: type: object required: diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala index 98b413b339..dd8854834c 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala @@ -485,6 +485,8 @@ case class SvAppBackendConfig( // where intentional overlap is required. instanceLockEnabled: Boolean = true, minMemberTrafficToOnboardValidator: Long = 100000L, + devNetPublicSetupTapAmount: BigDecimal = 10000.0, // will be moved to daml, later + devNetPublicSetupTrafficAmount: Long = 10000000L, ) extends SpliceBackendConfig { def allIgnoredAmuletVersions: Set[String] = From 36e91e8299504f6478904b29b7858558d937f70c Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Thu, 13 Aug 2026 09:43:48 +0000 Subject: [PATCH 02/11] did a make clean-all make build Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- .../splice/sv/admin/http/HttpSvPublicHandler.scala | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala index 381a2a6e08..a7d5e486c1 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala @@ -307,6 +307,16 @@ class HttpSvPublicHandler( } } + /** Intended use: Used by validator candidates to buy member traffic using SV's DevNet faucet + * + * Protection: Rate limiting, endpoint only used for DevNet + */ + override def devNetBuyMemberTraffic( + respond: r0.DevNetBuyMemberTrafficResponse.type + )( + body: definitions.DevNetBuyMemberTrafficRequest + )(extracted: TraceContext): Future[r0.DevNetBuyMemberTrafficResponse] = ??? + /** Intended use: Used by other validators to get a free onboarding secret * * Protection: Rate limiting, endpoint only used for DevNet From 281772ae8a32574f242ee18194933592f82e9692 Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Thu, 13 Aug 2026 10:28:17 +0000 Subject: [PATCH 03/11] Added handler Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- .../sv/admin/http/HttpSvPublicHandler.scala | 86 ++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala index a7d5e486c1..fbfec5e2cd 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala @@ -315,7 +315,91 @@ class HttpSvPublicHandler( respond: r0.DevNetBuyMemberTrafficResponse.type )( body: definitions.DevNetBuyMemberTrafficRequest - )(extracted: TraceContext): Future[r0.DevNetBuyMemberTrafficResponse] = ??? + )(extracted: TraceContext): Future[r0.DevNetBuyMemberTrafficResponse] = { + implicit val traceContext: TraceContext = extracted + withSpan(s"$workflowId.devNetBuyMemberTraffic") { _ => _ => + if (isDevNet) { + for { + participantId <- ParticipantId.fromProtoPrimitive( + body.participantId, + "participant_id", + ) match { + case Right(id) => Future.successful(id) + case Left(err) => + Future.failed(HttpErrorHandler.badRequest(s"Invalid participant ID: $err")) + } + amuletRules <- dsoStore.getAmuletRules() + dsoRules <- dsoStore.getDsoRules() + openRound <- dsoStore.getLatestActiveOpenMiningRound() + + _ = logger.info(s"AmuletRules DevNet Tap by $svUserName") + + tapCmd = amuletRules.exercise( + _.exerciseAmuletRules_DevNet_Tap( + svParty.toProtoPrimitive, + config.devNetPublicSetupTapAmount.bigDecimal, + openRound.contractId, + ) + ) + + tapResult <- dsoStoreWithIngestion + .connection(SpliceLedgerConnectionPriority.Low) + .submit( + actAs = Seq(svParty), + readAs = Seq.empty, + update = tapCmd, + ) + .withSynchronizerId(dsoRules.domain) + .noDedup + .yieldResult() + + amuletCid = tapResult.exerciseResult.amuletSum.amulet + + _ = logger.info(s"AmuletRules BuyMemberTraffic by $svUserName for $participantId") + + buyCmd = amuletRules.exercise( + _.exerciseAmuletRules_BuyMemberTraffic( + java.util.List.of( + new org.lfdecentralizedtrust.splice.codegen.java.splice.amuletrules.transferinput.InputAmulet( + amuletCid + ) + ), + new org.lfdecentralizedtrust.splice.codegen.java.splice.amuletrules.TransferContext( + openRound.contractId, + java.util.Collections.emptyMap(), + java.util.Collections.emptyMap(), + java.util.Optional.empty(), + ), + svParty.toProtoPrimitive, + participantId.toProtoPrimitive, + dsoRules.payload.config.decentralizedSynchronizer.activeSynchronizerId, + dsoStore.domainMigrationId, + config.devNetPublicSetupTrafficAmount, + java.util.Optional.of(dsoParty.toProtoPrimitive), + ) + ) + + _ <- dsoStoreWithIngestion + .connection(SpliceLedgerConnectionPriority.Low) + .submit( + actAs = Seq(svParty), + readAs = Seq(dsoParty), + update = buyCmd, + ) + .withSynchronizerId(dsoRules.domain) + .noDedup + .yieldUnit() + + } yield r0.DevNetBuyMemberTrafficResponseOK("Success") + } else { + Future.failed( + HttpErrorHandler.notImplemented( + "DevNet traffic purchasing self-service is only available in DevNet." + ) + ) + } + } + } /** Intended use: Used by other validators to get a free onboarding secret * From 5e8b416966c7b8c6c18473e17b7dc428291669c6 Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Thu, 13 Aug 2026 14:41:46 +0000 Subject: [PATCH 04/11] Added test [ci] Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- .../splice/console/SvAppReference.scala | 6 +++++ ...missionedSynchronizerIntegrationTest.scala | 20 +++++++++++++++++ .../commands/HttpSvPublicAppClient.scala | 22 +++++++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/SvAppReference.scala b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/SvAppReference.scala index 9f9083697c..4b1ed90cbb 100644 --- a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/SvAppReference.scala +++ b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/SvAppReference.scala @@ -83,6 +83,12 @@ abstract class SvAppReference( httpCommand(HttpSvPublicAppClient.getSvOnboardingStatus(candidate)) } + @Help.Summary("Buy member traffic for a participant (DevNet only) (via client API)") + def devNetBuyMemberTraffic(participantId: ParticipantId): Unit = + consoleEnvironment.run { + httpCommand(HttpSvPublicAppClient.DevNetBuyMemberTraffic(participantId.toProtoPrimitive)) + } + @Help.Summary("Prepare a validator onboarding and return an onboarding secret (via client API)") def devNetOnboardValidatorPrepare(): String = consoleEnvironment.run { diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/PermissionedSynchronizerIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/PermissionedSynchronizerIntegrationTest.scala index e59d2efaa5..2f0a5abc4c 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/PermissionedSynchronizerIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/PermissionedSynchronizerIntegrationTest.scala @@ -100,5 +100,25 @@ class PermissionedSynchronizerIntegrationTest aliceValidatorBackend.onboardUser("TestUser") }, ) + + clue("Sponser SV Buys Member Traffic for Bob in the DevNet") { + sv1Backend.devNetBuyMemberTraffic(bobValidatorBackend.participantClient.id) + } + + clue("Verify Bob is granted ParticipantSynchronizerPermission") { + eventually() { + sv1ScanBackend.getParticipantSynchronizerPermission( + decentralizedSynchronizerId.toProtoPrimitive, + bobValidatorBackend.participantClient.id.toProtoPrimitive, + ) shouldBe Some( + SynchronizerPermissionState(None) + ) + } + } + + clue("Bob Validator starts and onboards correctly") { + bobValidatorBackend.startSync() + bobValidatorBackend.onboardUser("TestUserBob") + } } } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/api/client/commands/HttpSvPublicAppClient.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/api/client/commands/HttpSvPublicAppClient.scala index 5111c3b7f0..378e93d8e9 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/api/client/commands/HttpSvPublicAppClient.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/api/client/commands/HttpSvPublicAppClient.scala @@ -158,6 +158,28 @@ object HttpSvPublicAppClient { } } + case class DevNetBuyMemberTraffic(participantId: String) + extends BaseCommandPublic[http.DevNetBuyMemberTrafficResponse, Unit] { + + override def submitRequest( + client: Client, + headers: List[HttpHeader], + ): EitherT[Future, Either[ + Throwable, + HttpResponse, + ], http.DevNetBuyMemberTrafficResponse] = + client.devNetBuyMemberTraffic( + body = definitions.DevNetBuyMemberTrafficRequest(participantId), + headers = headers, + ) + + override def handleOk()(implicit + decoder: TemplateJsonDecoder + ) = { case http.DevNetBuyMemberTrafficResponse.OK(_) => + Right(()) + } + } + case class DevNetOnboardValidatorPrepare() extends BaseCommandPublic[http.DevNetOnboardValidatorPrepareResponse, String] { From 2699b298fff3a84e9bd3936b50e9dd1615fc262f Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Thu, 13 Aug 2026 15:20:46 +0000 Subject: [PATCH 05/11] Fixes Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- .../splice/sv/admin/http/HttpSvPublicHandler.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala index fbfec5e2cd..5c7b959b16 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala @@ -346,7 +346,7 @@ class HttpSvPublicHandler( .connection(SpliceLedgerConnectionPriority.Low) .submit( actAs = Seq(svParty), - readAs = Seq.empty, + readAs = Seq(dsoParty), update = tapCmd, ) .withSynchronizerId(dsoRules.domain) From 1cb531f9a6b161f42bea08db394606303f88d4ff Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Thu, 13 Aug 2026 16:04:32 +0000 Subject: [PATCH 06/11] Refactor Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- .../sv/admin/http/HttpSvPublicHandler.scala | 131 ++++++++++-------- .../splice/sv/config/SvAppConfig.scala | 2 +- 2 files changed, 71 insertions(+), 62 deletions(-) diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala index 5c7b959b16..c580af6093 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala @@ -328,67 +328,7 @@ class HttpSvPublicHandler( case Left(err) => Future.failed(HttpErrorHandler.badRequest(s"Invalid participant ID: $err")) } - amuletRules <- dsoStore.getAmuletRules() - dsoRules <- dsoStore.getDsoRules() - openRound <- dsoStore.getLatestActiveOpenMiningRound() - - _ = logger.info(s"AmuletRules DevNet Tap by $svUserName") - - tapCmd = amuletRules.exercise( - _.exerciseAmuletRules_DevNet_Tap( - svParty.toProtoPrimitive, - config.devNetPublicSetupTapAmount.bigDecimal, - openRound.contractId, - ) - ) - - tapResult <- dsoStoreWithIngestion - .connection(SpliceLedgerConnectionPriority.Low) - .submit( - actAs = Seq(svParty), - readAs = Seq(dsoParty), - update = tapCmd, - ) - .withSynchronizerId(dsoRules.domain) - .noDedup - .yieldResult() - - amuletCid = tapResult.exerciseResult.amuletSum.amulet - - _ = logger.info(s"AmuletRules BuyMemberTraffic by $svUserName for $participantId") - - buyCmd = amuletRules.exercise( - _.exerciseAmuletRules_BuyMemberTraffic( - java.util.List.of( - new org.lfdecentralizedtrust.splice.codegen.java.splice.amuletrules.transferinput.InputAmulet( - amuletCid - ) - ), - new org.lfdecentralizedtrust.splice.codegen.java.splice.amuletrules.TransferContext( - openRound.contractId, - java.util.Collections.emptyMap(), - java.util.Collections.emptyMap(), - java.util.Optional.empty(), - ), - svParty.toProtoPrimitive, - participantId.toProtoPrimitive, - dsoRules.payload.config.decentralizedSynchronizer.activeSynchronizerId, - dsoStore.domainMigrationId, - config.devNetPublicSetupTrafficAmount, - java.util.Optional.of(dsoParty.toProtoPrimitive), - ) - ) - - _ <- dsoStoreWithIngestion - .connection(SpliceLedgerConnectionPriority.Low) - .submit( - actAs = Seq(svParty), - readAs = Seq(dsoParty), - update = buyCmd, - ) - .withSynchronizerId(dsoRules.domain) - .noDedup - .yieldUnit() + _ <- devNetTapAndBuyMemberTraffic(participantId) } yield r0.DevNetBuyMemberTrafficResponseOK("Success") } else { @@ -944,6 +884,75 @@ class HttpSvPublicHandler( .yieldUnit() } yield () + private def devNetTapAndBuyMemberTraffic( + participantId: ParticipantId + )(implicit tc: TraceContext): Future[Unit] = { + for { + amuletRules <- dsoStore.getAmuletRules() + dsoRules <- dsoStore.getDsoRules() + roundTriple <- dsoStore.getOpenMiningRoundTriple() + openRound = roundTriple.oldest + + _ = logger.info(s"AmuletRules DevNet Tap by $svUserName") + + tapCmd = amuletRules.exercise( + _.exerciseAmuletRules_DevNet_Tap( + svParty.toProtoPrimitive, + config.devNetPublicSetupTapAmount.bigDecimal, + openRound.contractId, + ) + ) + + tapResult <- dsoStoreWithIngestion + .connection(SpliceLedgerConnectionPriority.Low) + .submit( + actAs = Seq(svParty), + readAs = Seq(dsoParty), + update = tapCmd, + ) + .withSynchronizerId(dsoRules.domain) + .noDedup + .yieldResult() + + amuletCid = tapResult.exerciseResult.amuletSum.amulet + + _ = logger.info(s"AmuletRules BuyMemberTraffic by $svUserName for $participantId") + + buyCmd = amuletRules.exercise( + _.exerciseAmuletRules_BuyMemberTraffic( + java.util.List.of( + new org.lfdecentralizedtrust.splice.codegen.java.splice.amuletrules.transferinput.InputAmulet( + amuletCid + ) + ), + new org.lfdecentralizedtrust.splice.codegen.java.splice.amuletrules.TransferContext( + openRound.contractId, + java.util.Collections.emptyMap(), + java.util.Collections.emptyMap(), + java.util.Optional.empty(), + ), + svParty.toProtoPrimitive, + participantId.toProtoPrimitive, + dsoRules.payload.config.decentralizedSynchronizer.activeSynchronizerId, + dsoStore.domainMigrationId, + config.devNetPublicSetupTrafficAmount, + java.util.Optional.of(dsoParty.toProtoPrimitive), + ) + ) + + _ <- dsoStoreWithIngestion + .connection(SpliceLedgerConnectionPriority.Low) + .submit( + actAs = Seq(svParty), + readAs = Seq(dsoParty), + update = buyCmd, + ) + .withSynchronizerId(dsoRules.domain) + .noDedup + .yieldUnit() + } yield () + } + private def startSvOnboarding( candidateName: String, candidateParty: PartyId, diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala index dd8854834c..0a8ad90179 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala @@ -485,7 +485,7 @@ case class SvAppBackendConfig( // where intentional overlap is required. instanceLockEnabled: Boolean = true, minMemberTrafficToOnboardValidator: Long = 100000L, - devNetPublicSetupTapAmount: BigDecimal = 10000.0, // will be moved to daml, later + devNetPublicSetupTapAmount: BigDecimal = 1000000.0, // will be moved to daml, later devNetPublicSetupTrafficAmount: Long = 10000000L, ) extends SpliceBackendConfig { From dc9ef4a8a0b976449aaa85c24056f7ad93912452 Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Thu, 13 Aug 2026 16:34:40 +0000 Subject: [PATCH 07/11] Trigger Done [ci] Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- .../lfdecentralizedtrust/splice/console/SvAppReference.scala | 2 +- apps/sv/src/main/openapi/sv-internal.yaml | 2 +- .../splice/sv/admin/http/HttpSvPublicHandler.scala | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/SvAppReference.scala b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/SvAppReference.scala index 4b1ed90cbb..2097f429c8 100644 --- a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/SvAppReference.scala +++ b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/SvAppReference.scala @@ -83,7 +83,7 @@ abstract class SvAppReference( httpCommand(HttpSvPublicAppClient.getSvOnboardingStatus(candidate)) } - @Help.Summary("Buy member traffic for a participant (DevNet only) (via client API)") + @Help.Summary("Buy member traffic for a new validator (DevNet only) (via client API)") def devNetBuyMemberTraffic(participantId: ParticipantId): Unit = consoleEnvironment.run { httpCommand(HttpSvPublicAppClient.DevNetBuyMemberTraffic(participantId.toProtoPrimitive)) diff --git a/apps/sv/src/main/openapi/sv-internal.yaml b/apps/sv/src/main/openapi/sv-internal.yaml index 34da6d045e..2280b01486 100644 --- a/apps/sv/src/main/openapi/sv-internal.yaml +++ b/apps/sv/src/main/openapi/sv-internal.yaml @@ -493,7 +493,7 @@ paths: "$ref": "#/components/schemas/OnboardSvSequencerResponse" "400": $ref: "../../../../common/src/main/openapi/common-external.yaml#/components/responses/400" - /v0/devnet/onboard/validator/buy/traffic: + /v0/devnet/onboard/validator/purchase-traffic: post: tags: [sv] x-jvm-package: sv_public diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala index c580af6093..d23b017e0b 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala @@ -334,7 +334,7 @@ class HttpSvPublicHandler( } else { Future.failed( HttpErrorHandler.notImplemented( - "DevNet traffic purchasing self-service is only available in DevNet." + "Traffic purchasing self-service is only available in DevNet." ) ) } From 5a5bb699072bd28d0fbf43c8e094c49bc8f4a1fb Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Mon, 17 Aug 2026 22:04:53 +0000 Subject: [PATCH 08/11] Addressed all feedback [ci] Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- .../splice/sv/SvApp.scala | 2 +- .../sv/admin/http/HttpSvPublicHandler.scala | 80 +++++++----------- .../splice/sv/config/SvAppConfig.scala | 1 - .../sv/onboarding/NodeInitializerUtil.scala | 1 + .../splice/sv/store/SvSvStore.scala | 84 +++++++++++++------ .../splice/sv/store/db/DbSvSvStore.scala | 31 ++++++- .../splice/store/db/SvSvStoreTest.scala | 72 +++++++++++++++- 7 files changed, 191 insertions(+), 80 deletions(-) diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/SvApp.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/SvApp.scala index 3de5e74742..8946730723 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/SvApp.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/SvApp.scala @@ -118,7 +118,7 @@ class SvApp( ) { override def packagesForJsonDecoding: Seq[DarResource] = - super.packagesForJsonDecoding ++ DarResources.dsoGovernance.all ++ DarResources.validatorLifecycle.all ++ DarResources.amuletNameService.all + super.packagesForJsonDecoding ++ DarResources.dsoGovernance.all ++ DarResources.validatorLifecycle.all ++ DarResources.amuletNameService.all ++ DarResources.wallet.all override def preInitializeBeforeLedgerConnection()(implicit tc: TraceContext): Future[Unit] = { val participantAdminConnection = new ParticipantAdminConnection( diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala index d23b017e0b..c684e0f450 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala @@ -318,7 +318,7 @@ class HttpSvPublicHandler( )(extracted: TraceContext): Future[r0.DevNetBuyMemberTrafficResponse] = { implicit val traceContext: TraceContext = extracted withSpan(s"$workflowId.devNetBuyMemberTraffic") { _ => _ => - if (isDevNet) { + if (isDevNet && config.permissionedSynchronizer) { for { participantId <- ParticipantId.fromProtoPrimitive( body.participantId, @@ -334,7 +334,7 @@ class HttpSvPublicHandler( } else { Future.failed( HttpErrorHandler.notImplemented( - "Traffic purchasing self-service is only available in DevNet." + "Traffic purchasing self-service is only available in DevNet when permissioned synchronizer is enabled." ) ) } @@ -888,68 +888,48 @@ class HttpSvPublicHandler( participantId: ParticipantId )(implicit tc: TraceContext): Future[Unit] = { for { - amuletRules <- dsoStore.getAmuletRules() dsoRules <- dsoStore.getDsoRules() - roundTriple <- dsoStore.getOpenMiningRoundTriple() - openRound = roundTriple.oldest - - _ = logger.info(s"AmuletRules DevNet Tap by $svUserName") - tapCmd = amuletRules.exercise( - _.exerciseAmuletRules_DevNet_Tap( - svParty.toProtoPrimitive, - config.devNetPublicSetupTapAmount.bigDecimal, - openRound.contractId, - ) + svWalletInstall <- retryProvider.retryForClientCalls( + "wait_for_wallet_install", + "Wait for SV WalletAppInstall contract to be ingested", + for { + svWalletInstallOpt <- svStoreWithIngestion.store.lookupWalletAppInstallByEndUser(svParty) + install <- svWalletInstallOpt match { + case Some(install) => Future.successful(install) + case None => + Future.failed( + HttpErrorHandler.internalServerError( + "SV WalletAppInstall contract not found." + ) + ) + } + } yield install, + logger, ) - tapResult <- dsoStoreWithIngestion - .connection(SpliceLedgerConnectionPriority.Low) - .submit( - actAs = Seq(svParty), - readAs = Seq(dsoParty), - update = tapCmd, - ) - .withSynchronizerId(dsoRules.domain) - .noDedup - .yieldResult() - - amuletCid = tapResult.exerciseResult.amuletSum.amulet - - _ = logger.info(s"AmuletRules BuyMemberTraffic by $svUserName for $participantId") - - buyCmd = amuletRules.exercise( - _.exerciseAmuletRules_BuyMemberTraffic( - java.util.List.of( - new org.lfdecentralizedtrust.splice.codegen.java.splice.amuletrules.transferinput.InputAmulet( - amuletCid - ) - ), - new org.lfdecentralizedtrust.splice.codegen.java.splice.amuletrules.TransferContext( - openRound.contractId, - java.util.Collections.emptyMap(), - java.util.Collections.emptyMap(), - java.util.Optional.empty(), - ), - svParty.toProtoPrimitive, - participantId.toProtoPrimitive, - dsoRules.payload.config.decentralizedSynchronizer.activeSynchronizerId, - dsoStore.domainMigrationId, - config.devNetPublicSetupTrafficAmount, - java.util.Optional.of(dsoParty.toProtoPrimitive), - ) + cmd = svWalletInstall.contractId.exerciseWalletAppInstall_CreateBuyTrafficRequest( + participantId.toProtoPrimitive, + dsoRules.payload.config.decentralizedSynchronizer.activeSynchronizerId, + dsoStore.domainMigrationId.toInt, + config.devNetPublicSetupTrafficAmount, + clock.now.plus(java.time.Duration.ofMinutes(5)).toInstant, + s"devnet-onboard-${participantId.toProtoPrimitive}-${clock.now.toInstant.toEpochMilli}", ) + _ = logger.info(s"Creating BuyTrafficRequest by $svUserName for $participantId") + _ <- dsoStoreWithIngestion - .connection(SpliceLedgerConnectionPriority.Low) + .connection(SpliceLedgerConnectionPriority.Medium) .submit( actAs = Seq(svParty), readAs = Seq(dsoParty), - update = buyCmd, + update = cmd, ) .withSynchronizerId(dsoRules.domain) .noDedup .yieldUnit() + } yield () } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala index 0a8ad90179..b8e8dee1f5 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala @@ -485,7 +485,6 @@ case class SvAppBackendConfig( // where intentional overlap is required. instanceLockEnabled: Boolean = true, minMemberTrafficToOnboardValidator: Long = 100000L, - devNetPublicSetupTapAmount: BigDecimal = 1000000.0, // will be moved to daml, later devNetPublicSetupTrafficAmount: Long = 10000000L, ) extends SpliceBackendConfig { diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/NodeInitializerUtil.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/NodeInitializerUtil.scala index e10edf4164..32ebb6a2a0 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/NodeInitializerUtil.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/NodeInitializerUtil.scala @@ -95,6 +95,7 @@ trait NodeInitializerUtil extends NamedLogging with Spanning with SynchronizerNo config.automation.ingestion, config.parameters.defaultLimit, acsStoreDescriptorUserVersion, + Some(config), ) protected def newSvSvAutomationService( diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvSvStore.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvSvStore.scala index 4345d88999..c7036fd2ba 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvSvStore.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvSvStore.scala @@ -19,16 +19,19 @@ import org.lfdecentralizedtrust.splice.util.{Contract, TemplateJsonDecoder} import com.digitalasset.canton.lifecycle.CloseContext import com.digitalasset.canton.logging.NamedLoggerFactory import com.digitalasset.canton.resource.DbStorage -import com.digitalasset.canton.topology.ParticipantId +import com.digitalasset.canton.topology.{ParticipantId, PartyId} import com.digitalasset.canton.tracing.TraceContext +import org.lfdecentralizedtrust.splice.codegen.java.splice.wallet.install.WalletAppInstall import org.lfdecentralizedtrust.splice.config.IngestionConfig import org.lfdecentralizedtrust.splice.store.db.AcsInterfaceViewRowData +import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig import scala.concurrent.{ExecutionContext, Future} /* Store used by the SV app for filtering contracts visible to the SV party. */ trait SvSvStore extends AppStore { + def config: Option[SvAppBackendConfig] protected val outerLoggerFactory: NamedLoggerFactory override protected lazy val loggerFactory: NamedLoggerFactory = @@ -38,7 +41,7 @@ trait SvSvStore extends AppStore { : org.lfdecentralizedtrust.splice.store.MultiDomainAcsStore.ContractFilter[ org.lfdecentralizedtrust.splice.sv.store.db.SvTables.SvAcsStoreRowData, AcsInterfaceViewRowData.NoInterfacesIngested, - ] = SvSvStore.contractFilter(key) + ] = SvSvStore.contractFilter(key, config.map(_.permissionedSynchronizer).getOrElse(false)) def lookupValidatorOnboardingBySecretWithOffset( secret: String @@ -53,6 +56,19 @@ trait SvSvStore extends AppStore { ] = lookupValidatorOnboardingBySecretWithOffset(secret).map(_.value) + def lookupWalletAppInstallByEndUserWithOffset( + endUserParty: PartyId + )(implicit tc: TraceContext): Future[ + QueryResult[Option[Contract[WalletAppInstall.ContractId, WalletAppInstall]]] + ] + + def lookupWalletAppInstallByEndUser( + endUserParty: PartyId + )(implicit tc: TraceContext): Future[ + Option[Contract[WalletAppInstall.ContractId, WalletAppInstall]] + ] = + lookupWalletAppInstallByEndUserWithOffset(endUserParty).map(_.value) + def lookupUsedSecretWithOffset( secret: String )(implicit tc: TraceContext): Future[ @@ -103,6 +119,7 @@ object SvSvStore { ingestionConfig: IngestionConfig, defaultLimit: Limit, acsStoreDescriptorUserVersion: Option[Long] = None, + svBackendconfig: Option[SvAppBackendConfig] = None, )(implicit ec: ExecutionContext, templateJsonDecoder: TemplateJsonDecoder, @@ -118,40 +135,57 @@ object SvSvStore { ingestionConfig, acsStoreDescriptorUserVersion, defaultLimit = defaultLimit, + config = svBackendconfig, ) /** Contract filter of an sv acs store for a specific acs party. */ - def contractFilter(key: SvStore.Key): MultiDomainAcsStore.ContractFilter[ + def contractFilter( + key: SvStore.Key, + enablePermissionedSynchronizer: Boolean, + ): MultiDomainAcsStore.ContractFilter[ SvAcsStoreRowData, AcsInterfaceViewRowData.NoInterfacesIngested, ] = { import MultiDomainAcsStore.mkFilter val sv = key.svParty.toProtoPrimitive - MultiDomainAcsStore.SimpleContractFilter( - key.svParty, - Map( - mkFilter(vo.ValidatorOnboarding.COMPANION)(co => co.payload.sv == sv) { contract => - SvAcsStoreRowData( - contract, - contractExpiresAt = Some(Timestamp.assertFromInstant(contract.payload.expiresAt)), - onboardingSecret = Some(contract.payload.candidateSecret), - ) - }, - mkFilter(vo.UsedSecret.COMPANION)(co => co.payload.sv == sv) { contract => - SvAcsStoreRowData( - contract, - onboardingSecret = Some(contract.payload.secret), - ) - }, - mkFilter(so.SvOnboardingConfirmed.COMPANION)(co => co.payload.svParty == sv) { contract => + val svFilters = Map( + mkFilter(vo.ValidatorOnboarding.COMPANION)(co => co.payload.sv == sv) { contract => + SvAcsStoreRowData( + contract, + contractExpiresAt = Some(Timestamp.assertFromInstant(contract.payload.expiresAt)), + onboardingSecret = Some(contract.payload.candidateSecret), + ) + }, + mkFilter(vo.UsedSecret.COMPANION)(co => co.payload.sv == sv) { contract => + SvAcsStoreRowData( + contract, + onboardingSecret = Some(contract.payload.secret), + ) + }, + mkFilter(so.SvOnboardingConfirmed.COMPANION)(co => co.payload.svParty == sv) { contract => + SvAcsStoreRowData( + contract, + contractExpiresAt = Some(Timestamp.assertFromInstant(contract.payload.expiresAt)), + svCandidateName = Some(contract.payload.svName), + ) + }, + ) + + val finalFilters = if (enablePermissionedSynchronizer) { + svFilters + mkFilter(WalletAppInstall.COMPANION)(co => co.payload.endUserParty == sv) { + contract => SvAcsStoreRowData( - contract, - contractExpiresAt = Some(Timestamp.assertFromInstant(contract.payload.expiresAt)), - svCandidateName = Some(contract.payload.svName), + contract ) - }, - ), + } + } else { + svFilters + } + + MultiDomainAcsStore.SimpleContractFilter( + key.svParty, + finalFilters, ) } } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/db/DbSvSvStore.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/db/DbSvSvStore.scala index 61157a5b51..c29afc88b1 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/db/DbSvSvStore.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/db/DbSvSvStore.scala @@ -17,10 +17,11 @@ import org.lfdecentralizedtrust.splice.util.{Contract, TemplateJsonDecoder} import com.digitalasset.canton.lifecycle.CloseContext import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging} import com.digitalasset.canton.resource.DbStorage -import com.digitalasset.canton.topology.ParticipantId +import com.digitalasset.canton.topology.{ParticipantId, PartyId} import com.digitalasset.canton.tracing.TraceContext import org.lfdecentralizedtrust.splice.config.IngestionConfig import org.lfdecentralizedtrust.splice.store.db.AcsQueries.AcsStoreId +import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig import slick.jdbc.canton.ActionBasedSQLInterpolation.Implicits.actionBasedSQLInterpolationCanton import scala.concurrent.{ExecutionContext, Future} @@ -35,6 +36,7 @@ class DbSvSvStore( ingestionConfig: IngestionConfig, acsStoreDescriptorUserVersion: Option[Long] = None, override val defaultLimit: Limit, + override val config: Option[SvAppBackendConfig] = None, )(implicit override protected val ec: ExecutionContext, templateJsonDecoder: TemplateJsonDecoder, @@ -104,6 +106,33 @@ class DbSvSvStore( )).getOrRaise(offsetExpectedError()) } + import org.lfdecentralizedtrust.splice.codegen.java.splice.wallet.install.WalletAppInstall + + override def lookupWalletAppInstallByEndUserWithOffset( + endUserParty: PartyId + )(implicit tc: TraceContext): Future[MultiDomainAcsStore.QueryResult[ + Option[Contract[WalletAppInstall.ContractId, WalletAppInstall]] + ]] = waitUntilAcsIngested { + (for { + resultWithOffset <- storage + .querySingle( + selectFromAcsTableWithOffset( + DbSvSvStore.tableName, + acsStoreId, + domainMigrationId, + WalletAppInstall.COMPANION, + where = sql""" + create_arguments->>'endUserParty' = ${lengthLimited(endUserParty.toProtoPrimitive)} + """, + ).headOption, + "lookupWalletAppInstallByEndUserWithOffset", + ) + } yield QueryResult( + resultWithOffset.offset, + resultWithOffset.row.map(contractFromRow(WalletAppInstall.COMPANION)(_)), + )).getOrRaise(offsetExpectedError()) + } + override def lookupUsedSecretWithOffset(secret: String)(implicit tc: TraceContext ): Future[MultiDomainAcsStore.QueryResult[Option[Contract[UsedSecret.ContractId, UsedSecret]]]] = diff --git a/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SvSvStoreTest.scala b/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SvSvStoreTest.scala index d9881f151d..45c2823ceb 100644 --- a/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SvSvStoreTest.scala +++ b/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SvSvStoreTest.scala @@ -12,8 +12,10 @@ import org.lfdecentralizedtrust.splice.util.{ResourceTemplateDecoder, TemplateJs import com.digitalasset.canton.concurrent.FutureSupervisor import com.digitalasset.canton.lifecycle.FutureUnlessShutdown import com.digitalasset.canton.resource.DbStorage +import com.digitalasset.canton.topology.PartyId import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.{HasActorSystem, HasExecutionContext, SynchronizerAlias} +import org.lfdecentralizedtrust.splice.codegen.java.splice.wallet.install.WalletAppInstall import org.lfdecentralizedtrust.splice.config.IngestionConfig import java.time.Instant @@ -104,6 +106,51 @@ abstract class SvSvStoreTest extends StoreTestBase with HasExecutionContext { } } + "lookupWalletAppInstallByEndUserWithOffset" should { + + "find a WalletAppInstall for the SV party and ignore others" in { + val endUser1 = storeSvParty + val endUser2 = PartyId.tryFromProtoPrimitive("endUser2::domain") + + val wanted = walletAppInstall(endUser1, "sv-user") + val unwanted = walletAppInstall(endUser2, "other-user") + + val firstOffset = 101L + val secondOffset = 202L + + for { + store <- mkStore() + _ <- dummyDomain.create( + wanted, + firstOffset, + createdEventSignatories = Seq(storeSvParty), + )(store.multiDomainAcsStore) + + _ <- dummyDomain.create( + unwanted, + secondOffset, + createdEventSignatories = Seq(storeSvParty), + )(store.multiDomainAcsStore) + } yield { + store.lookupWalletAppInstallByEndUserWithOffset(endUser1).futureValue should be( + QueryResult(secondOffset, Some(wanted)) + ) + store.lookupWalletAppInstallByEndUserWithOffset(endUser2).futureValue should be( + QueryResult(secondOffset, None) // we ingest only when co.payload.endUserParty == sv + ) + } + } + + "return just the offset if there's no entries" in { + val nobody = PartyId.tryFromProtoPrimitive("nobody::domain") + for { + store <- mkStore() + result <- store.lookupWalletAppInstallByEndUserWithOffset(nobody) + } yield result should be(QueryResult(acsOffset, None)) + } + + } + "find a UsedSecret by secret in JSON format" in { val wanted = usedSecret( """{"sv": "splice-client-1::dummy", "validator_party_hint": "splice-client-2", "secret": "good_secret"}""" @@ -124,6 +171,23 @@ abstract class SvSvStoreTest extends StoreTestBase with HasExecutionContext { } } + private def walletAppInstall(endUserParty: PartyId, endUserName: String) = { + val template = new WalletAppInstall( + dsoParty.toProtoPrimitive, + storeSvParty.toProtoPrimitive, + endUserName, + endUserParty.toProtoPrimitive, + ) + + val templateId = WalletAppInstall.TEMPLATE_ID_WITH_PACKAGE_ID + + contract( + templateId, + new WalletAppInstall.ContractId(nextCid()), + template, + ) + } + private def validatorOnboarding(secret: String) = { val template = new vo.ValidatorOnboarding( @@ -173,7 +237,8 @@ class DbSvSvStoreTest DarResources.amulet.all ++ DarResources.validatorLifecycle.all ++ DarResources.dsoGovernance.all ++ - DarResources.dsoGovernance.all + DarResources.dsoGovernance.all ++ + DarResources.wallet.all ) implicit val templateJsonDecoder: TemplateJsonDecoder = new ResourceTemplateDecoder(packageSignatures, loggerFactory) @@ -187,7 +252,10 @@ class DbSvSvStoreTest participantId = mkParticipantId("SvSvStoreTest"), IngestionConfig(), defaultLimit = HardLimit.tryCreate(Limit.DefaultMaxPageSize), - )(parallelExecutionContext, implicitly, implicitly) + )(parallelExecutionContext, implicitly, implicitly) { + override lazy val acsContractFilter = + SvSvStore.contractFilter(key, enablePermissionedSynchronizer = true) + } for { _ <- store.multiDomainAcsStore.testIngestionSink.initialize() _ <- store.multiDomainAcsStore.testIngestionSink From fadaad401d0bb643550715a625e227d60d6c4326 Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Tue, 18 Aug 2026 13:09:13 +0000 Subject: [PATCH 09/11] Addressed Feedback; last commit [ci] Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- .../SvDsoStoreIngestionPerformanceTest.scala | 1 - .../sv/onboarding/NodeInitializerUtil.scala | 2 -- .../splice/sv/store/SvDsoStore.scala | 25 ++++++----------- .../splice/sv/store/SvSvStore.scala | 27 ++++++------------- .../splice/sv/store/db/DbSvDsoStore.scala | 1 - .../splice/sv/store/db/DbSvSvStore.scala | 2 -- .../splice/store/db/SvDsoStoreTest.scala | 1 - .../splice/store/db/SvSvStoreTest.scala | 2 +- 8 files changed, 17 insertions(+), 44 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/performance/tests/SvDsoStoreIngestionPerformanceTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/performance/tests/SvDsoStoreIngestionPerformanceTest.scala index a07405c35a..6a5588dc02 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/performance/tests/SvDsoStoreIngestionPerformanceTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/performance/tests/SvDsoStoreIngestionPerformanceTest.scala @@ -64,7 +64,6 @@ class SvDsoStoreIngestionPerformanceTest( participantId = mkParticipantId("IngestionPerformanceIngestionTest"), IngestionConfig(), defaultLimit = HardLimit.tryCreate(Limit.DefaultMaxPageSize), - config = None, )(ec, templateJsonDecoder, closeContext).multiDomainAcsStore } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/NodeInitializerUtil.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/NodeInitializerUtil.scala index 32ebb6a2a0..8dc2867e28 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/NodeInitializerUtil.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/NodeInitializerUtil.scala @@ -95,7 +95,6 @@ trait NodeInitializerUtil extends NamedLogging with Spanning with SynchronizerNo config.automation.ingestion, config.parameters.defaultLimit, acsStoreDescriptorUserVersion, - Some(config), ) protected def newSvSvAutomationService( @@ -148,7 +147,6 @@ trait NodeInitializerUtil extends NamedLogging with Spanning with SynchronizerNo config.automation.ingestion, config.parameters.defaultLimit, acsStoreDescriptorUserVersion, - Some(config), ) } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvDsoStore.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvDsoStore.scala index c5cb3d90e0..80e9c12681 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvDsoStore.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvDsoStore.scala @@ -68,7 +68,6 @@ trait SvDsoStore with ActiveVotesStore { protected val outerLoggerFactory: NamedLoggerFactory protected def templateJsonDecoder: TemplateJsonDecoder - def config: Option[org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig] override protected lazy val loggerFactory: NamedLoggerFactory = outerLoggerFactory.append("store", "dsoParty") @@ -80,7 +79,6 @@ trait SvDsoStore SvDsoStore.contractFilter( key.dsoParty, domainMigrationId, - config.map(_.permissionedSynchronizer).getOrElse(false), ) def key: SvStore.Key @@ -1212,7 +1210,6 @@ object SvDsoStore { ingestionConfig: IngestionConfig, defaultLimit: Limit, acsStoreDescriptorUserVersion: Option[Long] = None, - svBackendconfig: Option[org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig], )(implicit ec: ExecutionContext, templateJsonDecoder: TemplateJsonDecoder, @@ -1228,7 +1225,6 @@ object SvDsoStore { ingestionConfig, acsStoreDescriptorUserVersion, defaultLimit = defaultLimit, - config = svBackendconfig, ) } @@ -1236,7 +1232,6 @@ object SvDsoStore { def contractFilter( dsoParty: PartyId, domainMigrationId: Long, - enablePermissionedSynchronizer: Boolean, ): MultiDomainAcsStore.ContractFilter[ DsoAcsStoreRowData, AcsInterfaceViewRowData.NoInterfacesIngested, @@ -1672,22 +1667,18 @@ object SvDsoStore { contractExpiresAt = Some(Timestamp.assertFromInstant(contract.payload.expiresAt)), ) }, + mkFilter(vl.ValidatorLicenseRequest.COMPANION)(req => req.payload.dso == dso) { contract => + DsoAcsStoreRowData( + contract, + contractExpiresAt = Some(Timestamp.assertFromInstant(contract.payload.expiresAt)), + validator = Some(PartyId.tryFromProtoPrimitive(contract.payload.validator)), + ) + }, ) - val finalFilters = if (enablePermissionedSynchronizer) { - dsoFilters + mkFilter(vl.ValidatorLicenseRequest.COMPANION)(req => req.payload.dso == dso) { - contract => - DsoAcsStoreRowData( - contract, - contractExpiresAt = Some(Timestamp.assertFromInstant(contract.payload.expiresAt)), - validator = Some(PartyId.tryFromProtoPrimitive(contract.payload.validator)), - ) - } - } else dsoFilters - MultiDomainAcsStore.SimpleContractFilter( dsoParty, - finalFilters, + dsoFilters, ) } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvSvStore.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvSvStore.scala index c7036fd2ba..94bdeaf9f9 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvSvStore.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvSvStore.scala @@ -24,14 +24,12 @@ import com.digitalasset.canton.tracing.TraceContext import org.lfdecentralizedtrust.splice.codegen.java.splice.wallet.install.WalletAppInstall import org.lfdecentralizedtrust.splice.config.IngestionConfig import org.lfdecentralizedtrust.splice.store.db.AcsInterfaceViewRowData -import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig import scala.concurrent.{ExecutionContext, Future} /* Store used by the SV app for filtering contracts visible to the SV party. */ trait SvSvStore extends AppStore { - def config: Option[SvAppBackendConfig] protected val outerLoggerFactory: NamedLoggerFactory override protected lazy val loggerFactory: NamedLoggerFactory = @@ -41,7 +39,7 @@ trait SvSvStore extends AppStore { : org.lfdecentralizedtrust.splice.store.MultiDomainAcsStore.ContractFilter[ org.lfdecentralizedtrust.splice.sv.store.db.SvTables.SvAcsStoreRowData, AcsInterfaceViewRowData.NoInterfacesIngested, - ] = SvSvStore.contractFilter(key, config.map(_.permissionedSynchronizer).getOrElse(false)) + ] = SvSvStore.contractFilter(key) def lookupValidatorOnboardingBySecretWithOffset( secret: String @@ -119,7 +117,6 @@ object SvSvStore { ingestionConfig: IngestionConfig, defaultLimit: Limit, acsStoreDescriptorUserVersion: Option[Long] = None, - svBackendconfig: Option[SvAppBackendConfig] = None, )(implicit ec: ExecutionContext, templateJsonDecoder: TemplateJsonDecoder, @@ -135,13 +132,11 @@ object SvSvStore { ingestionConfig, acsStoreDescriptorUserVersion, defaultLimit = defaultLimit, - config = svBackendconfig, ) /** Contract filter of an sv acs store for a specific acs party. */ def contractFilter( - key: SvStore.Key, - enablePermissionedSynchronizer: Boolean, + key: SvStore.Key ): MultiDomainAcsStore.ContractFilter[ SvAcsStoreRowData, AcsInterfaceViewRowData.NoInterfacesIngested, @@ -170,22 +165,16 @@ object SvSvStore { svCandidateName = Some(contract.payload.svName), ) }, + mkFilter(WalletAppInstall.COMPANION)(co => co.payload.endUserParty == sv) { contract => + SvAcsStoreRowData( + contract + ) + }, ) - val finalFilters = if (enablePermissionedSynchronizer) { - svFilters + mkFilter(WalletAppInstall.COMPANION)(co => co.payload.endUserParty == sv) { - contract => - SvAcsStoreRowData( - contract - ) - } - } else { - svFilters - } - MultiDomainAcsStore.SimpleContractFilter( key.svParty, - finalFilters, + svFilters, ) } } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/db/DbSvDsoStore.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/db/DbSvDsoStore.scala index 7ad4dd8b20..b9d0bffed8 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/db/DbSvDsoStore.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/db/DbSvDsoStore.scala @@ -84,7 +84,6 @@ class DbSvDsoStore( ingestionConfig: IngestionConfig, acsStoreDescriptorUserVersion: Option[Long] = None, override val defaultLimit: Limit, - override val config: Option[org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig], )(implicit override protected val ec: ExecutionContext, override protected val templateJsonDecoder: TemplateJsonDecoder, diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/db/DbSvSvStore.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/db/DbSvSvStore.scala index c29afc88b1..33892810f1 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/db/DbSvSvStore.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/db/DbSvSvStore.scala @@ -21,7 +21,6 @@ import com.digitalasset.canton.topology.{ParticipantId, PartyId} import com.digitalasset.canton.tracing.TraceContext import org.lfdecentralizedtrust.splice.config.IngestionConfig import org.lfdecentralizedtrust.splice.store.db.AcsQueries.AcsStoreId -import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig import slick.jdbc.canton.ActionBasedSQLInterpolation.Implicits.actionBasedSQLInterpolationCanton import scala.concurrent.{ExecutionContext, Future} @@ -36,7 +35,6 @@ class DbSvSvStore( ingestionConfig: IngestionConfig, acsStoreDescriptorUserVersion: Option[Long] = None, override val defaultLimit: Limit, - override val config: Option[SvAppBackendConfig] = None, )(implicit override protected val ec: ExecutionContext, templateJsonDecoder: TemplateJsonDecoder, diff --git a/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SvDsoStoreTest.scala b/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SvDsoStoreTest.scala index 593c47839a..518289caa2 100644 --- a/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SvDsoStoreTest.scala +++ b/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SvDsoStoreTest.scala @@ -2349,7 +2349,6 @@ class DbSvDsoStoreTest participantId = mkParticipantId("SvDsoStoreTest"), IngestionConfig(), defaultLimit = HardLimit.tryCreate(Limit.DefaultMaxPageSize), - config = None, )(parallelExecutionContext, implicitly, implicitly) for { _ <- store.multiDomainAcsStore.testIngestionSink.initialize() diff --git a/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SvSvStoreTest.scala b/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SvSvStoreTest.scala index 45c2823ceb..d4162be43a 100644 --- a/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SvSvStoreTest.scala +++ b/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SvSvStoreTest.scala @@ -254,7 +254,7 @@ class DbSvSvStoreTest defaultLimit = HardLimit.tryCreate(Limit.DefaultMaxPageSize), )(parallelExecutionContext, implicitly, implicitly) { override lazy val acsContractFilter = - SvSvStore.contractFilter(key, enablePermissionedSynchronizer = true) + SvSvStore.contractFilter(key) } for { _ <- store.multiDomainAcsStore.testIngestionSink.initialize() From 41d3dcbda68ac9eb1b5610296eeb937255fce359 Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Tue, 18 Aug 2026 14:46:57 +0000 Subject: [PATCH 10/11] Fixing CI failure [ci] Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- .../scala/org/lfdecentralizedtrust/splice/sv/SvApp.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/SvApp.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/SvApp.scala index 8946730723..e90c91e4bc 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/SvApp.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/SvApp.scala @@ -117,8 +117,11 @@ class SvApp( metrics, ) { - override def packagesForJsonDecoding: Seq[DarResource] = - super.packagesForJsonDecoding ++ DarResources.dsoGovernance.all ++ DarResources.validatorLifecycle.all ++ DarResources.amuletNameService.all ++ DarResources.wallet.all + override def packagesForJsonDecoding: Seq[DarResource] = { + val base = + super.packagesForJsonDecoding ++ DarResources.dsoGovernance.all ++ DarResources.validatorLifecycle.all ++ DarResources.amuletNameService.all + if (config.permissionedSynchronizer) base ++ DarResources.wallet.all else base + } override def preInitializeBeforeLedgerConnection()(implicit tc: TraceContext): Future[Unit] = { val participantAdminConnection = new ParticipantAdminConnection( From 2b534b3c78e50d8719a815f1f2a7d63914ccbaeb Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Tue, 18 Aug 2026 15:37:56 +0000 Subject: [PATCH 11/11] Fixed bug by using packageversionSupport [ci] Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- .../lfdecentralizedtrust/splice/sv/store/SvDsoStore.scala | 7 ++++++- .../lfdecentralizedtrust/splice/sv/store/SvSvStore.scala | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvDsoStore.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvDsoStore.scala index 80e9c12681..821f631f43 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvDsoStore.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvDsoStore.scala @@ -1667,7 +1667,12 @@ object SvDsoStore { contractExpiresAt = Some(Timestamp.assertFromInstant(contract.payload.expiresAt)), ) }, - mkFilter(vl.ValidatorLicenseRequest.COMPANION)(req => req.payload.dso == dso) { contract => + mkFilter(vl.ValidatorLicenseRequest.COMPANION)( + req => req.payload.dso == dso, + versionGuard = { case (pkgVersionSupport, now) => + (tc) => pkgVersionSupport.supportsPermissionedSynchronizer(Seq(dsoParty), now)(tc) + }, + ) { contract => DsoAcsStoreRowData( contract, contractExpiresAt = Some(Timestamp.assertFromInstant(contract.payload.expiresAt)), diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvSvStore.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvSvStore.scala index 94bdeaf9f9..c5966f7366 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvSvStore.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvSvStore.scala @@ -165,7 +165,12 @@ object SvSvStore { svCandidateName = Some(contract.payload.svName), ) }, - mkFilter(WalletAppInstall.COMPANION)(co => co.payload.endUserParty == sv) { contract => + mkFilter(WalletAppInstall.COMPANION)( + co => co.payload.endUserParty == sv, + versionGuard = { case (pkgVersionSupport, now) => + (tc) => pkgVersionSupport.supportsPermissionedSynchronizer(Seq(key.dsoParty), now)(tc) + }, + ) { contract => SvAcsStoreRowData( contract )