From 15dd81b5241fbb127ff59b66b3e61ac2052d20a3 Mon Sep 17 00:00:00 2001 From: Remy Date: Mon, 24 Oct 2022 10:00:01 +0200 Subject: [PATCH] [Trigger] update Created with interface (#15299) CHANGELOG_BEGIN CHANGELOG_END --- triggers/daml/Daml/Trigger/Internal.daml | 3 +- triggers/daml/Daml/Trigger/LowLevel.daml | 17 ++++- .../daml/lf/engine/trigger/Converter.scala | 72 ++++++++++++++++--- .../daml/lf/engine/trigger/Runner.scala | 10 +-- triggers/tests/BUILD.bazel | 1 - .../daml/lf/engine/trigger/test/DevOnly.scala | 14 ++-- 6 files changed, 89 insertions(+), 28 deletions(-) diff --git a/triggers/daml/Daml/Trigger/Internal.daml b/triggers/daml/Daml/Trigger/Internal.daml index d9f0840df70..c1291bbb923 100644 --- a/triggers/daml/Daml/Trigger/Internal.daml +++ b/triggers/daml/Daml/Trigger/Internal.daml @@ -148,7 +148,8 @@ lookupTpl cid acs = do -- | HIDE applyEvent : Event -> ACS -> ACS applyEvent ev acs = case ev of - CreatedEvent (Created _ cid tpl) -> insertTpl cid tpl acs + CreatedEvent (Created _ cid (Some tpl) _) -> insertTpl cid tpl acs + CreatedEvent _ -> acs ArchivedEvent (Archived _ cid) -> deleteTpl cid acs -- | HIDE diff --git a/triggers/daml/Daml/Trigger/LowLevel.daml b/triggers/daml/Daml/Trigger/LowLevel.daml index 50b97b30c7a..6fe551151ae 100644 --- a/triggers/daml/Daml/Trigger/LowLevel.daml +++ b/triggers/daml/Daml/Trigger/LowLevel.daml @@ -95,6 +95,15 @@ data Transaction = Transaction , events : [Event] } +-- TODO: https://github.com/digital-asset/daml/issues/14830 +-- replace by DA.Internal.Any.AnyView.AnyView once it is introduced as stable package +type AnyView = () + +data InterfaceView = InterfaceView { + interfaceTypeRep : TemplateTypeRep, + anyView: Optional AnyView +} + -- | An event in a transaction. -- This definition should be kept consistent with the object `EventVariant` defined in -- triggers/runner/src/main/scala/com/digitalasset/daml/lf/engine/trigger/Converter.scala @@ -106,15 +115,17 @@ data Event data Created = Created { eventId : EventId , contractId : AnyContractId - , argument : AnyTemplate + , argument : Optional AnyTemplate + , views : [InterfaceView] } -- | Check if a `Created` event corresponds to the given template. fromCreated : Template t => Created -> Optional (EventId, ContractId t, t) fromCreated Created {eventId, contractId, argument} | Some contractId' <- fromAnyContractId contractId - , Some argument' <- fromAnyTemplate argument - = Some (eventId, contractId', argument') + , Some argument' <- argument + , Some argument'' <- fromAnyTemplate argument' + = Some (eventId, contractId', argument'') | otherwise = None diff --git a/triggers/runner/src/main/scala/com/digitalasset/daml/lf/engine/trigger/Converter.scala b/triggers/runner/src/main/scala/com/digitalasset/daml/lf/engine/trigger/Converter.scala index 2c7f083504f..e2cd9d3e65e 100644 --- a/triggers/runner/src/main/scala/com/digitalasset/daml/lf/engine/trigger/Converter.scala +++ b/triggers/runner/src/main/scala/com/digitalasset/daml/lf/engine/trigger/Converter.scala @@ -6,6 +6,8 @@ package engine package trigger import scalaz.std.either._ +import scalaz.std.option._ +import scalaz.std.list._ import scalaz.syntax.traverse._ import com.daml.lf.data.{FrontStack, ImmArray} import com.daml.lf.data.Ref._ @@ -21,7 +23,7 @@ import com.daml.ledger.api.v1.commands.{ ExerciseCommand, } import com.daml.ledger.api.v1.completion.Completion -import com.daml.ledger.api.v1.event.{ArchivedEvent, CreatedEvent, Event} +import com.daml.ledger.api.v1.event.{ArchivedEvent, CreatedEvent, Event, InterfaceView} import com.daml.ledger.api.v1.transaction.Transaction import com.daml.ledger.api.v1.value import com.daml.ledger.api.validation.NoLoggingValueValidator @@ -36,7 +38,10 @@ import com.daml.platform.participant.util.LfEngineToApi.{ import scala.concurrent.duration.{FiniteDuration, MICROSECONDS} // Convert from a Ledger API transaction to an SValue corresponding to a Message from the Daml.Trigger module -final class Converter(compiledPackages: CompiledPackages, triggerIds: TriggerIds) { +final class Converter( + compiledPackages: CompiledPackages, + triggerDef: TriggerDefinition, +) { import Converter._ import com.daml.script.converter.Converter._, Implicits._ @@ -52,8 +57,13 @@ final class Converter(compiledPackages: CompiledPackages, triggerIds: TriggerIds private[this] def translateValue(ty: Type, value: Value): Either[String, SValue] = valueTranslator.translateValue(ty, value).left.map(res => s"Failure to translate value: $res") - private[this] val anyTemplateTyCon = DA.Internal.Any.assertIdentifier("AnyTemplate") private[this] val templateTypeRepTyCon = DA.Internal.Any.assertIdentifier("TemplateTypeRep") + private[this] val anyTemplateTyCon = DA.Internal.Any.assertIdentifier("AnyTemplate") + // TODO: https://github.com/digital-asset/daml/issues/14830 + // replace by DA.Internal.Any.AnyView.AnyView once it is introduced as stable package + private[this] val anyViewTyCon = DA.Internal.Any.assertIdentifier("AnyView") + + private[this] def triggerIds = triggerDef.triggerIds private[this] val activeContractsTy = triggerIds.damlTriggerLowLevel("ActiveContracts") private[this] val anyContractIdTy = triggerIds.damlTriggerLowLevel("AnyContractId") @@ -130,25 +140,66 @@ final class Converter(compiledPackages: CompiledPackages, triggerIds: TriggerIds "contractId" -> fromAnyContractId(archived.getTemplateId, archived.contractId), ) - private[this] def fromCreatedEvent( + private[this] def fromRecord(typ: Type, record: value.Record): Either[String, SValue] = + for { + record <- validateRecord(record) + tmplPayload <- translateValue(typ, record) + } yield tmplPayload + + private[this] def fromAnyTemplate(typ: Type, value: SValue) = + record(anyTemplateTyCon, "getAnyTemplate" -> SAny(typ, value)) + + private[this] def fromV20CreatedEvent( created: CreatedEvent ): Either[String, SValue] = for { tmplId <- fromIdentifier(created.getTemplateId) - createArguments <- validateRecord(created.getCreateArguments) - tmplPayload <- translateValue(TTyCon(tmplId), createArguments) + tmplType = TTyCon(tmplId) + tmplPayload <- fromRecord(tmplType, created.getCreateArguments) } yield { record( createdTy, "eventId" -> fromEventId(created.eventId), "contractId" -> fromAnyContractId(created.getTemplateId, created.contractId), - "argument" -> record( - anyTemplateTyCon, - "getAnyTemplate" -> SAny(TTyCon(tmplId), tmplPayload), - ), + "argument" -> fromAnyTemplate(tmplType, tmplPayload), ) } + private[this] def fromAnyView(typ: Type, value: SValue) = + record(anyViewTyCon, "getAnyView" -> SAny(typ, value)) + + private[this] def fromInterfaceView(view: InterfaceView): Either[String, SOptional] = + for { + ifaceId <- fromIdentifier(view.getInterfaceId) + iface <- compiledPackages.pkgInterface.lookupInterface(ifaceId).left.map(_.pretty) + viewType = iface.view + viewValue <- view.viewValue.traverseU(fromRecord(viewType, _)) + } yield SOptional(viewValue.map(fromAnyView(viewType, _))) + + private[this] def fromV25CreatedEvent( + created: CreatedEvent + ): Either[String, SValue] = + for { + tmplId <- fromIdentifier(created.getTemplateId) + tmplType = TTyCon(tmplId) + tmplPayload <- created.createArguments.traverseU(fromRecord(tmplType, _)) + views <- created.interfaceViews.toList.traverseU(fromInterfaceView) + } yield { + record( + createdTy, + "eventId" -> fromEventId(created.eventId), + "contractId" -> fromAnyContractId(created.getTemplateId, created.contractId), + "argument" -> SOptional(tmplPayload.map(fromAnyTemplate(tmplType, _))), + "views" -> SList(views.to(FrontStack)), + ) + } + + private[this] val fromCreatedEvent: CreatedEvent => Either[String, SValue] = + triggerDef.version match { + case Trigger.Version.`2.0` => fromV20CreatedEvent + case Trigger.Version.`2.5` => fromV25CreatedEvent + } + private def fromEvent(ev: Event): Either[String, SValue] = ev.event match { case Event.Event.Archived(archivedEvent) => @@ -427,6 +478,7 @@ object Converter { private final case class AnyContractId(templateId: Identifier, contractId: ContractId) private final case class AnyTemplate(ty: Identifier, arg: SValue) + private final case class AnyChoice(name: ChoiceName, arg: SValue) private final case class AnyContractKey(key: SValue) diff --git a/triggers/runner/src/main/scala/com/digitalasset/daml/lf/engine/trigger/Runner.scala b/triggers/runner/src/main/scala/com/digitalasset/daml/lf/engine/trigger/Runner.scala index f4f5aa1d8e6..a7875f5e019 100644 --- a/triggers/runner/src/main/scala/com/digitalasset/daml/lf/engine/trigger/Runner.scala +++ b/triggers/runner/src/main/scala/com/digitalasset/daml/lf/engine/trigger/Runner.scala @@ -222,7 +222,7 @@ object Trigger extends StrictLogging { for { triggerDef <- detectTriggerDefinition(compiledPackages.pkgInterface, triggerId) hasReadAs <- detectHasReadAs(compiledPackages.pkgInterface, triggerDef.triggerIds) - converter = new Converter(compiledPackages, triggerDef.triggerIds) + converter = new Converter(compiledPackages, triggerDef) filter <- getTriggerFilter(compiledPackages, compiler, converter, triggerDef) heartbeat <- getTriggerHeartbeat(compiledPackages, compiler, converter, triggerDef) } yield Trigger(triggerDef, filter, heartbeat, hasReadAs) @@ -354,14 +354,14 @@ class Runner( import Runner.{SeenMsgs, alterF} // Compiles LF expressions into Speedy expressions. - private val compiler: Compiler = compiledPackages.compiler + private val compiler = compiledPackages.compiler // Converts between various objects and SValues. - private val converter: Converter = new Converter(compiledPackages, trigger.defn.triggerIds) + private val converter: Converter = new Converter(compiledPackages, trigger.defn) // These are the command IDs used on the ledger API to submit commands for // this trigger for which we are awaiting either a completion or transaction // message, or both. - private[this] var pendingCommandIds: Map[UUID, SeenMsgs] = Map.empty - private val transactionFilter: TransactionFilter = + private[this] var pendingCommandIds = Map.empty[UUID, SeenMsgs] + private val transactionFilter = TransactionFilter(parties.readers.map(p => (p.unwrap, trigger.filters)).toMap) private[this] def logger = ContextualizedLogger get getClass diff --git a/triggers/tests/BUILD.bazel b/triggers/tests/BUILD.bazel index 7850a4ecffd..a2ef0b7fa00 100644 --- a/triggers/tests/BUILD.bazel +++ b/triggers/tests/BUILD.bazel @@ -154,7 +154,6 @@ da_scala_library( "//daml-lf/engine", "//daml-lf/interpreter", "//daml-lf/language", - "//daml-script/converter", "//language-support/scala/bindings", "//language-support/scala/bindings-akka", "//ledger-api/rs-grpc-bridge", diff --git a/triggers/tests/src/test/scala/com/digitalasset/daml/lf/engine/trigger/test/DevOnly.scala b/triggers/tests/src/test/scala/com/digitalasset/daml/lf/engine/trigger/test/DevOnly.scala index 23f0a4cd922..1908a90e77f 100644 --- a/triggers/tests/src/test/scala/com/digitalasset/daml/lf/engine/trigger/test/DevOnly.scala +++ b/triggers/tests/src/test/scala/com/digitalasset/daml/lf/engine/trigger/test/DevOnly.scala @@ -13,7 +13,6 @@ import org.scalatest._ import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AsyncWordSpec import com.daml.lf.engine.trigger.TriggerMsg -import com.daml.script.converter.ConverterException class DevOnly extends AsyncWordSpec @@ -107,10 +106,9 @@ class DevOnly } } - // TODO: modify Converter so that it may parse interface and view related transaction messages - "fail with interface registration and implementing template not registered" in { + "succeed with interface registration and implementing template not registered" in { val triggerId = QualifiedName.assertFromString("InterfaceTriggers:triggerWithRegistration") - val result = for { + for { client <- ledgerClient() party <- allocateParty(client) runner = getRunner(client, triggerId, party) @@ -163,10 +161,10 @@ class DevOnly // 1 for create of template A // 1 for create of template B, via interface I _ <- runner.runWithACS(acs, offset, msgFlow = Flow[TriggerMsg].take(2))._2 - } yield fail() - - result.recoverWith { case exn: ConverterException => - exn.getMessage should startWith("Failure to translate value: TypeMismatch") + acs <- queryACS(client, party) + } yield { + acs(templateA) should have length 1 + acs(templateB) should have length 1 } } }