Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add contract keys to created events in the Ledger API #1586

Merged
merged 12 commits into from Jun 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -2,13 +2,8 @@
// SPDX-License-Identifier: Apache-2.0

package com.digitalasset.daml.lf.engine
import com.digitalasset.daml.lf.transaction.Node.{
NodeCreate,
NodeExercises,
NodeFetch,
NodeLookupByKey
}
import com.digitalasset.daml.lf.data.Ref.{ChoiceName, Identifier, Party}
import com.digitalasset.daml.lf.transaction.Node._
import com.digitalasset.daml.lf.data.{FrontStack, FrontStackCons, ImmArray}
import com.digitalasset.daml.lf.transaction.GenTransaction
import com.digitalasset.daml.lf.data.Relation.Relation
Expand All @@ -29,20 +24,25 @@ sealed trait Event[+Nid, +Cid, +Val] extends Product with Serializable {
*
* @param contractId id for the contract this event notifies
* @param templateId identifier of the creating template
* @param contractKey key for the contract this event notifies
* @param argument argument of the contract creation
* @param stakeholders the stakeholders of the created contract -- must be a subset of witnesses. see comment for `collectEvents`
* @param witnesses additional witnesses induced by parent exercises
*/
final case class CreateEvent[Cid, Val](
contractId: Cid,
templateId: Identifier,
contractKey: Option[KeyWithMaintainers[Val]],
argument: Val,
agreementText: String,
stakeholders: Set[Party],
witnesses: Set[Party])
extends Event[Nothing, Cid, Val] {
override def mapContractId[Cid2, Val2](f: Cid => Cid2, g: Val => Val2): CreateEvent[Cid2, Val2] =
copy(contractId = f(contractId), argument = g(argument))
copy(
contractId = f(contractId),
argument = g(argument),
contractKey = contractKey.map(_.mapValue(g)))

override def mapNodeId[Nid2](f: Nothing => Nid2): CreateEvent[Cid, Val] = this
}
Expand Down Expand Up @@ -153,6 +153,7 @@ object Event {
CreateEvent(
nc.coid,
templateId,
nc.key,
nc.coinst.arg,
nc.coinst.agreementText,
stakeholders intersect disclosure(nodeId),
Expand Down
Expand Up @@ -945,6 +945,7 @@ class EngineTest extends WordSpec with Matchers with BazelRunfiles {
CreateEvent(
RelativeContractId(Tx.NodeId.unsafeFromIndex(1)),
Identifier(basicTestsPkgId, "BasicTests:CallablePayout"),
None,
assertAsVersionedValue(
ValueRecord(
Some(Identifier(basicTestsPkgId, "BasicTests:CallablePayout")),
Expand Down
Expand Up @@ -119,11 +119,14 @@ final case class Enum(constructors: ImmArraySeq[Ref.Name]) extends DataType[Noth
def asDataType[RT, PVT]: DataType[RT, PVT] = this
}

final case class DefTemplate[+Ty](choices: Map[Ref.Name, TemplateChoice[Ty]]) {
final case class DefTemplate[+Ty](choices: Map[Ref.Name, TemplateChoice[Ty]], key: Option[Type]) {
def map[B](f: Ty => B): DefTemplate[B] = Functor[DefTemplate].map(this)(f)

def getChoices: j.Map[Ref.ChoiceName, _ <: TemplateChoice[Ty]] =
choices.asJava

def getKey: j.Optional[Type] =
key.fold(j.Optional.empty[Type])(k => j.Optional.of(k))
}

object DefTemplate {
Expand Down
Expand Up @@ -26,7 +26,6 @@ import com.digitalasset.daml.lf.data.Ref.{
PackageId,
QualifiedName
}
import com.digitalasset.daml.lf.iface.TemplateChoice.FWT

import scala.collection.JavaConverters._
import scala.collection.immutable.Map
Expand Down Expand Up @@ -213,12 +212,16 @@ object InterfaceReader {
point(InvalidDataTypeDefinition(
s"Cannot find a record associated with template: $templateName")))
case Some((rec, newState)) =>
val y: Errors[ErrorLoc, InterfaceReaderError] \/ Map[ChoiceName, FWT] =
locate('choices, choices(a, ctx))

y.fold(
newState.addError, { cs =>
newState.addTemplate(templateName, rec, DefTemplate(cs))
val templateArgs =
for {
choices <- locate('choices, choices(a, ctx))
key <- locate('key, key(a, ctx))
} yield (choices, key)

templateArgs.fold(
newState.addError, {
case (cs, k) =>
newState.addTemplate(templateName, rec, DefTemplate(cs, k))
}
)
}
Expand Down Expand Up @@ -250,6 +253,13 @@ object InterfaceReader {
choice = TemplateChoice(p, consuming = a.getConsuming, returnType = r)
} yield choice

private def key(
a: DamlLf1.DefTemplate,
ctx: Context
): InterfaceReaderError.Tree \/ Option[Type] =
if (a.hasKey) locate('key, rootErr(type_(a.getKey.getType, ctx)).map(Some(_)))
else \/-(None)

private def fullName(
m: ModuleName,
a: DamlLf1.DottedName): InterfaceReaderError \/ QualifiedName =
Expand Down
Expand Up @@ -193,6 +193,7 @@ class SemanticTester(
val scenarioCreateEvent = CreateEvent(
nextScenarioCoidToLedgerCoid(scenarioCreateNode.coid),
scenarioCreateNode.coinst.template,
scenarioCreateNode.key,
scenarioCreateNode.coinst.arg.mapContractId(nextScenarioCoidToLedgerCoid),
scenarioCreateNode.coinst.agreementText,
scenarioCreateNode.stakeholders intersect scenarioWitnesses(scenarioNodeId),
Expand Down
Expand Up @@ -425,6 +425,10 @@ object Transaction {
*
* @param targetId Contract-id referencing the contract-instance on
* which we are exercising a choice.
* @param templateId Template-id referencing the template of the
* contract on which we are exercising a choice.
* @param contractKey Optional contract key, if defined for the
* contract on which we are exercising a choice.
* @param choiceId Label of the choice that we are exercising.
* @param consuming True if the choice consumes the contract.
* @param actingParties The parties exercising the choice.
Expand All @@ -441,6 +445,7 @@ object Transaction {
case class ExercisesContext(
targetId: TContractId,
templateId: TypeConName,
contractKey: Option[KeyWithMaintainers[AbsoluteContractId]],
choiceId: ChoiceName,
optLocation: Option[Location],
consuming: Boolean,
Expand Down Expand Up @@ -701,6 +706,7 @@ object Transaction {
ExercisesContext(
targetId = targetId,
templateId = templateId,
contractKey = None,
choiceId = choiceId,
optLocation = optLocation,
consuming = consuming,
Expand Down
18 changes: 18 additions & 0 deletions docs/source/support/release-notes.rst
Expand Up @@ -42,6 +42,24 @@ Scala bindings
- New `--root` command-line option for limiting what templates are selected for codegen.
See `#1210 <https://github.com/digital-asset/daml/pull/1210>`__.

Ledger API
~~~~~~~~~~

- Contract keys are now available for created events from the transaction service.
See `#1268 <https://github.com/digital-asset/daml/issues/1268>`__.

Java Bindings
~~~~~~~~~~~~~

- The addition of contract keys on created events in the Ledger API is reflected in the bindings.
See `#1268 <https://github.com/digital-asset/daml/issues/1268>`__.

Java Codegen
~~~~~~~~~~~~

- Contracts decoded from the transaction service now expose their contract key (if defined).
See `#1268 <https://github.com/digital-asset/daml/issues/1268>`__.

.. _release-0-12-24:

0.12.24 - 2019-06-06
Expand Down
Expand Up @@ -427,7 +427,9 @@ class BotTest extends FlatSpec with Matchers with DataLayerHelpers {
templateId,
s"cid_$id",
new Record(List.empty[Record.Field].asJava),
Optional.empty())
Optional.empty(),
Optional.empty()
)

def archive(event: CreatedEvent): ArchivedEvent =
new ArchivedEvent(
Expand Down
Expand Up @@ -52,7 +52,7 @@ class TransactionClientTest extends FlatSpec with GeneratorDrivenPropertyChecks
val begin = new data.LedgerOffset.Absolute("1")
val end = new data.LedgerOffset.Absolute("2")

val trasnactionFilter = new data.FiltersByParty(
val transactionFilter = new data.FiltersByParty(
Map[String, data.Filter](
"Alice" -> new data.InclusiveFilter(
Set(
Expand All @@ -63,7 +63,7 @@ class TransactionClientTest extends FlatSpec with GeneratorDrivenPropertyChecks
)

transactionClient
.getTransactions(begin, end, trasnactionFilter, true)
.getTransactions(begin, end, transactionFilter, true)
.toList()
.blockingGet()

Expand Down
Expand Up @@ -184,6 +184,7 @@ object TransactionGenerator {
eventId <- nonEmptyId
contractId <- nonEmptyId
agreementText <- Gen.option(Gen.asciiStr)
contractKey <- Gen.option(valueGen(0))
(scalaTemplateId, javaTemplateId) <- identifierGen
(scalaRecord, javaRecord) <- Gen.sized(recordGen)
parties <- Gen.listOf(nonEmptyId)
Expand All @@ -194,6 +195,7 @@ object TransactionGenerator {
eventId,
contractId,
Some(scalaTemplateId),
contractKey.map(_._1),
Some(scalaRecord),
parties,
agreementText = agreementText)),
Expand All @@ -203,7 +205,9 @@ object TransactionGenerator {
javaTemplateId,
contractId,
javaRecord,
agreementText.map(Optional.of[String]).getOrElse(Optional.empty()))
agreementText.map(Optional.of[String]).getOrElse(Optional.empty()),
contractKey.fold(Optional.empty[data.Value])(c => Optional.of[data.Value](c._2))
)
)

val archivedEventGen: Gen[(Archived, data.ArchivedEvent)] = for {
Expand Down
Expand Up @@ -25,13 +25,16 @@ public final class CreatedEvent implements Event, TreeEvent {

private final Optional<String> agreementText;

public CreatedEvent(@NonNull List<@NonNull String> witnessParties, @NonNull String eventId, @NonNull Identifier templateId, @NonNull String contractId, @NonNull Record arguments, Optional<String> agreementText) {
private final Optional<Value> contractKey;

public CreatedEvent(@NonNull List<@NonNull String> witnessParties, @NonNull String eventId, @NonNull Identifier templateId, @NonNull String contractId, @NonNull Record arguments, @NonNull Optional<String> agreementText, @NonNull Optional<Value> contractKey) {
this.witnessParties = witnessParties;
this.eventId = eventId;
this.templateId = templateId;
this.contractId = contractId;
this.arguments = arguments;
this.agreementText = agreementText;
this.contractKey = contractKey;
}

@NonNull
Expand Down Expand Up @@ -68,6 +71,9 @@ public Optional<String> getAgreementText() {
return agreementText;
}

@NonNull
public Optional<Value> getContractKey() { return contractKey; }

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand All @@ -78,13 +84,13 @@ public boolean equals(Object o) {
Objects.equals(templateId, that.templateId) &&
Objects.equals(contractId, that.contractId) &&
Objects.equals(arguments, that.arguments) &&
Objects.equals(agreementText, that.agreementText);
Objects.equals(agreementText, that.agreementText) &&
Objects.equals(contractKey, that.contractKey);
}

@Override
public int hashCode() {

return Objects.hash(witnessParties, eventId, templateId, contractId, arguments, agreementText);
return Objects.hash(witnessParties, eventId, templateId, contractId, arguments, agreementText, contractKey);
}

@Override
Expand All @@ -96,6 +102,7 @@ public String toString() {
", contractId='" + contractId + '\'' +
", arguments=" + arguments +
", agreementText='" + agreementText + '\'' +
", contractKey=" + contractKey +
'}';
}

Expand All @@ -107,6 +114,7 @@ public String toString() {
.setTemplateId(getTemplateId().toProto())
.addAllWitnessParties(this.getWitnessParties());
agreementText.ifPresent(a -> builder.setAgreementText(StringValue.of(a)));
contractKey.ifPresent(a -> builder.setContractKey(a.toProto()));
return builder.build();
}

Expand All @@ -117,7 +125,8 @@ public static CreatedEvent fromProto(EventOuterClass.CreatedEvent createdEvent)
Identifier.fromProto(createdEvent.getTemplateId()),
createdEvent.getContractId(),
Record.fromProto(createdEvent.getCreateArguments()),
createdEvent.hasAgreementText() ? Optional.of(createdEvent.getAgreementText().getValue()) : Optional.empty());
createdEvent.hasAgreementText() ? Optional.of(createdEvent.getAgreementText().getValue()) : Optional.empty(),
createdEvent.hasContractKey() ? Optional.of(Value.fromProto(createdEvent.getContractKey())) : Optional.empty());

}
}
Expand Up @@ -74,8 +74,8 @@ void contractHasFromIdAndRecord() {

@Test
void contractHasFromCreatedEvent() {
CreatedEvent agreementEvent = new CreatedEvent(Collections.emptyList(), "eventId", SimpleTemplate.TEMPLATE_ID, "cid", simpleTemplateRecord, Optional.of("I agree"));
CreatedEvent noAgreementEvent = new CreatedEvent(Collections.emptyList(), "eventId", SimpleTemplate.TEMPLATE_ID, "cid", simpleTemplateRecord, Optional.empty());
CreatedEvent agreementEvent = new CreatedEvent(Collections.emptyList(), "eventId", SimpleTemplate.TEMPLATE_ID, "cid", simpleTemplateRecord, Optional.of("I agree"), Optional.empty());
CreatedEvent noAgreementEvent = new CreatedEvent(Collections.emptyList(), "eventId", SimpleTemplate.TEMPLATE_ID, "cid", simpleTemplateRecord, Optional.empty(), Optional.empty());

SimpleTemplate.Contract withAgreement = SimpleTemplate.Contract.fromCreatedEvent(agreementEvent);
assertTrue(withAgreement.agreementText.isPresent(), "AgreementText was not present");
Expand Down
Expand Up @@ -14,6 +14,8 @@ data Color = Grey
| Custom Text
deriving (Eq, Show, Ord)

data WolpertingerKey = WolpertingerKey with owner: Party; age: Decimal

template Wolpertinger
with
owner : Party
Expand All @@ -28,6 +30,9 @@ template Wolpertinger
where
signatory owner

key WolpertingerKey owner age : WolpertingerKey
maintainer key.owner

agreement name <> " has " <> show wings <> " wings and is " <> show age <> " years old."

controller owner can
Expand Down
Expand Up @@ -32,7 +32,6 @@ import com.digitalasset.ledger.api.v1.TransactionServiceOuterClass.{
GetTransactionsResponse
}
import com.digitalasset.ledger.api.v1.{CommandServiceGrpc, TransactionServiceGrpc}

import com.digitalasset.platform.common.LedgerIdMode
import com.digitalasset.platform.sandbox.config.{DamlPackageContainer, SandboxConfig}
import com.digitalasset.platform.sandbox.services.SandboxServerResource
Expand Down Expand Up @@ -217,4 +216,14 @@ class CodegenLedgerTest extends FlatSpec with Matchers with BazelRunfiles {
wolpertinger.agreementText.isPresent shouldBe true
wolpertinger.agreementText.get shouldBe s"${wolpertinger.data.name} has ${wolpertinger.data.wings} wings and is ${wolpertinger.data.age} years old."
}

it should "provide the key" in withClient { client =>
sendCmd(client, glookofly.create())

val wolpertinger :: _ = readActiveContracts(client)

wolpertinger.key.isPresent shouldBe true
wolpertinger.key.get.owner shouldEqual "Alice"
wolpertinger.key.get.age shouldEqual java.math.BigDecimal.valueOf(17.42)
}
}