Skip to content

Commit

Permalink
Fix disclosure bug in the presence of upgrades (#19167)
Browse files Browse the repository at this point in the history
* repro of #19162 as a test and fix for daml3

* use a different party for exercising a choice on the disclosed contract

* factorize TemplateTypeRep svalue creation

* Move makePair/makeTriplet to Converter.makeTuple
  • Loading branch information
paulbrauner-da authored May 14, 2024
1 parent c1ced1f commit 3145345
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 93 deletions.
1 change: 1 addition & 0 deletions sdk/daml-script/converter/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ da_scala_library(
"//daml-lf/data",
"//daml-lf/interpreter",
"//daml-lf/language",
"//daml-lf/stable-packages",
"//daml-lf/transaction",
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@

package com.daml.script.converter

import java.util
import scala.jdk.CollectionConverters._
import scalaz.syntax.bind._
import scalaz.std.either._
import com.daml.lf.data.{ImmArray, Ref}
import Ref._
import com.daml.lf.data.ImmArray
import com.daml.lf.data.Ref._
import com.daml.lf.language.Ast
import com.daml.lf.speedy.SValue._
import com.daml.lf.speedy.{ArrayList, SValue}
import SValue._
import com.daml.lf.stablepackages.StablePackagesV2
import com.daml.lf.value.Value.ContractId
import scalaz.std.either._
import scalaz.syntax.bind._

import java.util
import scala.jdk.CollectionConverters._

class ConverterException(message: String) extends RuntimeException(message)

Expand All @@ -35,6 +37,12 @@ private[daml] object Converter {
SRecord(ty, fieldNames, args) // TODO: construct SRecord directly from Map
}

def makeTuple(v1: SValue, v2: SValue): SValue =
record(StablePackagesV2.Tuple2, ("_1", v1), ("_2", v2))

def makeTuple(v1: SValue, v2: SValue, v3: SValue): SValue =
record(StablePackagesV2.Tuple3, ("_1", v1), ("_2", v2), ("_3", v3))

/** Unpack one step of a Pure/Roll-style free monad representation,
* with the assumption that `f` is a variant type.
*/
Expand Down
4 changes: 2 additions & 2 deletions sdk/daml-script/daml/Daml/Script.daml
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,7 @@ data QueryDisclosurePayload a = QueryDisclosurePayload
{ parties : [Party]
, tplId : TemplateTypeRep
, cid : ContractId ()
, continue : Optional Text -> a
, continue : Optional (TemplateTypeRep, Text) -> a
, locations : [(Text, SrcLoc)]
} deriving Functor

Expand All @@ -317,7 +317,7 @@ queryDisclosure p c = lift $ Free $ QueryDisclosure QueryDisclosurePayload with
parties = toParties p
tplId = templateTypeRep @t
cid = cid
continue = pure . fmap \blob -> Disclosure tplId cid blob
continue = pure . fmap \(actualTplId, blob) -> Disclosure actualTplId cid blob
locations = getCallStack callStack
where
tplId = templateTypeRep @t
Expand Down
13 changes: 8 additions & 5 deletions sdk/daml-script/daml3/Daml/Script/Internal/Questions/Query.daml
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,23 @@ data QueryContractId = QueryContractId with
parties : [Party]
tplId : TemplateTypeRep
cid : ContractId ()
instance IsQuestion QueryContractId (Optional (AnyTemplate, Text)) where command = "QueryContractId"
instance IsQuestion QueryContractId (Optional (AnyTemplate, TemplateTypeRep, Text)) where command = "QueryContractId"

-- | Query for the contract with the given contract id.
--
-- Returns `None` if there is no active contract the party is a stakeholder on.
-- Otherwise returns a triplet (anyTemplate, templateId, blob) where anyTemplate
-- is the contract upgraded or downgraded to `t`, templateId is the ID of the
-- template as stored in the ledger (may be different from `t`), and blob is the
-- disclosure of the template as stored in the ledger (of type templateId).
--
-- WARNING: Over the gRPC and with the JSON API
-- in-memory backend this performs a linear search so only use this if the number of
-- active contracts is small.
--
-- This is semantically equivalent to calling `query`
-- and filtering on the client side.
queryContractId_ : forall t p. (Template t, IsParties p) => HasCallStack => p -> ContractId t -> Script (Optional (AnyTemplate, Text))
queryContractId_ : forall t p. (Template t, IsParties p) => HasCallStack => p -> ContractId t -> Script (Optional (AnyTemplate, TemplateTypeRep, Text))
queryContractId_ p c = lift $ QueryContractId with
parties = toParties p
tplId = templateTypeRep @t
Expand All @@ -58,14 +62,13 @@ queryContractId_ p c = lift $ QueryContractId with
-- convert = fmap $ fromSome . fromAnyTemplate

queryContractId: forall t p. (Template t, HasEnsure t, IsParties p) => HasCallStack => p -> ContractId t -> Script (Optional t)
queryContractId p c = fmap (fmap $ fromSome . fromAnyTemplate . fst) $ queryContractId_ p c
queryContractId p c = fmap (fmap $ \(anyTpl, _, _) -> fromSome (fromAnyTemplate anyTpl)) $ queryContractId_ p c

-- TODO https://github.com/digital-asset/daml/issues/17755
-- clean the API for different query function
queryDisclosure: forall t p. (Template t, IsParties p) => HasCallStack => p -> ContractId t -> Script (Optional Disclosure)
queryDisclosure p c = fmap (fmap $ \(_, blob) -> Disclosure tplId cid blob) $ queryContractId_ p c
queryDisclosure p c = fmap (fmap $ \(_, tplId, blob) -> Disclosure tplId cid blob) $ queryContractId_ p c
where
tplId = templateTypeRep @t
cid = coerceContractId c

data QueryInterface = QueryInterface with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,14 @@ abstract class ConverterMethods(stablePackages: StablePackages) {
)
}

private[lf] def fromTemplateTypeRep(templateId: SValue): SValue =
record(stablePackages.TemplateTypeRep, ("getTemplateTypeRep", templateId))

private[lf] def fromTemplateTypeRep(templateId: value.Identifier): SValue =
record(stablePackages.TemplateTypeRep, ("getTemplateTypeRep", fromIdentifier(templateId)))
fromTemplateTypeRep(fromIdentifier(templateId))

private[lf] def fromTemplateTypeRep(templateId: Identifier): SValue =
fromTemplateTypeRep(STypeRep(TTyCon(templateId)))

private[lf] def fromAnyContractId(
scriptIds: ScriptIds,
Expand Down Expand Up @@ -246,10 +252,7 @@ abstract class ConverterMethods(stablePackages: StablePackages) {
("getAnyContractKey", SAny(key.ty, key.key)),
(
"getAnyContractKeyTemplateTypeRep",
record(
stablePackages.TemplateTypeRep,
("getTemplateTypeRep", STypeRep(TTyCon(key.templateId))),
),
fromTemplateTypeRep(key.templateId),
),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,9 @@ object Converter extends script.ConverterMethods(StablePackagesV2) {
): Either[String, SValue] = {
for {
anyTpl <- fromContract(translator, contract)
} yield record(
StablePackagesV2.Tuple2,
("_1", SContractId(contract.contractId)),
("_2", anyTpl),
} yield makeTuple(
SContractId(contract.contractId),
anyTpl,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,29 @@ package engine
package script
package v1

import java.time.Clock
import org.apache.pekko.stream.Materializer
import com.daml.grpc.adapter.ExecutionSequencerFactory
import com.digitalasset.canton.ledger.api.domain.{User, UserRight}
import com.daml.lf.data.FrontStack
import com.daml.lf.{CompiledPackages, command}
import com.daml.lf.engine.preprocessing.ValueTranslator
import com.daml.lf.data.Ref.{Identifier, Name, PackageId, Party, UserId}
import com.daml.lf.data.Ref._
import com.daml.lf.data.Time.Timestamp
import com.daml.lf.language.{Ast}
import com.daml.lf.speedy.SExpr.{SEAppAtomic, SEValue}
import com.daml.lf.speedy.{ArrayList, SError, SValue}
import com.daml.lf.speedy.SExpr.SExpr
import com.daml.lf.engine.preprocessing.ValueTranslator
import com.daml.lf.language.Ast
import com.daml.lf.speedy.SExpr.{SEAppAtomic, SEValue, SExpr}
import com.daml.lf.speedy.SValue._
import com.daml.lf.speedy.Speedy.PureMachine
import com.daml.lf.stablepackages.StablePackagesV2
import com.daml.lf.speedy.{ArrayList, SError, SValue}
import com.daml.lf.value.Value
import com.daml.lf.value.Value.ContractId
import scalaz.{Foldable, OneAnd}
import scalaz.syntax.traverse._
import com.daml.lf.{CompiledPackages, command}
import com.daml.script.converter.Converter.{makeTuple, toContractId, toText}
import com.digitalasset.canton.ledger.api.domain.{User, UserRight}
import org.apache.pekko.stream.Materializer
import scalaz.std.either._
import scalaz.std.list._
import scalaz.std.option._
import com.daml.script.converter.Converter.{toContractId, toText}
import scalaz.syntax.traverse._
import scalaz.{Foldable, OneAnd}

import java.time.Clock
import scala.concurrent.{ExecutionContext, Future}

sealed trait ScriptF
Expand Down Expand Up @@ -231,6 +229,7 @@ object ScriptF {
} yield SEAppAtomic(SEValue(continue), Array(SEValue(SList(res))))

}

final case class QueryContractId(
parties: OneAnd[Set, Party],
tplId: Identifier,
Expand All @@ -251,7 +250,14 @@ object ScriptF {
optR <- client.queryContractId(parties, tplId, cid)
optR <- Converter.toFuture(
if (asDisclosure)
Right(optR.map(c => SValue.SText(c.blob.toHexString)))
Right(
optR.map(c =>
makeTuple(
Converter.fromTemplateTypeRep(c.templateId),
SValue.SText(c.blob.toHexString),
)
)
)
else
optR.traverse(Converter.fromContract(env.valueTranslator, _))
)
Expand All @@ -272,11 +278,6 @@ object ScriptF {
esf: ExecutionSequencerFactory,
): Future[SExpr] = {

def makePair(v1: SValue, v2: SValue): SValue = {
import com.daml.script.converter.Converter.record
record(StablePackagesV2.Tuple2, ("_1", v1), ("_2", v2))
}

for {
viewType <- Converter.toFuture(env.lookupInterfaceViewTy(interfaceId))
client <- Converter.toFuture(env.clients.getPartiesParticipant(parties))
Expand All @@ -287,7 +288,7 @@ object ScriptF {
.traverse { case (cid, optView) =>
optView match {
case None =>
Right(makePair(SContractId(cid), SOptional(None)))
Right(makeTuple(SContractId(cid), SOptional(None)))
case Some(view) =>
for {
view <- Converter.fromInterfaceView(
Expand All @@ -296,7 +297,7 @@ object ScriptF {
view,
)
} yield {
makePair(SContractId(cid), SOptional(Some(view)))
makeTuple(SContractId(cid), SOptional(Some(view)))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,26 +221,23 @@ object Converter extends script.ConverterMethods(StablePackagesV2) {
)
.map { xs => SList(xs.to(FrontStack)) }

// Convert an active contract to AnyTemplate
def fromContract(
translator: preprocessing.ValueTranslator,
contract: ScriptLedgerClient.ActiveContract,
enableContractUpgrading: Boolean = false,
): Either[String, SValue] =
fromAnyTemplate(translator, contract.templateId, contract.argument, enableContractUpgrading)

// Convert a Created event to a pair of (ContractId (), AnyTemplate)
def fromCreated(
translator: preprocessing.ValueTranslator,
contract: ScriptLedgerClient.ActiveContract,
targetTemplateId: Identifier,
enableContractUpgrading: Boolean = false,
): Either[String, SValue] = {
for {
anyTpl <- fromContract(translator, contract, enableContractUpgrading)
} yield record(
StablePackagesV2.Tuple2,
("_1", SContractId(contract.contractId)),
("_2", anyTpl),
anyTpl <- fromAnyTemplate(
translator,
targetTemplateId,
contract.argument,
enableContractUpgrading,
)
} yield makeTuple(
SContractId(contract.contractId),
anyTpl,
)
}

Expand Down
Loading

0 comments on commit 3145345

Please sign in to comment.