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
This commit is contained in:
pbatko-da 2022-05-16 15:27:03 +02:00 committed by GitHub
parent 3794012e11
commit 724a0c1b1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 463 additions and 46 deletions

View File

@ -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"))

View File

@ -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,
)
}

View File

@ -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],

View File

@ -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",

View File

@ -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,
)
)

View File

@ -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 = {

View File

@ -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)
}
}

View File

@ -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 ()
}
}

View File

@ -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"
}

View File

@ -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"

View File

@ -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(

View File

@ -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,
)

View File

@ -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(

View File

@ -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)
}
}
}

View File

@ -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