From c9db43f579e05e58e867920d374dec18bf2f5d2b Mon Sep 17 00:00:00 2001 From: nickchapman-da <49153372+nickchapman-da@users.noreply.github.com> Date: Thu, 3 Nov 2022 17:41:07 +0000 Subject: [PATCH] implement queryView for GrpcLedgerClient (#15406) --- .../ledgerinteraction/GrpcLedgerClient.scala | 76 ++++++++++-- daml-script/test/daml/TestInterfaces.daml | 112 ++++++++++++++++++ .../engine/script/test/AbstractFuncIT.scala | 14 +++ 3 files changed, 192 insertions(+), 10 deletions(-) diff --git a/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/ledgerinteraction/GrpcLedgerClient.scala b/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/ledgerinteraction/GrpcLedgerClient.scala index de06248b5b44..68d391904d28 100644 --- a/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/ledgerinteraction/GrpcLedgerClient.scala +++ b/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/ledgerinteraction/GrpcLedgerClient.scala @@ -12,12 +12,19 @@ import com.daml.grpc.adapter.ExecutionSequencerFactory import com.daml.grpc.adapter.client.akka.ClientAdapter import com.daml.ledger.api.domain.{User, UserRight} import com.daml.ledger.api.refinements.ApiTypes.ApplicationId +import com.daml.ledger.api.v1.active_contracts_service.GetActiveContractsResponse import com.daml.ledger.api.v1.command_service.SubmitAndWaitRequest import com.daml.ledger.api.v1.commands._ +import com.daml.ledger.api.v1.event.{CreatedEvent, InterfaceView} import com.daml.ledger.api.v1.testing.time_service.TimeServiceGrpc.TimeServiceStub import com.daml.ledger.api.v1.testing.time_service.{GetTimeRequest, SetTimeRequest, TimeServiceGrpc} import com.daml.ledger.api.v1.transaction.TreeEvent -import com.daml.ledger.api.v1.transaction_filter.{Filters, InclusiveFilters, TransactionFilter} +import com.daml.ledger.api.v1.transaction_filter.{ + Filters, + InclusiveFilters, + InterfaceFilter, + TransactionFilter, +} import com.daml.ledger.api.validation.NoLoggingValueValidator import com.daml.ledger.client.LedgerClient import com.daml.lf.command @@ -51,11 +58,11 @@ class GrpcLedgerClient(val grpcClient: LedgerClient, val applicationId: Applicat override def query(parties: OneAnd[Set, Ref.Party], templateId: Identifier)(implicit ec: ExecutionContext, mat: Materializer, - ) = { + ): Future[Vector[ScriptLedgerClient.ActiveContract]] = { queryWithKey(parties, templateId).map(_.map(_._1)) } - private def transactionFilter( + private def templateFilter( parties: OneAnd[Set, Ref.Party], templateId: Identifier, ): TransactionFilter = { @@ -63,12 +70,28 @@ class GrpcLedgerClient(val grpcClient: LedgerClient, val applicationId: Applicat TransactionFilter(parties.toList.map(p => (p, filters)).toMap) } + private def interfaceFilter( + parties: OneAnd[Set, Ref.Party], + interfaceId: Identifier, + ): TransactionFilter = { + val filters = + Filters( + Some( + InclusiveFilters( + List(), + List(InterfaceFilter(Some(toApiIdentifier(interfaceId)), true)), + ) + ) + ) + TransactionFilter(parties.toList.map(p => (p, filters)).toMap) + } + // Helper shared by query, queryContractId and queryContractKey private def queryWithKey(parties: OneAnd[Set, Ref.Party], templateId: Identifier)(implicit ec: ExecutionContext, mat: Materializer, ): Future[Vector[(ScriptLedgerClient.ActiveContract, Option[Value])]] = { - val filter = transactionFilter(parties, templateId) + val filter = templateFilter(parties, templateId) val acsResponses = grpcClient.activeContractSetClient .getActiveContracts(filter, verbose = false) @@ -116,11 +139,38 @@ class GrpcLedgerClient(val grpcClient: LedgerClient, val applicationId: Applicat } } - override def queryView( - parties: OneAnd[Set, Ref.Party], - interfaceId: Identifier, - )(implicit ec: ExecutionContext, mat: Materializer): Future[Seq[(ContractId, Option[Value])]] = { - sys.error("not implemented") // TODO https://github.com/digital-asset/daml/issues/14830 + override def queryView(parties: OneAnd[Set, Ref.Party], interfaceId: Identifier)(implicit + ec: ExecutionContext, + mat: Materializer, + ): Future[Seq[(ContractId, Option[Value])]] = { + val filter = interfaceFilter(parties, interfaceId) + val acsResponses = + grpcClient.activeContractSetClient + .getActiveContracts(filter, verbose = false) + .runWith(Sink.seq) + acsResponses.map { acsPages: Seq[GetActiveContractsResponse] => + acsPages.toVector.flatMap { page: GetActiveContractsResponse => + page.activeContracts.toVector.flatMap { createdEvent: CreatedEvent => + val cid = + ContractId + .fromString(createdEvent.contractId) + .fold( + err => throw new ConverterException(err), + identity, + ) + createdEvent.interfaceViews.map { iv: InterfaceView => + val viewValue: Value.ValueRecord = + NoLoggingValueValidator.validateRecord(iv.getViewValue) match { + case Left(err) => throw new ConverterException(err.toString) + case Right(argument) => argument + } + // Because we filter for a specific interfaceId, + // we will get at most one view for a given cid. + (cid, if (viewValue.fields.isEmpty) None else Some(viewValue)) + } + } + } + } } override def queryViewContractId( @@ -131,7 +181,13 @@ class GrpcLedgerClient(val grpcClient: LedgerClient, val applicationId: Applicat ec: ExecutionContext, mat: Materializer, ): Future[Option[Value]] = { - sys.error("not implemented") // TODO https://github.com/digital-asset/daml/issues/14830 + for { + activeViews <- queryView(parties, interfaceId) + } yield { + activeViews.collectFirst { + case (k, Some(v)) if (k == cid) => v + } + } } // TODO (MK) https://github.com/digital-asset/daml/issues/11737 diff --git a/daml-script/test/daml/TestInterfaces.daml b/daml-script/test/daml/TestInterfaces.daml index 07d431956dac..cb1727101c52 100644 --- a/daml-script/test/daml/TestInterfaces.daml +++ b/daml-script/test/daml/TestInterfaces.daml @@ -5,6 +5,7 @@ module TestInterfaces where import Daml.Script import DA.Assert +import DA.List (sortOn) data EmptyInterfaceView = EmptyInterfaceView {} @@ -84,3 +85,114 @@ test = script do asset2 === Asset with {issuer = p, owner = p, amount = 10} asset3 === Asset with {issuer = p, owner = p, amount = 5} pure () + + +-- Tests for queryView/queryViewContractId + +-- Two interfaces (1,2)... +interface MyInterface1 where + viewtype MyView1 +data MyView1 = MyView1 { info : Int } deriving (Eq,Ord) + +interface MyInterface2 where + viewtype MyView2 +data MyView2 = MyView2 { info : Text } deriving (Eq,Ord) + +-- ...which are variously implemented by three templates (A,B,C) +template MyTemplateA + with + p : Party + v : Int + where + signatory p + interface instance MyInterface1 for MyTemplateA where + view = MyView1 { info = 100 + v } + +template MyTemplateB -- Note: B implements both interfaces! + with + p : Party + v : Int + where + signatory p + interface instance MyInterface1 for MyTemplateB where + view = MyView1 { info = 200 + v } + interface instance MyInterface2 for MyTemplateB where + view = MyView2 { info = "B:" <> show v } + +template MyTemplateC + with + p : Party + text : Text + isError : Bool + where + signatory p + interface instance MyInterface2 for MyTemplateC where + view = (if isError then error else MyView2) ("C:" <> text) + + +test_queryView : Script () +test_queryView = script do + + p <- allocateParty "Alice" -- primary party in the test script + p2 <- allocateParty "Bob" -- other/different party + + -- Create various contract-instances of A,B,C (for p) + a1 <- submit p do createCmd (MyTemplateA p 42) + a2 <- submit p do createCmd (MyTemplateA p 43) + b1 <- submit p do createCmd (MyTemplateB p 44) + c1 <- submit p do createCmd (MyTemplateC p "I-am-c1" False) + c2 <- submit p do createCmd (MyTemplateC p "I-am-c2" True) + + -- Archived contracts wont be visible when querying + a3 <- submit p do createCmd (MyTemplateA p 999) + submit p do archiveCmd a3 + + -- Contracts created by another party (p2) wont be visible when querying (by p) + a4 <- submit p2 do createCmd (MyTemplateA p2 911) + + -- Refer to p's instances via interface-contract-ids + -- (Note: we can refer to b1 via either Interface 1 or 2) + let i1a1 = toInterfaceContractId @MyInterface1 a1 + let i1a2 = toInterfaceContractId @MyInterface1 a2 + let i1a3 = toInterfaceContractId @MyInterface1 a3 + let i1a4 = toInterfaceContractId @MyInterface1 a4 + let i1b1 = toInterfaceContractId @MyInterface1 b1 + let i2b1 = toInterfaceContractId @MyInterface2 b1 + let i2c1 = toInterfaceContractId @MyInterface2 c1 + let i2c2 = toInterfaceContractId @MyInterface2 c2 + + -- Test queryViewContractId (Interface1) + Some v <- queryViewContractId p i1a1 + v.info === 142 + Some v <- queryViewContractId p i1a2 + v.info === 143 + Some v <- queryViewContractId p i1b1 + v.info === 244 + None <- queryViewContractId p i1a3 -- contract is archived + None <- queryViewContractId p i1a4 -- not a stakeholder + + -- Test queryViewContractId (Interface2) + Some v <- queryViewContractId p i2b1 + v.info === "B:44" + Some v <- queryViewContractId p i2c1 + v.info === "C:I-am-c1" + None <- queryViewContractId p i2c2 -- view function failed + + -- Test queryView (Interface1) + [(i1,Some v1),(i2,Some v2),(i3,Some v3)] <- sortOn snd <$> queryView @MyInterface1 p + i1 === i1a1 + i2 === i1a2 + i3 === i1b1 + v1.info === 142 + v2.info === 143 + v3.info === 244 + + -- Test queryView (Interface2) + [(i1,None),(i2,Some v2),(i3,Some v3)] <- sortOn snd <$> queryView @MyInterface2 p + i1 === i2c2 -- view function failed, so no info + i2 === i2b1 + i3 === i2c1 + v2.info === "B:44" + v3.info === "C:I-am-c1" + + pure () diff --git a/daml-script/test/src/test-utils/com/daml/lf/engine/script/test/AbstractFuncIT.scala b/daml-script/test/src/test-utils/com/daml/lf/engine/script/test/AbstractFuncIT.scala index 0dc7a6b44ae8..29aba3e2e03e 100644 --- a/daml-script/test/src/test-utils/com/daml/lf/engine/script/test/AbstractFuncIT.scala +++ b/daml-script/test/src/test-utils/com/daml/lf/engine/script/test/AbstractFuncIT.scala @@ -328,6 +328,20 @@ abstract class AbstractFuncIT } } } + "Interface:test_queryView" should { + "succeed" in { + for { + clients <- participantClients() + v <- run( + clients, + QualifiedName.assertFromString("TestInterfaces:test_queryView"), + dar = devDar, + ) + } yield { + v shouldBe (SUnit) + } + } + } "Interface:test" should { "succeed" in { for {