From 724a0c1b1a996474ffb85b802f126c5f1add9bf2 Mon Sep 17 00:00:00 2001 From: pbatko-da Date: Mon, 16 May 2022 15:27:03 +0200 Subject: [PATCH] Benchtool: Non-stakeholder informees generation [DPP-978] (#13808) We obtain non-empty non-stakeholder informees by performing an immediate divulgence of instances of Foo1, Foo2 or Foo3 templates through instances of a helper Divulger template. The divulgence is controlled by 1) configuring the number of all divulgees to generated and 2) probabilistically picking a non-empty subset from all divulgees for each contract (similar to how observers are picked for each contract with certain probabilities). If the divulgees for a contract are non-empty a Foo1, Foo2 or Foo3 contract is created via a helper choice on a Divulger contract. Otherwise a standard create Foo1, Foo2 or Foo3 command is issued. Each Divulger contract can divulge any number of Foo1, Foo2 or Foo3 contracts but it's limited to divulging to a fixed set of divulgees. In other words, there is a separate Divulger contract for each non-empty subset of all divulgees. We expect the set of all divulgees to be very small in practice similar to how only a small number observers (currently at most 3) is currently configured for existing benchmarks. Thus, the set of all subsets is also small. To be conservative, we allow the number of divulgees to be at most 5. changelog_begin changelog_end --- .../ledger/api/benchtool/ConfigEnricher.scala | 2 +- .../api/benchtool/LedgerApiBenchTool.scala | 15 +- .../api/benchtool/config/WorkflowConfig.scala | 3 + .../config/WorkflowConfigParser.scala | 3 +- .../submission/CommandSubmitter.scala | 34 +++- .../submission/FooCommandGenerator.scala | 82 +++++++- .../FooDivulgerCommandGenerator.scala | 67 +++++++ .../benchtool/submission/FooSubmission.scala | 40 +++- .../submission/FooTemplateDescriptor.scala | 3 + .../api/benchtool/submission/Names.scala | 7 + .../config/WorkflowConfigParserSpec.scala | 4 + .../FibonacciCommandSubmitterITSpec.scala | 5 +- .../FooCommandSubmitterITSpec.scala | 23 ++- .../NonStakeholderInformeesITSpec.scala | 177 ++++++++++++++++++ .../test-common/src/main/daml/model/Foo.daml | 44 +++++ 15 files changed, 463 insertions(+), 46 deletions(-) create mode 100644 ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/FooDivulgerCommandGenerator.scala create mode 100644 ledger/ledger-api-bench-tool/src/test/suite/scala/com/daml/ledger/api/benchtool/submission/NonStakeholderInformeesITSpec.scala diff --git a/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/ConfigEnricher.scala b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/ConfigEnricher.scala index 468921674d5..4213c6c3e9a 100644 --- a/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/ConfigEnricher.scala +++ b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/ConfigEnricher.scala @@ -33,7 +33,7 @@ object ConfigEnricher { submissionResult match { case None => party case Some(summary) => - summary.observers + summary.allocatedParties .map(_.unwrap) .find(_.contains(party)) .getOrElse(throw new RuntimeException(s"Observer not found: $party")) diff --git a/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/LedgerApiBenchTool.scala b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/LedgerApiBenchTool.scala index f84d5f675e1..4d95cfad2e2 100644 --- a/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/LedgerApiBenchTool.scala +++ b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/LedgerApiBenchTool.scala @@ -83,7 +83,10 @@ object LedgerApiBenchTool { case class SubmissionStepResult( signatory: Primitive.Party, observers: List[Primitive.Party], -) + divulgees: List[Primitive.Party], +) { + val allocatedParties: List[Primitive.Party] = List(signatory) ++ observers ++ divulgees +} class LedgerApiBenchTool( names: Names, @@ -229,6 +232,8 @@ class LedgerApiBenchTool( signatory = signatory, config = submissionConfig, allObservers = List.empty, + allDivulgees = List.empty, + divulgeesToDivulgerKeyMap = Map.empty, ) for { metricsManager <- MetricsManager( @@ -248,7 +253,7 @@ class LedgerApiBenchTool( .generateAndSubmit( generator = generator, config = submissionConfig, - signatory = signatory, + actAs = List(signatory), maxInFlightCommands = config.maxInFlightCommands, submissionBatchSize = config.submissionBatchSize, ) @@ -290,7 +295,7 @@ class LedgerApiBenchTool( metricsManager = NoOpMetricsManager(), ) for { - (signatory, allObservers) <- submitter.prepare( + (signatory, allObservers, allDivulgees) <- submitter.prepare( submissionConfig ) _ <- @@ -303,6 +308,7 @@ class LedgerApiBenchTool( submissionConfig = submissionConfig, signatory = signatory, allObservers = allObservers, + allDivulgees = allDivulgees, ).performSubmission() case submissionConfig: FibonacciSubmissionConfig => val generator: CommandGenerator = new FibonacciCommandGenerator( @@ -314,7 +320,7 @@ class LedgerApiBenchTool( .generateAndSubmit( generator = generator, config = submissionConfig, - signatory = signatory, + actAs = List(signatory) ++ allDivulgees, maxInFlightCommands = config.maxInFlightCommands, submissionBatchSize = config.submissionBatchSize, ) @@ -323,6 +329,7 @@ class LedgerApiBenchTool( } yield SubmissionStepResult( signatory = signatory, observers = allObservers, + divulgees = allDivulgees, ) } diff --git a/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/config/WorkflowConfig.scala b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/config/WorkflowConfig.scala index ee264ba0699..973d0f2e86a 100644 --- a/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/config/WorkflowConfig.scala +++ b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/config/WorkflowConfig.scala @@ -19,6 +19,7 @@ object WorkflowConfig { sealed trait SubmissionConfig extends Product with Serializable { def numberOfInstances: Int def numberOfObservers: Int + def numberOfDivulgees: Int def uniqueParties: Boolean } @@ -28,11 +29,13 @@ object WorkflowConfig { value: Int, ) extends SubmissionConfig { override val numberOfObservers = 0 + override val numberOfDivulgees = 0 } final case class FooSubmissionConfig( numberOfInstances: Int, numberOfObservers: Int, + numberOfDivulgees: Int, uniqueParties: Boolean, instanceDistribution: List[WorkflowConfig.FooSubmissionConfig.ContractDescription], nonConsumingExercises: Option[NonconsumingExercises], diff --git a/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/config/WorkflowConfigParser.scala b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/config/WorkflowConfigParser.scala index 150ec37e79c..b48dbb6f1c1 100644 --- a/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/config/WorkflowConfigParser.scala +++ b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/config/WorkflowConfigParser.scala @@ -117,9 +117,10 @@ object WorkflowConfigParser { )(FooSubmissionConfig.ConsumingExercises.apply) implicit val fooSubmissionConfigDecoder: Decoder[FooSubmissionConfig] = - Decoder.forProduct6( + Decoder.forProduct7( "num_instances", "num_observers", + "num_divulgees", "unique_parties", "instance_distribution", "nonconsuming_exercises", diff --git a/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/CommandSubmitter.scala b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/CommandSubmitter.scala index edbcdb987c6..5ea187971b9 100644 --- a/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/CommandSubmitter.scala +++ b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/CommandSubmitter.scala @@ -40,20 +40,24 @@ case class CommandSubmitter( ( client.binding.Primitive.Party, List[client.binding.Primitive.Party], + List[client.binding.Primitive.Party], ) ] = { val observerPartyNames = names.observerPartyNames(config.numberOfObservers, config.uniqueParties) + val divulgeePartyNames = + names.divulgeePartyNames(config.numberOfDivulgees, config.uniqueParties) logger.info("Generating contracts...") logger.info(s"Identifier suffix: ${names.identifierSuffix}") (for { signatory <- allocateSignatoryParty() - observers <- allocateObserverParties(observerPartyNames) + observers <- allocateParties(observerPartyNames) + divulgees <- allocateParties(divulgeePartyNames) _ <- uploadTestDars() } yield { logger.info("Prepared command submission.") - (signatory, observers) + (signatory, observers, divulgees) }) .recoverWith { case NonFatal(ex) => logger.error( @@ -64,10 +68,24 @@ case class CommandSubmitter( } } + def submitSingleBatch( + commandId: String, + actAs: Seq[Primitive.Party], + commands: Seq[Command], + )(implicit + ec: ExecutionContext + ): Future[Unit] = { + submitAndWait( + id = commandId, + actAs = actAs, + commands = commands, + ) + } + def generateAndSubmit( generator: CommandGenerator, config: SubmissionConfig, - signatory: client.binding.Primitive.Party, + actAs: List[client.binding.Primitive.Party], maxInFlightCommands: Int, submissionBatchSize: Int, )(implicit ec: ExecutionContext): Future[Unit] = { @@ -76,9 +94,9 @@ case class CommandSubmitter( _ <- submitCommands( generator = generator, config = config, - signatory = signatory, maxInFlightCommands = maxInFlightCommands, submissionBatchSize = submissionBatchSize, + actAs = actAs, ) } yield { logger.info("Commands submitted successfully.") @@ -93,11 +111,11 @@ case class CommandSubmitter( private def allocateSignatoryParty()(implicit ec: ExecutionContext): Future[Primitive.Party] = adminServices.partyManagementService.allocateParty(names.signatoryPartyName) - private def allocateObserverParties(observerPartyNames: Seq[String])(implicit + private def allocateParties(divulgeePartyNames: Seq[String])(implicit ec: ExecutionContext ): Future[List[Primitive.Party]] = { Future.sequence( - observerPartyNames.toList.map(adminServices.partyManagementService.allocateParty) + divulgeePartyNames.toList.map(adminServices.partyManagementService.allocateParty) ) } @@ -145,7 +163,7 @@ case class CommandSubmitter( private def submitCommands( generator: CommandGenerator, config: SubmissionConfig, - signatory: Primitive.Party, + actAs: List[Primitive.Party], maxInFlightCommands: Int, submissionBatchSize: Int, )(implicit @@ -188,7 +206,7 @@ case class CommandSubmitter( timed(submitAndWaitTimer, metricsManager)( submitAndWait( id = names.commandId(index), - actAs = Seq(signatory), + actAs = actAs, commands = commands.flatten, ) ) diff --git a/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/FooCommandGenerator.scala b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/FooCommandGenerator.scala index c970c64e392..f7e92b4a1ed 100644 --- a/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/FooCommandGenerator.scala +++ b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/FooCommandGenerator.scala @@ -17,11 +17,15 @@ import com.daml.ledger.client.binding import scala.util.control.NonFatal import scala.util.{Failure, Try} +/** @param divulgeesToDivulgerKeyMap map whose keys are sorted divulgees lists + */ final class FooCommandGenerator( randomnessProvider: RandomnessProvider, config: FooSubmissionConfig, signatory: Primitive.Party, allObservers: List[Primitive.Party], + allDivulgees: List[Primitive.Party], + divulgeesToDivulgerKeyMap: Map[Set[Primitive.Party], Value], ) extends CommandGenerator { private val distribution = new Distribution(config.instanceDistribution.map(_.weight)) private val descriptionMapping: Map[Int, FooSubmissionConfig.ContractDescription] = @@ -30,6 +34,10 @@ final class FooCommandGenerator( .toMap private val observersWithUnlikelihood: List[(Primitive.Party, Int)] = allObservers.zipWithIndex.toMap.view.mapValues(unlikelihood).toList + private val divulgeesWithUnlikelihood: List[(Primitive.Party, Int)] = + allDivulgees.zipWithIndex.toMap.view.mapValues(unlikelihood).toList.sortBy { case (party, _) => + party.toString + } /** @return denominator of a 1/(10**i) likelihood */ @@ -37,14 +45,16 @@ final class FooCommandGenerator( def next(): Try[Seq[Command]] = (for { - (description, observers) <- Try( - (pickDescription(), pickObservers()) + (description, observers, divulgees) <- Try( + (pickDescription(), pickObservers(), pickDivulgees()) ) createContractPayload <- Try(randomPayload(description.payloadSizeBytes)) command = createCommands( templateDescriptor = FooTemplateDescriptor.forName(description.template), signatory = signatory, observers = observers, + divulgerContractKeyO = + if (divulgees.isEmpty) None else divulgeesToDivulgerKeyMap.get(divulgees), payload = createContractPayload, ) } yield command).recoverWith { case NonFatal(ex) => @@ -64,6 +74,11 @@ final class FooCommandGenerator( .filter { case (_, unlikelihood) => randomDraw(unlikelihood) } .map(_._1) + private def pickDivulgees(): Set[Primitive.Party] = + divulgeesWithUnlikelihood.view.collect { + case (party, unlikelihood) if randomDraw(unlikelihood) => party + }.toSet + private def randomDraw(unlikelihood: Int): Boolean = randomnessProvider.randomNatural(unlikelihood) == 0 @@ -71,15 +86,27 @@ final class FooCommandGenerator( templateDescriptor: FooTemplateDescriptor, signatory: Primitive.Party, observers: List[Primitive.Party], + divulgerContractKeyO: Option[Value], payload: String, ): Seq[Command] = { - val contractNumber = FooCommandGenerator.nextContractNumber.getAndIncrement() - val fooKeyId = "foo-" + contractNumber + val contractCounter = FooCommandGenerator.nextContractNumber.getAndIncrement() + val fooKeyId = "foo-" + contractCounter val fooContractKey = FooCommandGenerator.makeContractKeyValue(signatory, fooKeyId) - val createFooCmd = templateDescriptor.name match { - case "Foo1" => Foo1(signatory, observers, payload, keyId = fooKeyId).create.command - case "Foo2" => Foo2(signatory, observers, payload, keyId = fooKeyId).create.command - case "Foo3" => Foo3(signatory, observers, payload, keyId = fooKeyId).create.command + val createFooCmd = divulgerContractKeyO match { + case Some(divulgerContractKey) => + makeCreateAndDivulgeFooCommand( + divulgerContractKey = divulgerContractKey, + payload = payload, + fooKeyId = fooKeyId, + observers = observers, + templateName = templateDescriptor.name, + ) + case None => + templateDescriptor.name match { + case "Foo1" => Foo1(signatory, observers, payload, keyId = fooKeyId).create.command + case "Foo2" => Foo2(signatory, observers, payload, keyId = fooKeyId).create.command + case "Foo3" => Foo3(signatory, observers, payload, keyId = fooKeyId).create.command + } } val nonconsumingExercisePayloads: Seq[String] = config.nonConsumingExercises.fold(Seq.empty[String]) { config => @@ -123,6 +150,45 @@ final class FooCommandGenerator( Seq(createFooCmd) ++ nonconsumingExercises ++ consumingExerciseO.toList } + private def makeCreateAndDivulgeFooCommand( + divulgerContractKey: Value, + payload: String, + fooKeyId: String, + observers: List[Primitive.Party], + templateName: String, + ) = { + makeExerciseByKeyCommand( + templateId = FooTemplateDescriptor.Divulger_templateId, + choiceName = FooTemplateDescriptor.Divulger_DivulgeImmediate, + args = Seq( + RecordField( + label = "fooObservers", + value = Some( + Value( + Value.Sum.List( + com.daml.ledger.api.v1.value.List( + observers.map(obs => Value(Value.Sum.Party(obs.toString))) + ) + ) + ) + ), + ), + RecordField( + label = "fooPayload", + value = Some(Value(Value.Sum.Text(payload))), + ), + RecordField( + label = "fooKeyId", + value = Some(Value(Value.Sum.Text(fooKeyId))), + ), + RecordField( + label = "fooTemplateName", + value = Some(Value(Value.Sum.Text(templateName))), + ), + ), + )(contractKey = divulgerContractKey) + } + def makeExerciseByKeyCommand(templateId: Identifier, choiceName: String, args: Seq[RecordField])( contractKey: Value ): Command = { diff --git a/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/FooDivulgerCommandGenerator.scala b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/FooDivulgerCommandGenerator.scala new file mode 100644 index 00000000000..24cfd376a3e --- /dev/null +++ b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/FooDivulgerCommandGenerator.scala @@ -0,0 +1,67 @@ +// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.daml.ledger.api.benchtool.submission + +import com.daml.ledger.api.v1.commands.Command +import com.daml.ledger.api.v1.value.Value +import com.daml.ledger.client.binding.Primitive +import com.daml.ledger.test.model.Foo.Divulger + +object FooDivulgerCommandGenerator { + + /** Builds a create Divulger command for each non-empty subset of divulgees + * such that the created Divulger contract can be used to divulge (by immediate divulgence) Foo1, Foo2 or Foo3 contracts + * to the corresponding subset of divulgees. + * + * @param allDivulgees - Small number of divulgees. At most 5. + * @return A tuple of: + * - a sequence of create Divulger commands, + * - a map from sets of divulgees (all non-empty subsets of all divulgees) to corresponding contract keys, + */ + def makeCreateDivulgerCommands( + divulgingParty: Primitive.Party, + allDivulgees: List[Primitive.Party], + ): (List[Command], Map[Set[Primitive.Party], Value]) = { + require( + allDivulgees.size <= 5, + s"Number of divulgee parties must be at most 5, was: ${allDivulgees.size}.", + ) + def allNonEmptySubsets(divulgees: List[Primitive.Party]): List[List[Primitive.Party]] = { + def iter(remaining: List[Primitive.Party]): List[List[Primitive.Party]] = { + remaining match { + case Nil => List(List.empty) + case head :: tail => + val sub: List[List[Primitive.Party]] = iter(tail) + val sub2: List[List[Primitive.Party]] = sub.map(xs => xs.prepended(head)) + sub ::: sub2 + } + } + import scalaz.syntax.tag._ + iter(divulgees) + .collect { + case parties if parties.nonEmpty => parties.sortBy(_.unwrap) + } + } + + def createDivulgerFor(divulgees: List[Primitive.Party]): (Command, Value) = { + val keyId = "divulger-" + FooCommandGenerator.nextContractNumber.getAndIncrement() + val createDivulgerCmd = Divulger( + divulgees = divulgees, + divulger = divulgingParty, + keyId = keyId, + ).create.command + val divulgerKey: Value = FooCommandGenerator.makeContractKeyValue(divulgingParty, keyId) + (createDivulgerCmd, divulgerKey) + } + + val allSubsets = allNonEmptySubsets(allDivulgees) + val (commands, keys, divulgeeSets) = allSubsets.map { divulgees: List[Primitive.Party] => + val (cmd, key) = createDivulgerFor(divulgees) + (cmd, key, divulgees.toSet) + }.unzip3 + val divulgeesToContractKeysMap = divulgeeSets.zip(keys).toMap + (commands, divulgeesToContractKeysMap) + } + +} diff --git a/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/FooSubmission.scala b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/FooSubmission.scala index 4a6a58325bf..122fe8187ef 100644 --- a/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/FooSubmission.scala +++ b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/FooSubmission.scala @@ -8,8 +8,6 @@ import com.daml.ledger.client.binding import scala.concurrent.{ExecutionContext, Future} -/** Generates and submits Foo and related commands - */ class FooSubmission( submitter: CommandSubmitter, maxInFlightCommands: Int, @@ -17,27 +15,49 @@ class FooSubmission( submissionConfig: FooSubmissionConfig, signatory: binding.Primitive.Party, allObservers: List[binding.Primitive.Party], + allDivulgees: List[binding.Primitive.Party], ) { def performSubmission()(implicit ec: ExecutionContext ): Future[Unit] = { - val generator = new FooCommandGenerator( - randomnessProvider = RandomnessProvider.Default, - signatory = signatory, - config = submissionConfig, - allObservers = allObservers, - ) + val (divulgerCmds, divulgeesToDivulgerKeyMap) = FooDivulgerCommandGenerator + .makeCreateDivulgerCommands( + divulgingParty = signatory, + allDivulgees = allDivulgees, + ) + for { + _ <- + if (divulgerCmds.nonEmpty) { + require( + divulgeesToDivulgerKeyMap.nonEmpty, + "Map from divulgees to Divulger contract keys must be non empty.", + ) + submitter.submitSingleBatch( + commandId = "divulgence-setup", + actAs = Seq(signatory) ++ allDivulgees, + commands = divulgerCmds, + ) + } else { + Future.unit + } + generator: CommandGenerator = new FooCommandGenerator( + randomnessProvider = RandomnessProvider.Default, + signatory = signatory, + config = submissionConfig, + allObservers = allObservers, + allDivulgees = allDivulgees, + divulgeesToDivulgerKeyMap = divulgeesToDivulgerKeyMap, + ) _ <- submitter .generateAndSubmit( generator = generator, config = submissionConfig, - signatory = signatory, + actAs = List(signatory) ++ allDivulgees, maxInFlightCommands = maxInFlightCommands, submissionBatchSize = submissionBatchSize, ) } yield () } - } diff --git a/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/FooTemplateDescriptor.scala b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/FooTemplateDescriptor.scala index 383d7ccee5a..fe4d1de8ce6 100644 --- a/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/FooTemplateDescriptor.scala +++ b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/FooTemplateDescriptor.scala @@ -41,4 +41,7 @@ object FooTemplateDescriptor { def forName(templateName: String): FooTemplateDescriptor = all.getOrElse(templateName, sys.error(s"Invalid template: $templateName")) + val Divulger_templateId: Identifier = + com.daml.ledger.test.model.Foo.Divulger.id.asInstanceOf[Identifier] + val Divulger_DivulgeImmediate = "DivulgeImmediate" } diff --git a/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/Names.scala b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/Names.scala index c77e419da4d..d0d6e3308bd 100644 --- a/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/Names.scala +++ b/ledger/ledger-api-bench-tool/src/main/scala/com/daml/ledger/api/benchtool/submission/Names.scala @@ -17,9 +17,16 @@ class Names { if (uniqueParties) s"Obs-$index-$identifierSuffix" else s"Obs-$index" + def divulgeePartyName(index: Int, uniqueParties: Boolean): String = + if (uniqueParties) s"Div-$index-$identifierSuffix" + else s"Div-$index" + def observerPartyNames(numberOfObservers: Int, uniqueParties: Boolean): Seq[String] = (0 until numberOfObservers).map(i => observerPartyName(i, uniqueParties)) + def divulgeePartyNames(numberOfDivulgees: Int, uniqueParties: Boolean): Seq[String] = + (0 until numberOfDivulgees).map(i => divulgeePartyName(i, uniqueParties)) + def commandId(index: Int): String = s"command-$index-$identifierSuffix" def darId(index: Int) = s"submission-dars-$index-$identifierSuffix" diff --git a/ledger/ledger-api-bench-tool/src/test/suite/scala/com/daml/ledger/api/benchtool/config/WorkflowConfigParserSpec.scala b/ledger/ledger-api-bench-tool/src/test/suite/scala/com/daml/ledger/api/benchtool/config/WorkflowConfigParserSpec.scala index de95054cd5c..f466e53076b 100644 --- a/ledger/ledger-api-bench-tool/src/test/suite/scala/com/daml/ledger/api/benchtool/config/WorkflowConfigParserSpec.scala +++ b/ledger/ledger-api-bench-tool/src/test/suite/scala/com/daml/ledger/api/benchtool/config/WorkflowConfigParserSpec.scala @@ -23,6 +23,7 @@ class WorkflowConfigParserSpec extends AnyWordSpec with Matchers { | type: foo | num_instances: 500 | num_observers: 4 + | num_divulgees: 5 | unique_parties: true | instance_distribution: | - template: Foo1 @@ -52,6 +53,7 @@ class WorkflowConfigParserSpec extends AnyWordSpec with Matchers { WorkflowConfig.FooSubmissionConfig( numberOfInstances = 500, numberOfObservers = 4, + numberOfDivulgees = 5, uniqueParties = true, instanceDistribution = List( WorkflowConfig.FooSubmissionConfig.ContractDescription( @@ -101,6 +103,7 @@ class WorkflowConfigParserSpec extends AnyWordSpec with Matchers { | type: foo | num_instances: 500 | num_observers: 4 + | num_divulgees: 5 | unique_parties: true | instance_distribution: | - template: Foo1 @@ -119,6 +122,7 @@ class WorkflowConfigParserSpec extends AnyWordSpec with Matchers { WorkflowConfig.FooSubmissionConfig( numberOfInstances = 500, numberOfObservers = 4, + numberOfDivulgees = 5, uniqueParties = true, instanceDistribution = List( WorkflowConfig.FooSubmissionConfig.ContractDescription( diff --git a/ledger/ledger-api-bench-tool/src/test/suite/scala/com/daml/ledger/api/benchtool/submission/FibonacciCommandSubmitterITSpec.scala b/ledger/ledger-api-bench-tool/src/test/suite/scala/com/daml/ledger/api/benchtool/submission/FibonacciCommandSubmitterITSpec.scala index 9b33b3c4a78..6e56812c94a 100644 --- a/ledger/ledger-api-bench-tool/src/test/suite/scala/com/daml/ledger/api/benchtool/submission/FibonacciCommandSubmitterITSpec.scala +++ b/ledger/ledger-api-bench-tool/src/test/suite/scala/com/daml/ledger/api/benchtool/submission/FibonacciCommandSubmitterITSpec.scala @@ -42,7 +42,8 @@ class FibonacciCommandSubmitterITSpec metricRegistry = new MetricRegistry, metricsManager = NoOpMetricsManager(), ) - (signatory, _) <- tested.prepare(config) + (signatory, _, divulgees) <- tested.prepare(config) + _ = divulgees shouldBe empty generator = new FibonacciCommandGenerator( signatory = signatory, config = config, @@ -50,7 +51,7 @@ class FibonacciCommandSubmitterITSpec _ <- tested.generateAndSubmit( generator = generator, config = config, - signatory = signatory, + actAs = List(signatory) ++ divulgees, maxInFlightCommands = 1, submissionBatchSize = 5, ) diff --git a/ledger/ledger-api-bench-tool/src/test/suite/scala/com/daml/ledger/api/benchtool/submission/FooCommandSubmitterITSpec.scala b/ledger/ledger-api-bench-tool/src/test/suite/scala/com/daml/ledger/api/benchtool/submission/FooCommandSubmitterITSpec.scala index 7d9d199668f..2f89df7499a 100644 --- a/ledger/ledger-api-bench-tool/src/test/suite/scala/com/daml/ledger/api/benchtool/submission/FooCommandSubmitterITSpec.scala +++ b/ledger/ledger-api-bench-tool/src/test/suite/scala/com/daml/ledger/api/benchtool/submission/FooCommandSubmitterITSpec.scala @@ -49,6 +49,7 @@ class FooCommandSubmitterITSpec val config = WorkflowConfig.FooSubmissionConfig( numberOfInstances = 10, numberOfObservers = 1, + numberOfDivulgees = 0, uniqueParties = false, instanceDistribution = List( foo1Config, @@ -64,27 +65,25 @@ class FooCommandSubmitterITSpec authorizationHelper = None, ) apiServices = ledgerApiServicesF("someUser") - tested = CommandSubmitter( + submitter = CommandSubmitter( names = new Names(), benchtoolUserServices = apiServices, adminServices = apiServices, metricRegistry = new MetricRegistry, metricsManager = NoOpMetricsManager(), ) - (signatory, observers) <- tested.prepare(config) - generator: CommandGenerator = new FooCommandGenerator( - randomnessProvider = RandomnessProvider.Default, - signatory = signatory, - config = config, - allObservers = observers, - ) - _ <- tested.generateAndSubmit( - generator = generator, - config = config, - signatory = signatory, + (signatory, observers, divulgees) <- submitter.prepare(config) + _ = divulgees shouldBe empty + tested = new FooSubmission( + submitter = submitter, maxInFlightCommands = 1, submissionBatchSize = 5, + submissionConfig = config, + signatory = signatory, + allObservers = observers, + allDivulgees = divulgees, ) + _ <- tested.performSubmission() eventsObserver = TreeEventsObserver(expectedTemplateNames = Set("Foo1", "Foo2")) _ <- apiServices.transactionService.transactionTrees( config = WorkflowConfig.StreamConfig.TransactionTreesStreamConfig( diff --git a/ledger/ledger-api-bench-tool/src/test/suite/scala/com/daml/ledger/api/benchtool/submission/NonStakeholderInformeesITSpec.scala b/ledger/ledger-api-bench-tool/src/test/suite/scala/com/daml/ledger/api/benchtool/submission/NonStakeholderInformeesITSpec.scala new file mode 100644 index 00000000000..f61ba3b294c --- /dev/null +++ b/ledger/ledger-api-bench-tool/src/test/suite/scala/com/daml/ledger/api/benchtool/submission/NonStakeholderInformeesITSpec.scala @@ -0,0 +1,177 @@ +// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.daml.ledger.api.benchtool.submission + +import com.codahale.metrics.MetricRegistry +import com.daml.ledger.api.benchtool.config.WorkflowConfig +import com.daml.ledger.api.benchtool.metrics.MetricsManager.NoOpMetricsManager +import com.daml.ledger.api.benchtool.services.LedgerApiServices +import com.daml.ledger.api.testing.utils.SuiteResourceManagementAroundAll +import com.daml.ledger.api.v1.ledger_offset.LedgerOffset +import com.daml.ledger.client.binding +import com.daml.platform.sandbox.fixture.SandboxFixture +import org.scalatest.{AppendedClues, OptionValues} +import org.scalatest.flatspec.AsyncFlatSpec +import org.scalatest.matchers.should.Matchers + +import scala.concurrent.Future + +class NonStakeholderInformeesITSpec + extends AsyncFlatSpec + with SandboxFixture + with SuiteResourceManagementAroundAll + with Matchers + with AppendedClues + with OptionValues { + + it should "divulge events" in { + val expectedTemplateNames = Set("Foo1", "Divulger") + val submissionConfig = WorkflowConfig.FooSubmissionConfig( + numberOfInstances = 100, + numberOfObservers = 1, + numberOfDivulgees = 3, + uniqueParties = false, + instanceDistribution = List( + WorkflowConfig.FooSubmissionConfig.ContractDescription( + template = "Foo1", + weight = 1, + payloadSizeBytes = 0, + ) + ), + nonConsumingExercises = None, + consumingExercises = None, + ) + for { + ledgerApiServicesF <- LedgerApiServices.forChannel( + channel = channel, + authorizationHelper = None, + ) + apiServices: LedgerApiServices = ledgerApiServicesF("someUser") + submitter = CommandSubmitter( + names = new Names(), + benchtoolUserServices = apiServices, + adminServices = apiServices, + metricRegistry = new MetricRegistry, + metricsManager = NoOpMetricsManager(), + ) + (signatory, observers, divulgees) <- submitter.prepare(submissionConfig) + tested = new FooSubmission( + submitter = submitter, + maxInFlightCommands = 1, + submissionBatchSize = 5, + submissionConfig = submissionConfig, + signatory = signatory, + allObservers = observers, + allDivulgees = divulgees, + ) + _ <- tested.performSubmission() + (treeResults_divulgee0, flatResults_divulgee0) <- observeAllTemplatesForParty( + party = divulgees(0), + apiServices = apiServices, + expectedTemplateNames = expectedTemplateNames, + ) + (treeResults_divulgee1, flatResults_divulgee1) <- observeAllTemplatesForParty( + party = divulgees(1), + apiServices = apiServices, + expectedTemplateNames = expectedTemplateNames, + ) + (treeResults_observer0, flatResults_observer0) <- observeAllTemplatesForParty( + party = observers(0), + apiServices = apiServices, + expectedTemplateNames = expectedTemplateNames, + ) + (treeResults_signatory, _) <- observeAllTemplatesForParty( + party = signatory, + apiServices = apiServices, + expectedTemplateNames = expectedTemplateNames, + ) + } yield { + // Creates of Foo1 are divulged to "divulgee" party, + // thus, they are visible on transaction trees stream but absent from flat transactions stream. + { + // Divulge0 + val treeFoo1 = treeResults_divulgee0.numberOfCreatesPerTemplateName("Foo1") + val flatFoo1 = flatResults_divulgee0.numberOfCreatesPerTemplateName("Foo1") + treeFoo1 shouldBe 100 withClue ("number of Foo1 contracts visible to divulgee0 on tree transactions stream") + flatFoo1 shouldBe 0 withClue ("number of Foo1 contracts visible to divulgee0 on flat transactions stream") + val divulger = treeResults_divulgee0.numberOfCreatesPerTemplateName("Divulger") + // For 3 divulgees in total (a, b, c) there are 4 subsets that contain 'a': a, ab, ac, abc. + divulger shouldBe 4 withClue ("number divulger contracts visible to divulgee0") + } + { + // Divulgee1 + val treeFoo1 = treeResults_divulgee1.numberOfCreatesPerTemplateName("Foo1") + val flatFoo1 = flatResults_divulgee1.numberOfCreatesPerTemplateName("Foo1") + // This assertion will fail once in ~37k test executions + // because for 100 instances and 10% chance of divulging to divulgee1, divulgee1 won't be disclosed any contracts once in 1/(0.9**100) ~= 37649 + treeFoo1 should be > 0 + flatFoo1 shouldBe 0 + + val divulger = treeResults_divulgee1.numberOfCreatesPerTemplateName("Divulger") + divulger shouldBe 4 + } + { + // Observer0 + val treeFoo1 = flatResults_observer0.numberOfCreatesPerTemplateName("Foo1") + val flatFoo1 = treeResults_observer0.numberOfCreatesPerTemplateName("Foo1") + flatFoo1 shouldBe 100 + flatFoo1 shouldBe treeFoo1 + + val divulger = treeResults_observer0.numberOfCreatesPerTemplateName("Divulger") + divulger shouldBe 0 + } + { + val divulger = treeResults_signatory.numberOfCreatesPerTemplateName("Divulger") + divulger shouldBe 7 + } + succeed + } + } + + private def observeAllTemplatesForParty( + party: binding.Primitive.Party, + apiServices: LedgerApiServices, + expectedTemplateNames: Set[String], + ): Future[(ObservedEvents, ObservedEvents)] = { + val treeTxObserver = TreeEventsObserver(expectedTemplateNames = expectedTemplateNames) + val flatTxObserver = FlatEventsObserver(expectedTemplateNames = expectedTemplateNames) + for { + _ <- apiServices.transactionService.transactionTrees( + config = WorkflowConfig.StreamConfig.TransactionTreesStreamConfig( + name = "dummy-name", + filters = List( + WorkflowConfig.StreamConfig.PartyFilter( + party = party.toString, + templates = List.empty, + ) + ), + beginOffset = None, + endOffset = Some(LedgerOffset().withBoundary(LedgerOffset.LedgerBoundary.LEDGER_END)), + objectives = None, + ), + observer = treeTxObserver, + ) + _ <- apiServices.transactionService.transactions( + config = WorkflowConfig.StreamConfig.TransactionsStreamConfig( + name = "dummy-name", + filters = List( + WorkflowConfig.StreamConfig.PartyFilter( + party = party.toString, + templates = List.empty, + ) + ), + beginOffset = None, + endOffset = Some(LedgerOffset().withBoundary(LedgerOffset.LedgerBoundary.LEDGER_END)), + objectives = None, + ), + observer = flatTxObserver, + ) + treeResults: ObservedEvents <- treeTxObserver.result + flatResults: ObservedEvents <- flatTxObserver.result + } yield { + (treeResults, flatResults) + } + } + +} diff --git a/ledger/test-common/src/main/daml/model/Foo.daml b/ledger/test-common/src/main/daml/model/Foo.daml index 72eafd903d3..e995021a1f3 100644 --- a/ledger/test-common/src/main/daml/model/Foo.daml +++ b/ledger/test-common/src/main/daml/model/Foo.daml @@ -3,6 +3,50 @@ module Foo where +import DA.Functor (void) + + +template Divulger + with + divulgees: [Party] -- Parties to whom something is divulged + divulger: Party -- Party who divulges something + keyId: Text + where + signatory [divulger] <> divulgees + + key (divulger, keyId): (Party, Text) + maintainer key._1 + + nonconsuming choice DivulgeImmediate: () + with + fooObservers : [Party] + fooPayload : Text + fooKeyId: Text + fooTemplateName: Text + controller divulger + do + -- Party 'divulgee' sees the creation of Foo even though she is not a stakeholder i.e. immediate divulgence occurs. + if fooTemplateName == "Foo1" then + void $ create Foo1 with + signatory = divulger + observers = fooObservers + payload = fooPayload + keyId = fooKeyId + else if fooTemplateName == "Foo2" then + void $ create Foo2 with + signatory = divulger + observers = fooObservers + payload = fooPayload + keyId = fooKeyId + else if fooTemplateName == "Foo3" then + void $ create Foo3 with + signatory = divulger + observers = fooObservers + payload = fooPayload + keyId = fooKeyId + else + return () + template Foo1 with signatory : Party