From 13c2c614d4d61c2a4791707a1031b9ba20f35a01 Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Tue, 25 Aug 2026 12:10:29 +0000 Subject: [PATCH 1/7] Public Sequencer: Add automation to revoke ParticipantSynchronizerPermission based on ValidatorUnpermision. Extend integration test to to check both temporary revocation and permanent revocation [ci] Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- ...missionedSynchronizerIntegrationTest.scala | 81 +++++++++++++++ .../environment/TopologyAdminConnection.scala | 34 +++++-- .../automation/SvDsoAutomationService.scala | 8 ++ .../ValidatorUnpermissionTrigger.scala | 99 +++++++++++++++++++ .../ExecuteConfirmedActionTrigger.scala | 10 ++ 5 files changed, 224 insertions(+), 8 deletions(-) create mode 100644 apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/ValidatorUnpermissionTrigger.scala 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 2f0a5abc4c..9bec77e434 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 @@ -1,12 +1,20 @@ package org.lfdecentralizedtrust.splice.integration.tests import com.digitalasset.canton.HasExecutionContext +import org.lfdecentralizedtrust.splice.codegen.java.splice.dsorules.DsoRules_UnpermissionValidator +import org.lfdecentralizedtrust.splice.codegen.java.splice.dsorules.actionrequiringconfirmation.ARC_DsoRules +import org.lfdecentralizedtrust.splice.codegen.java.splice.dsorules.dsorules_actionrequiringconfirmation.SRARC_UnpermissionValidator import org.lfdecentralizedtrust.splice.config.ConfigTransforms import org.lfdecentralizedtrust.splice.integration.EnvironmentDefinition import org.lfdecentralizedtrust.splice.integration.tests.SpliceTests.IntegrationTest import org.lfdecentralizedtrust.splice.scan.admin.api.client.commands.HttpScanAppClient.SynchronizerPermissionState import org.lfdecentralizedtrust.splice.util.* +import java.time.Instant +import scala.concurrent.duration.* +import java.util.Optional +import com.digitalasset.canton.data.CantonTimestamp + class PermissionedSynchronizerIntegrationTest extends IntegrationTest with HasExecutionContext @@ -120,5 +128,78 @@ class PermissionedSynchronizerIntegrationTest bobValidatorBackend.startSync() bobValidatorBackend.onboardUser("TestUserBob") } + + val bobParticipantId = bobValidatorBackend.participantClient.id.toProtoPrimitive + val suspendTime = env.environment.clock.now.plus(java.time.Duration.ofHours(1)).toInstant + + clue("SVs vote to temporarily suspend Bob") { + manuallyUnpermissionValidator(bobParticipantId, Some(suspendTime), revoked = false) + } + + clue("Verify Bob's ParticipantSynchronizerPermission is updated with loginAfter") { + eventually() { + sv1ScanBackend.getParticipantSynchronizerPermission( + decentralizedSynchronizerId.toProtoPrimitive, + bobParticipantId, + ) shouldBe Some( + SynchronizerPermissionState(Some(CantonTimestamp.assertFromInstant(suspendTime))) + ) + } + } + + clue("SVs vote to permanently revoke Bob") { + manuallyUnpermissionValidator(bobParticipantId, None, revoked = true) + } + + clue("Verify Bob's ParticipantSynchronizerPermission is completely removed") { + eventually() { + sv1ScanBackend.getParticipantSynchronizerPermission( + decentralizedSynchronizerId.toProtoPrimitive, + bobParticipantId, + ) shouldBe None + } + } + def manuallyUnpermissionValidator( + participantId: String, + loginAfter: Option[Instant], + revoked: Boolean, + ): Unit = { + val dsoParty = sv1Backend.getDsoInfo().dsoParty + + Seq(sv1Backend, sv2Backend, sv3Backend).foreach { sv => + eventuallySucceeds(timeUntilSuccess = 40.seconds, maxPollInterval = 1.second) { + val svParty = sv.getDsoInfo().svParty + val dsoRules = sv.appState.dsoStore.getDsoRules().futureValue + + clue(s"${sv.participantClient.name} votes for UnpermissionValidator(revoked=$revoked)") { + sv.appState.svAutomation + .connection( + org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority.High + ) + .submit( + actAs = Seq(svParty), + readAs = Seq(dsoParty), + update = dsoRules.contractId.exerciseDsoRules_ConfirmAction( + svParty.toProtoPrimitive, + new ARC_DsoRules( + new SRARC_UnpermissionValidator( + new DsoRules_UnpermissionValidator( + participantId, + loginAfter.map(Optional.of(_)).getOrElse(Optional.empty()), + java.lang.Boolean.valueOf(revoked), + ) + ) + ), + ), + ) + .withSynchronizerId(decentralizedSynchronizerId) + .noDedup + .yieldUnit() + .futureValue + } + } + } + } + } } diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/TopologyAdminConnection.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/TopologyAdminConnection.scala index 6786e5ef9a..4d9ffa34c0 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/TopologyAdminConnection.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/TopologyAdminConnection.scala @@ -198,10 +198,19 @@ abstract class TopologyAdminConnection( participantId: ParticipantId, permission: ParticipantPermission, retryFor: RetryFor, + limits: Option[ParticipantSynchronizerLimits] = None, + loginAfter: Option[CantonTimestamp] = None, )(implicit tc: TraceContext, ec: ExecutionContext, ): Future[TopologyResult[ParticipantSynchronizerPermission]] = { + val expectedMapping = ParticipantSynchronizerPermission( + synchronizerId = synchronizerId, + participantId = participantId, + permission = permission, + limits = limits, + loginAfter = loginAfter, + ) ensureTopologyMappingO( TopologyStoreId.Synchronizer(synchronizerId), s"ParticipantSynchronizerPermission with $permission for $participantId", @@ -216,7 +225,7 @@ abstract class TopologyAdminConnection( ) .subflatMap { results => results.headOption match { - case Some(result) if result.mapping.permission == permission => + case Some(result) if result.mapping == expectedMapping => Right(result) case other => Left(other) @@ -224,13 +233,7 @@ abstract class TopologyAdminConnection( }, update = { _ => Right( - ParticipantSynchronizerPermission( - synchronizerId = synchronizerId, - participantId = participantId, - permission = permission, - limits = None, - loginAfter = None, - ) + expectedMapping ) }, isProposal = true, @@ -238,6 +241,21 @@ abstract class TopologyAdminConnection( ) } + def ensureParticipantSynchronizerPermissionRemoved( + synchronizerId: SynchronizerId, + participantId: ParticipantId, + )(implicit tc: TraceContext, ec: ExecutionContext): Future[Unit] = { + ensureTopologyMappingRemoved( + s"Remove ParticipantSynchronizerPermission for $participantId on $synchronizerId", + synchronizerId, + listParticipantSynchronizerPermission( + synchronizerId, + participantId.filterString, + ).map(_.headOption), + proposal = true, + ) + } + def listPartyToParticipant( store: Option[TopologyStoreId] = None, // list only active (non-removed) mappings by default; this matches the Canton console defaults diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/SvDsoAutomationService.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/SvDsoAutomationService.scala index 4dfa730574..a0782755ee 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/SvDsoAutomationService.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/SvDsoAutomationService.scala @@ -297,6 +297,13 @@ class SvDsoAutomationService( connection(SpliceLedgerConnectionPriority.High), ) ) + registerTrigger( + new ValidatorUnpermissionTrigger( + triggerContext, + dsoStore, + participantAdminConnection, + ) + ) } registerTrigger( new SvOnboardingRequestTrigger( @@ -792,5 +799,6 @@ object SvDsoAutomationService extends AutomationServiceCompanion { aTrigger[ReconcileSequencingParametersTrigger], aTrigger[GrantValidatorPermissionTrigger], aTrigger[ValidatorLicenseRequestTrigger], + aTrigger[ValidatorUnpermissionTrigger], ) } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/ValidatorUnpermissionTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/ValidatorUnpermissionTrigger.scala new file mode 100644 index 0000000000..c88fcd64e2 --- /dev/null +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/ValidatorUnpermissionTrigger.scala @@ -0,0 +1,99 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package org.lfdecentralizedtrust.splice.sv.automation + +import com.digitalasset.canton.data.CantonTimestamp +import com.digitalasset.canton.topology.{ParticipantId, SynchronizerId} +import com.digitalasset.canton.topology.transaction.ParticipantPermission.Submission +import com.digitalasset.canton.tracing.TraceContext +import io.opentelemetry.api.trace.Tracer +import org.apache.pekko.stream.Materializer +import org.lfdecentralizedtrust.splice.automation.{ + OnAssignedContractTrigger, + TaskOutcome, + TaskSuccess, + TriggerContext, +} +import org.lfdecentralizedtrust.splice.codegen.java.splice.validatorunpermission.ValidatorUnpermission +import org.lfdecentralizedtrust.splice.environment.{ParticipantAdminConnection, RetryFor} +import org.lfdecentralizedtrust.splice.sv.store.SvDsoStore +import org.lfdecentralizedtrust.splice.util.AssignedContract + +import scala.concurrent.{ExecutionContext, Future} +import scala.jdk.OptionConverters.* + +class ValidatorUnpermissionTrigger( + override protected val context: TriggerContext, + store: SvDsoStore, + participantAdminConnection: ParticipantAdminConnection, +)(implicit + override val ec: ExecutionContext, + mat: Materializer, + tracer: Tracer, +) extends OnAssignedContractTrigger.Template[ + ValidatorUnpermission.ContractId, + ValidatorUnpermission, + ]( + store, + ValidatorUnpermission.COMPANION, + ) { + + override protected def completeTask( + unpermission: AssignedContract[ValidatorUnpermission.ContractId, ValidatorUnpermission] + )(implicit tc: TraceContext): Future[TaskOutcome] = { + val payload = unpermission.payload + + ParticipantId + .fromProtoPrimitive(payload.participantId, "participantId") + .fold( + err => + Future.successful( + TaskSuccess(s"Skipping ValidatorUnpermission with invalid participantId: $err") + ), + participantId => { + for { + dsoRules <- store.getDsoRules() + synchronizerId = SynchronizerId.tryFromString( + dsoRules.payload.config.decentralizedSynchronizer.activeSynchronizerId + ) + + outcome <- + if (payload.revoked) { + participantAdminConnection + .ensureParticipantSynchronizerPermissionRemoved( + synchronizerId, + participantId, + ) + .map { _ => + TaskSuccess( + s"Permanently revoked ParticipantSynchronizerPermission for participant $participantId" + ) + } + } else { + for { + existingMappings <- participantAdminConnection + .listParticipantSynchronizerPermission( + synchronizerId, + participantId.filterString, + ) + + _ <- participantAdminConnection.ensureParticipantSynchronizerPermission( + synchronizerId = synchronizerId, + participantId = participantId, + permission = Submission, + retryFor = RetryFor.Automation, + limits = existingMappings.headOption.flatMap(_.mapping.limits), + loginAfter = payload.loginAfter.toScala + .map(t => CantonTimestamp.assertFromInstant(t)), + ) + } yield TaskSuccess( + s"Temporarily revoked ParticipantSynchronizerPermission for participant $participantId (loginAfter: ${payload.loginAfter.toScala + .map(t => CantonTimestamp.assertFromInstant(t))})" + ) + } + } yield outcome + }, + ) + } +} diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExecuteConfirmedActionTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExecuteConfirmedActionTrigger.scala index caaa5d4f2b..b60261c5bb 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExecuteConfirmedActionTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExecuteConfirmedActionTrigger.scala @@ -272,6 +272,16 @@ class ExecuteConfirmedActionTrigger( rejectAction.dsoRules_RejectValidatorLicenseValue.validatorLicenseRequestCid ) .map(_.isEmpty) + case unpermissionAction: SRARC_UnpermissionValidator if config.permissionedSynchronizer => + val participantId = unpermissionAction.dsoRules_UnpermissionValidatorValue.participantId + val revoked = unpermissionAction.dsoRules_UnpermissionValidatorValue.revoked + val loginAfterOpt = unpermissionAction.dsoRules_UnpermissionValidatorValue.loginAfter + store.listValidatorUnpermissions(participantId).map { contracts => + contracts.exists(co => + co.payload.revoked == revoked && + co.payload.loginAfter == loginAfterOpt + ) + } case action => throw new UnsupportedOperationException( show"DsoRules $action is not yet supported" From 7fa4f18c0a565f5a92eb929a7a6b88c488779139 Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Tue, 25 Aug 2026 14:12:08 +0000 Subject: [PATCH 2/7] Fixed AssignedContractTrigger 2026 Aug 25 16.12 [ci] Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- .../ExecuteConfirmedActionTrigger.scala | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExecuteConfirmedActionTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExecuteConfirmedActionTrigger.scala index b60261c5bb..781cf18af4 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExecuteConfirmedActionTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExecuteConfirmedActionTrigger.scala @@ -11,6 +11,7 @@ import org.lfdecentralizedtrust.splice.automation.{ TaskSuccess, TriggerContext, } +import scala.jdk.OptionConverters.* import org.lfdecentralizedtrust.splice.codegen.java.splice.round.{ ClosedMiningRound, OpenMiningRound, @@ -273,14 +274,30 @@ class ExecuteConfirmedActionTrigger( ) .map(_.isEmpty) case unpermissionAction: SRARC_UnpermissionValidator if config.permissionedSynchronizer => - val participantId = unpermissionAction.dsoRules_UnpermissionValidatorValue.participantId - val revoked = unpermissionAction.dsoRules_UnpermissionValidatorValue.revoked - val loginAfterOpt = unpermissionAction.dsoRules_UnpermissionValidatorValue.loginAfter - store.listValidatorUnpermissions(participantId).map { contracts => - contracts.exists(co => - co.payload.revoked == revoked && - co.payload.loginAfter == loginAfterOpt - ) + // MergeUnpermissionValidatorContractsTrigger merges the new contract with any previous contract, + // hence we have to consider the merged contract state when calculating isStaleAction + val payload = unpermissionAction.dsoRules_UnpermissionValidatorValue + val proposedIsRevoked = payload.revoked + val proposedLoginAfter = payload.loginAfter.toScala + + store.listValidatorUnpermissions(payload.participantId).map { contracts => + contracts.exists { co => + val existingIsRevoked = co.payload.revoked + val existingLoginAfter = co.payload.loginAfter.toScala + + if (proposedIsRevoked) { + existingIsRevoked + } else if (existingIsRevoked) { + true + } else { + (existingLoginAfter, proposedLoginAfter) match { + case (Some(existing), Some(proposed)) => existing.compareTo(proposed) >= 0 + case (Some(_), None) => true + case (None, None) => true + case (None, Some(_)) => false + } + } + } } case action => throw new UnsupportedOperationException( From 3cf46a559abfcb98c952a516c8b8af06fb7a4a5f Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Wed, 26 Aug 2026 11:57:05 +0000 Subject: [PATCH 3/7] Addressed comments [ci] Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- ...missionedSynchronizerIntegrationTest.scala | 69 ++++++++++--------- .../ExecuteConfirmedActionTrigger.scala | 27 -------- 2 files changed, 36 insertions(+), 60 deletions(-) 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 9bec77e434..576dd8bdc5 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 @@ -11,7 +11,6 @@ import org.lfdecentralizedtrust.splice.scan.admin.api.client.commands.HttpScanAp import org.lfdecentralizedtrust.splice.util.* import java.time.Instant -import scala.concurrent.duration.* import java.util.Optional import com.digitalasset.canton.data.CantonTimestamp @@ -164,38 +163,42 @@ class PermissionedSynchronizerIntegrationTest loginAfter: Option[Instant], revoked: Boolean, ): Unit = { - val dsoParty = sv1Backend.getDsoInfo().dsoParty - - Seq(sv1Backend, sv2Backend, sv3Backend).foreach { sv => - eventuallySucceeds(timeUntilSuccess = 40.seconds, maxPollInterval = 1.second) { - val svParty = sv.getDsoInfo().svParty - val dsoRules = sv.appState.dsoStore.getDsoRules().futureValue - - clue(s"${sv.participantClient.name} votes for UnpermissionValidator(revoked=$revoked)") { - sv.appState.svAutomation - .connection( - org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority.High - ) - .submit( - actAs = Seq(svParty), - readAs = Seq(dsoParty), - update = dsoRules.contractId.exerciseDsoRules_ConfirmAction( - svParty.toProtoPrimitive, - new ARC_DsoRules( - new SRARC_UnpermissionValidator( - new DsoRules_UnpermissionValidator( - participantId, - loginAfter.map(Optional.of(_)).getOrElse(Optional.empty()), - java.lang.Boolean.valueOf(revoked), - ) - ) - ), - ), - ) - .withSynchronizerId(decentralizedSynchronizerId) - .noDedup - .yieldUnit() - .futureValue + val action = new ARC_DsoRules( + new SRARC_UnpermissionValidator( + new DsoRules_UnpermissionValidator( + participantId, + loginAfter.map(Optional.of(_)).getOrElse(Optional.empty()), + java.lang.Boolean.valueOf(revoked), + ) + ) + ) + + val (_, voteRequest) = actAndCheck( + s"SV1 creates vote request to unpermission $participantId (revoked=$revoked)", + eventuallySucceeds() { + sv1Backend.createVoteRequest( + sv1Backend.getDsoInfo().svParty.toProtoPrimitive, + action, + "url", + "description", + sv1Backend.getDsoInfo().dsoRules.payload.config.voteRequestTimeout, + None, + ) + }, + )( + "vote request has been created", + _ => sv1Backend.listVoteRequests().filter(_.payload.action == action).head, + ) + + Seq(sv2Backend, sv3Backend, sv4Backend).foreach { sv => + clue(s"${sv.participantClient.name} accepts the vote request") { + eventuallySucceeds() { + sv.castVote( + voteRequest.contractId, + isAccepted = true, + "url", + "description", + ) } } } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExecuteConfirmedActionTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExecuteConfirmedActionTrigger.scala index 781cf18af4..caaa5d4f2b 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExecuteConfirmedActionTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExecuteConfirmedActionTrigger.scala @@ -11,7 +11,6 @@ import org.lfdecentralizedtrust.splice.automation.{ TaskSuccess, TriggerContext, } -import scala.jdk.OptionConverters.* import org.lfdecentralizedtrust.splice.codegen.java.splice.round.{ ClosedMiningRound, OpenMiningRound, @@ -273,32 +272,6 @@ class ExecuteConfirmedActionTrigger( rejectAction.dsoRules_RejectValidatorLicenseValue.validatorLicenseRequestCid ) .map(_.isEmpty) - case unpermissionAction: SRARC_UnpermissionValidator if config.permissionedSynchronizer => - // MergeUnpermissionValidatorContractsTrigger merges the new contract with any previous contract, - // hence we have to consider the merged contract state when calculating isStaleAction - val payload = unpermissionAction.dsoRules_UnpermissionValidatorValue - val proposedIsRevoked = payload.revoked - val proposedLoginAfter = payload.loginAfter.toScala - - store.listValidatorUnpermissions(payload.participantId).map { contracts => - contracts.exists { co => - val existingIsRevoked = co.payload.revoked - val existingLoginAfter = co.payload.loginAfter.toScala - - if (proposedIsRevoked) { - existingIsRevoked - } else if (existingIsRevoked) { - true - } else { - (existingLoginAfter, proposedLoginAfter) match { - case (Some(existing), Some(proposed)) => existing.compareTo(proposed) >= 0 - case (Some(_), None) => true - case (None, None) => true - case (None, Some(_)) => false - } - } - } - } case action => throw new UnsupportedOperationException( show"DsoRules $action is not yet supported" From fa6773aaf17e9c51445081cdbd6d7bfeb83ae94f Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Wed, 26 Aug 2026 12:47:15 +0000 Subject: [PATCH 4/7] Logger supress [ci] Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- ...missionedSynchronizerIntegrationTest.scala | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) 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 576dd8bdc5..e76f05347b 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 @@ -13,6 +13,7 @@ import org.lfdecentralizedtrust.splice.util.* import java.time.Instant import java.util.Optional import com.digitalasset.canton.data.CantonTimestamp +import com.digitalasset.canton.logging.SuppressionRule class PermissionedSynchronizerIntegrationTest extends IntegrationTest @@ -131,33 +132,40 @@ class PermissionedSynchronizerIntegrationTest val bobParticipantId = bobValidatorBackend.participantClient.id.toProtoPrimitive val suspendTime = env.environment.clock.now.plus(java.time.Duration.ofHours(1)).toInstant - clue("SVs vote to temporarily suspend Bob") { - manuallyUnpermissionValidator(bobParticipantId, Some(suspendTime), revoked = false) - } - - clue("Verify Bob's ParticipantSynchronizerPermission is updated with loginAfter") { - eventually() { - sv1ScanBackend.getParticipantSynchronizerPermission( - decentralizedSynchronizerId.toProtoPrimitive, - bobParticipantId, - ) shouldBe Some( - SynchronizerPermissionState(Some(CantonTimestamp.assertFromInstant(suspendTime))) - ) + loggerFactory.suppress( + SuppressionRule.Level( + org.slf4j.event.Level.WARN + ) + ) { + clue("SVs vote to temporarily suspend Bob") { + manuallyUnpermissionValidator(bobParticipantId, Some(suspendTime), revoked = false) } - } - clue("SVs vote to permanently revoke Bob") { - manuallyUnpermissionValidator(bobParticipantId, None, revoked = true) - } + clue("Verify Bob's ParticipantSynchronizerPermission is updated with loginAfter") { + eventually() { + sv1ScanBackend.getParticipantSynchronizerPermission( + decentralizedSynchronizerId.toProtoPrimitive, + bobParticipantId, + ) shouldBe Some( + SynchronizerPermissionState(Some(CantonTimestamp.assertFromInstant(suspendTime))) + ) + } + } + clue("SVs vote to permanently revoke Bob") { + manuallyUnpermissionValidator(bobParticipantId, None, revoked = true) + } - clue("Verify Bob's ParticipantSynchronizerPermission is completely removed") { - eventually() { - sv1ScanBackend.getParticipantSynchronizerPermission( - decentralizedSynchronizerId.toProtoPrimitive, - bobParticipantId, - ) shouldBe None + clue("Verify Bob's ParticipantSynchronizerPermission is completely removed") { + eventually() { + sv1ScanBackend.getParticipantSynchronizerPermission( + decentralizedSynchronizerId.toProtoPrimitive, + bobParticipantId, + ) shouldBe None + } } + } + def manuallyUnpermissionValidator( participantId: String, loginAfter: Option[Instant], From a689c8c62c8f97af97c2d856a15fb7e700ee2940 Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Wed, 26 Aug 2026 22:20:29 +0000 Subject: [PATCH 5/7] stop bob [ci] Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- .../tests/PermissionedSynchronizerIntegrationTest.scala | 2 ++ 1 file changed, 2 insertions(+) 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 e76f05347b..becafdbb41 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 @@ -164,6 +164,8 @@ class PermissionedSynchronizerIntegrationTest } } + bobValidatorBackend.stop() // to avoid logs in canton_before_shutdown.clog + } def manuallyUnpermissionValidator( From 1fb36680b40e0facc63859859ca7231642464b9f Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Thu, 27 Aug 2026 08:33:03 +0000 Subject: [PATCH 6/7] Addressed Feedback [ci] Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- .../tests/PermissionedSynchronizerIntegrationTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 becafdbb41..61efa2a954 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 @@ -200,7 +200,7 @@ class PermissionedSynchronizerIntegrationTest _ => sv1Backend.listVoteRequests().filter(_.payload.action == action).head, ) - Seq(sv2Backend, sv3Backend, sv4Backend).foreach { sv => + Seq(sv2Backend, sv3Backend).foreach { sv => clue(s"${sv.participantClient.name} accepts the vote request") { eventuallySucceeds() { sv.castVote( From 6267f4560d6750c4a3ddcc0dce463978b77780dc Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Thu, 27 Aug 2026 09:06:03 +0000 Subject: [PATCH 7/7] Trigger CI [static] Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- .../tests/PermissionedSynchronizerIntegrationTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 61efa2a954..42efc5a994 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 @@ -134,7 +134,7 @@ class PermissionedSynchronizerIntegrationTest loggerFactory.suppress( SuppressionRule.Level( - org.slf4j.event.Level.WARN + org.slf4j.event.Level.WARN // because unpermissioning Bob leads to many warnings from sequencer ) ) { clue("SVs vote to temporarily suspend Bob") {