Skip to content

Commit

Permalink
implement queryView for GrpcLedgerClient (digital-asset#15406)
Browse files Browse the repository at this point in the history
  • Loading branch information
nickchapman-da authored Nov 3, 2022
1 parent 43fafb8 commit c9db43f
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -51,24 +58,40 @@ 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 = {
val filters = Filters(Some(InclusiveFilters(Seq(toApiIdentifier(templateId)))))
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)
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand Down
112 changes: 112 additions & 0 deletions daml-script/test/daml/TestInterfaces.daml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module TestInterfaces where

import Daml.Script
import DA.Assert
import DA.List (sortOn)

data EmptyInterfaceView = EmptyInterfaceView {}

Expand Down Expand Up @@ -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 ()
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit c9db43f

Please sign in to comment.