Skip to content

Commit

Permalink
Refactor trigger rule simulation lib (#16645)
Browse files Browse the repository at this point in the history
  • Loading branch information
carlpulley-da committed Apr 3, 2023
1 parent 69d4a30 commit 1a58e9a
Show file tree
Hide file tree
Showing 4 changed files with 404 additions and 285 deletions.
39 changes: 32 additions & 7 deletions triggers/tests/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,11 @@ EOF
da_scala_library(
name = "test-utils",
srcs = [
"src/test/scala/com/digitalasset/daml/lf/engine/trigger/TriggerRuleSimulationLib.scala",
"src/test/scala/com/digitalasset/daml/lf/engine/trigger/test/AbstractFuncTests.scala",
"src/test/scala/com/digitalasset/daml/lf/engine/trigger/test/AbstractTriggerTest.scala",
],
scala_deps = [
"@maven//:com_typesafe_akka_akka_stream",
"@maven//:org_scalacheck_scalacheck",
"@maven//:org_scalactic_scalactic",
"@maven//:org_scalatest_scalatest_core",
"@maven//:org_scalatest_scalatest_matchers_core",
Expand All @@ -104,7 +102,6 @@ da_scala_library(
"//daml-lf/interpreter",
"//daml-lf/language",
"//daml-lf/transaction",
"//daml-script/converter",
"//language-support/scala/bindings",
"//language-support/scala/bindings-akka",
"//ledger-api/rs-grpc-bridge",
Expand All @@ -116,13 +113,9 @@ da_scala_library(
"//ledger/participant-integration-api",
"//ledger/sandbox-on-x",
"//ledger/sandbox-on-x:sandbox-on-x-test-lib",
"//libs-scala/contextualized-logging",
"//libs-scala/ledger-resources",
"//libs-scala/logging-entries",
"//libs-scala/ports",
"//libs-scala/resources",
"//libs-scala/scala-utils",
"//observability/tracing",
"//test-common",
"//triggers/runner:trigger-runner-lib",
"@maven//:org_scalatest_scalatest_compatible",
Expand Down Expand Up @@ -246,6 +239,37 @@ da_scala_library(
for suffix in [("-" + lf_version) if lf_version else ""]
]

da_scala_library(
name = "trigger-simulation-lib",
srcs = [
"src/test/scala/com/digitalasset/daml/lf/engine/trigger/CatGenerators.scala",
"src/test/scala/com/digitalasset/daml/lf/engine/trigger/TriggerRuleSimulationLib.scala",
],
scala_deps = [
"@maven//:com_typesafe_akka_akka_stream",
"@maven//:org_scalacheck_scalacheck",
"@maven//:org_scalactic_scalactic",
"@maven//:org_scalatest_scalatest_core",
"@maven//:org_scalaz_scalaz_core",
],
deps = [
"//daml-lf/data",
"//daml-lf/interpreter",
"//daml-lf/transaction",
"//daml-script/converter",
"//language-support/scala/bindings",
"//language-support/scala/bindings-akka",
"//ledger/ledger-api-common",
"//ledger/participant-integration-api",
"//libs-scala/contextualized-logging",
"//libs-scala/logging-entries",
"//libs-scala/scala-utils",
"//observability/tracing",
"//triggers/runner:trigger-runner-lib",
"@maven//:org_scalatest_scalatest_compatible",
],
)

da_scala_test_suite(
name = "trigger-simulation-lib-tests",
timeout = "long",
Expand All @@ -264,6 +288,7 @@ da_scala_test_suite(
],
deps = [
":test-utils",
":trigger-simulation-lib",
"//bazel_tools/runfiles:scala_runfiles",
"//daml-lf/archive:daml_lf_archive_reader",
"//daml-lf/data",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.daml.lf.engine.trigger

import akka.NotUsed
import akka.stream.scaladsl.Source
import com.daml.ledger.api.v1.command_submission_service.SubmitRequest
import com.daml.ledger.api.v1.commands.Command.{Command => ApiCommand}
import com.daml.ledger.api.v1.completion.Completion
import com.daml.ledger.api.v1.event.{ArchivedEvent, CreatedEvent, Event}
import com.daml.ledger.api.v1.transaction.Transaction
import com.daml.ledger.api.v1.{value => LedgerApi}
import com.daml.lf.crypto
import com.daml.lf.data.{Bytes, Ref}
import com.daml.lf.data.Ref.{Identifier, PackageId, QualifiedName}
import com.daml.lf.speedy.{Command, SValue}
import com.daml.lf.speedy.SValue.{SContractId, SInt64, SParty}
import com.daml.lf.value.Value.ContractId
import com.daml.script.converter.Converter.record
import com.google.rpc.status.Status
import io.grpc.Status.Code
import io.grpc.Status.Code.OK
import org.scalacheck.Gen

import java.util.UUID
import scala.concurrent.ExecutionContext

trait CatGenerators {

protected def packageId: PackageId

val partyGen: Gen[String] = Gen.const("alice")

def toContractId(s: String): ContractId =
ContractId.V1.assertBuild(crypto.Hash.hashPrivateKey(s), Bytes.assertFromString("00"))

def create(template: String, owner: String, i: Long): CreatedEvent =
CreatedEvent(
contractId = toContractId(s"$template:$i").coid,
templateId = Some(LedgerApi.Identifier(packageId, "Cats", template)),
createArguments = Some(
LedgerApi.Record(fields =
Seq(
LedgerApi.RecordField("owner", Some(LedgerApi.Value().withParty(owner))),
template match {
case "TestControl" =>
LedgerApi.RecordField("size", Some(LedgerApi.Value().withInt64(i)))
case _ =>
LedgerApi.RecordField("isin", Some(LedgerApi.Value().withInt64(i)))
},
)
)
),
)

def archive(template: String, owner: String, i: Long): ArchivedEvent =
ArchivedEvent(
contractId = toContractId(s"$template:$i").coid,
templateId = Some(LedgerApi.Identifier(packageId, "Cats", template)),
witnessParties = Seq(owner),
)

def createCat(owner: String, i: Long): CreatedEvent = create("Cat", owner, i)

def archivedCat(owner: String, i: Long): ArchivedEvent = archive("Cat", owner, i)

def createFood(owner: String, i: Long): CreatedEvent = create("Food", owner, i)

def archivedFood(owner: String, i: Long): ArchivedEvent = archive("Food", owner, i)

def createCommandGen(maxNumOfCats: Long): Gen[Command.Create] =
for {
party <- partyGen
templateId <- Gen.frequency(
1 -> Identifier(packageId, QualifiedName.assertFromString("Cats:Cat")),
1 -> Identifier(packageId, QualifiedName.assertFromString("Cats:Food")),
)
n <- Gen.choose(0, maxNumOfCats)
arguments = record(
templateId,
"owner" -> SParty(Ref.Party.assertFromString(party)),
"isin" -> SInt64(n),
)
} yield Command.Create(templateId, arguments)

def exerciseCommandGen(maxNumOfCats: Long): Gen[Command.ExerciseTemplate] =
for {
n <- Gen.choose(0L, maxNumOfCats)
templateId = Identifier(packageId, QualifiedName.assertFromString("Cats:Cat"))
contractId = SContractId(toContractId(s"Cat:$n"))
foodCid = SContractId(toContractId(s"Food:$n"))
choiceId <- Gen.const("Feed")
argument = record(templateId, "foodCid" -> foodCid)
} yield Command.ExerciseTemplate(
templateId,
contractId,
Ref.ChoiceName.assertFromString(choiceId),
argument,
)

def commandGen(maxNumOfCats: Long): Gen[Command] = Gen.frequency(
1 -> createCommandGen(maxNumOfCats),
9 -> exerciseCommandGen(maxNumOfCats),
)

def transactionGen(
owner: String,
numOfCats: Long = 0,
amountOfFood: Long = 0,
catsKilled: Long = 0,
foodEaten: Long = 0,
knownCmdId: String = UUID.randomUUID().toString,
): Gen[Transaction] =
for {
id <- Gen.numStr
cmdId <- Gen.frequency(
1 -> Gen.const(""),
9 -> Gen.frequency(
1 -> Gen.uuid.map(_.toString),
9 -> Gen.const(knownCmdId),
),
)
cats = (0L to numOfCats)
.map(i => createCat(owner, i))
.map(event => Event(Event.Event.Created(event)))
deadCats = (0L to catsKilled)
.map(i => archivedCat(owner, i))
.map(event => Event(Event.Event.Archived(event)))
food = (0L to amountOfFood)
.map(i => createFood(owner, i))
.map(event => Event(Event.Event.Created(event)))
eatenFood = (0L to foodEaten)
.map(i => archivedFood(owner, i))
.map(event => Event(Event.Event.Archived(event)))
} yield Transaction(
transactionId = id,
commandId = cmdId,
events = cats ++ food ++ deadCats ++ eatenFood,
)

def successfulCompletionGen(
owner: String,
knownCmdId: String = UUID.randomUUID().toString,
): Gen[Completion] =
for {
id <- Gen.numStr
cmdId <- Gen.frequency(
1 -> Gen.const(""),
9 -> Gen.frequency(
1 -> Gen.uuid.map(_.toString),
9 -> Gen.const(knownCmdId),
),
)
} yield Completion(
commandId = cmdId,
status = Some(Status(OK.value(), "")),
transactionId = id,
actAs = Seq(owner),
)

def failingCompletionGen(
owner: String,
knownCmdId: String = UUID.randomUUID().toString,
): Gen[Completion] =
for {
id <- Gen.numStr
cmdId <- Gen.frequency(
1 -> Gen.const(""),
9 -> Gen.frequency(
1 -> Gen.uuid.map(_.toString),
9 -> Gen.const(knownCmdId),
),
)
code <- Gen.choose(1, Code.values().length)
} yield Completion(
commandId = cmdId,
status = Some(Status(code, "simulated-failure")),
transactionId = id,
actAs = Seq(owner),
)
}

trait CatTriggerResourceUsageTestGenerators extends CatGenerators {

def acsGen(owner: String, size: Long): Seq[CreatedEvent] =
acsGen(owner, size, size)

def acsGen(owner: String, numOfCats: Long, amountOfFood: Long): Seq[CreatedEvent] =
(0L to numOfCats).map(i => createCat(owner, i)) ++ (0L to amountOfFood).map(i =>
createFood(owner, i)
)

def monotonicACS(owner: String, sizeGen: Iterator[Long]): Iterator[Seq[CreatedEvent]] =
sizeGen.map(acsGen(owner, _))

def triggerIterator(simulator: TriggerRuleSimulationLib, startState: SValue)(implicit
ec: ExecutionContext
): Source[(Seq[SubmitRequest], TriggerRuleMetrics.RuleMetrics, SValue), NotUsed] = {
Source
.repeat(TriggerMsg.Heartbeat)
.scanAsync[(Seq[SubmitRequest], Option[TriggerRuleMetrics.RuleMetrics], SValue)](
(Seq.empty, None, startState)
) { case ((_, _, state), msg) =>
for {
(submissions, metrics, nextState) <- simulator.updateStateLambda(state, msg)
} yield (submissions, Some(metrics), nextState)
}
.collect { case (submissions, Some(metrics), state) => (submissions, metrics, state) }
}

implicit class TriggerRuleSimulationHelpers(
source: Source[(Seq[SubmitRequest], TriggerRuleMetrics.RuleMetrics, SValue), NotUsed]
) {
def findDuplicateCommandRequests: Source[Set[ApiCommand], NotUsed] = {
source
.scan[(Set[ApiCommand], Set[ApiCommand])]((Set.empty, Set.empty)) {
case ((observations, _), (submissions, _, _)) =>
val newObservations = submissions.flatMap(_.getCommands.commands.map(_.command)).toSet
val newDuplicates = newObservations.intersect(observations)

(observations ++ newObservations, newDuplicates)
}
.map(_._2)
}
}
}
Loading

0 comments on commit 1a58e9a

Please sign in to comment.