Skip to content

Commit

Permalink
Expose a contract's agreement text on the Ledger API
Browse files Browse the repository at this point in the history
Fixes #1110
  • Loading branch information
gerolf-da committed May 17, 2019
1 parent 44c6754 commit 2b00186
Show file tree
Hide file tree
Showing 49 changed files with 294 additions and 116 deletions.
Expand Up @@ -37,6 +37,7 @@ final case class CreateEvent[Cid, Val](
contractId: Cid,
templateId: Identifier,
argument: Val,
agreementText: String,
stakeholders: Set[Party],
witnesses: Set[Party])
extends Event[Nothing, Cid, Val] {
Expand Down Expand Up @@ -153,6 +154,7 @@ object Event {
nc.coid,
templateId,
nc.coinst.arg,
nc.coinst.agreementText,
stakeholders intersect disclosure(nodeId),
disclosure(nodeId))
evts += (nodeId -> evt)
Expand Down
Expand Up @@ -953,6 +953,7 @@ class EngineTest extends WordSpec with Matchers with BazelRunfiles {
(Some[Name]("giver"), ValueParty("Alice")),
(Some[Name]("receiver"), ValueParty("Clara")))
)),
"",
Set("Clara", "Alice"),
Set("Bob", "Clara", "Alice"),
)
Expand Down
Expand Up @@ -194,6 +194,7 @@ class SemanticTester(
nextScenarioCoidToLedgerCoid(scenarioCreateNode.coid),
scenarioCreateNode.coinst.template,
scenarioCreateNode.coinst.arg.mapContractId(nextScenarioCoidToLedgerCoid),
scenarioCreateNode.coinst.agreementText,
scenarioCreateNode.stakeholders intersect scenarioWitnesses(scenarioNodeId),
scenarioWitnesses(scenarioNodeId),
)
Expand Down
6 changes: 3 additions & 3 deletions docs/source/app-dev/bindings-java/codegen.rst
Expand Up @@ -60,7 +60,7 @@ To avoid possible name clashes in the generated Java sources, you should specify
Generate the decoder utility class
----------------------------------

When reading transactions from the ledger, you typically want to convert a `CreatedEvent <https://docs.daml.com/app-dev/bindings-java/javadocs/com/daml/ledger/javaapi/data/CreatedEvent.html>`__ from the Ledger API to the corresponding generated ``Contract`` class. The Java codegen can optionally generate a decoder class based on the input DAR files that calls the ``fromIdAndRecord`` method of the respective generated ``Contract`` class (see :ref:`daml-codegen-java-templates`). The decoder class can do this for all templates in the input DAR files.
When reading transactions from the ledger, you typically want to convert a `CreatedEvent <https://docs.daml.com/app-dev/bindings-java/javadocs/com/daml/ledger/javaapi/data/CreatedEvent.html>`__ from the Ledger API to the corresponding generated ``Contract`` class. The Java codegen can optionally generate a decoder class based on the input DAR files that calls the ``fromCreatedEvent`` method of the respective generated ``Contract`` class (see :ref:`daml-codegen-java-templates`). The decoder class can do this for all templates in the input DAR files.

To generate such a decoder class, provide the command line parameter ``-d`` or ``--decoderClass`` with a fully qualified class name:

Expand Down Expand Up @@ -254,7 +254,7 @@ The Java codegen generates three classes for a DAML template:
.. TODO: refer to another section explaining exactly that, when we have it.
**TemplateName.Contract**
Represents an actual contract on the ledger. It contains a field for the contract ID (of type ``TemplateName.ContractId``) and a field for the template data (of type ``TemplateName``). With the static method ``TemplateName.Contract.fromIdAndRecord``, you can deserialize a `CreatedEvent <https://docs.daml.com/app-dev/bindings-java/javadocs/com/daml/ledger/javaapi/data/CreatedEvent.html>`__ to an instance of ``TemplateName.Contract``.
Represents an actual contract on the ledger. It contains a field for the contract ID (of type ``TemplateName.ContractId``) and a field for the template data (of type ``TemplateName``). With the static method ``TemplateName.Contract.fromCreatedEvent``, you can deserialize a `CreatedEvent <https://docs.daml.com/app-dev/bindings-java/javadocs/com/daml/ledger/javaapi/data/CreatedEvent.html>`__ to an instance of ``TemplateName.Contract``.


.. literalinclude:: ./code-snippets/Templates.daml
Expand Down Expand Up @@ -300,7 +300,7 @@ A file is generated that defines three Java classes:
public final ContractId id;
public final Bar data;
public static Contract fromIdAndRecord(String contractId, Record record) { /* ... */ }
public static Contract fromCreatedEvent(CreatedEvent event) { /* ... */ }
}
}
Expand Down
Expand Up @@ -70,7 +70,7 @@ public static void main(String[] args) {
.blockingForEach(response -> {
response.getOffset().ifPresent(offset -> acsOffset.set(new LedgerOffset.Absolute(offset)));
response.getCreatedEvents().stream()
.map(e -> Iou.Contract.fromIdAndRecord(e.getContractId(), e.getArguments()))
.map(Iou.Contract::fromCreatedEvent)
.forEach(contract -> {
long id = idCounter.getAndIncrement();
contracts.put(id, contract.data);
Expand All @@ -85,7 +85,7 @@ public static void main(String[] args) {
if (event instanceof CreatedEvent) {
CreatedEvent createdEvent = (CreatedEvent) event;
long id = idCounter.getAndIncrement();
Iou.Contract contract = Iou.Contract.fromIdAndRecord(createdEvent.getContractId(), createdEvent.getArguments());
Iou.Contract contract = Iou.Contract.fromCreatedEvent(createdEvent);
contracts.put(id, contract.data);
idMap.put(id, contract.id);
} else if (event instanceof ArchivedEvent) {
Expand Down
Expand Up @@ -426,7 +426,8 @@ class BotTest extends FlatSpec with Matchers with DataLayerHelpers {
s"eid_$id",
templateId,
s"cid_$id",
new Record(List.empty[Record.Field].asJava))
new Record(List.empty[Record.Field].asJava),
Optional.empty())

def archive(event: CreatedEvent): ArchivedEvent =
new ArchivedEvent(
Expand Down
Expand Up @@ -5,7 +5,7 @@ package com.daml.ledger.rxjava.grpc.helpers

import java.time.Instant
import java.util
import java.util.Collections
import java.util.{Collections, Optional}

import com.daml.ledger.javaapi.data
import com.daml.ledger.testkit.services.TransactionServiceImpl
Expand Down Expand Up @@ -183,13 +183,27 @@ object TransactionGenerator {
val createdEventGen: Gen[(Created, data.CreatedEvent)] = for {
eventId <- nonEmptyId
contractId <- nonEmptyId
agreementText <- Gen.option(Gen.asciiStr)
(scalaTemplateId, javaTemplateId) <- identifierGen
(scalaRecord, javaRecord) <- Gen.sized(recordGen)
parties <- Gen.listOf(nonEmptyId)
} yield
(
Created(CreatedEvent(eventId, contractId, Some(scalaTemplateId), Some(scalaRecord), parties)),
new data.CreatedEvent(parties.asJava, eventId, javaTemplateId, contractId, javaRecord)
Created(
CreatedEvent(
eventId,
contractId,
Some(scalaTemplateId),
Some(scalaRecord),
parties,
agreementText = agreementText)),
new data.CreatedEvent(
parties.asJava,
eventId,
javaTemplateId,
contractId,
javaRecord,
agreementText.map(Optional.of[String]).getOrElse(Optional.empty()))
)

val archivedEventGen: Gen[(Archived, data.ArchivedEvent)] = for {
Expand Down
Expand Up @@ -4,10 +4,12 @@
package com.daml.ledger.javaapi.data;

import com.digitalasset.ledger.api.v1.EventOuterClass;
import com.google.protobuf.StringValue;
import org.checkerframework.checker.nullness.qual.NonNull;

import java.util.List;
import java.util.Objects;
import java.util.Optional;

public final class CreatedEvent implements Event, TreeEvent {

Expand All @@ -21,12 +23,15 @@ public final class CreatedEvent implements Event, TreeEvent {

private final Record arguments;

public CreatedEvent(@NonNull List<@NonNull String> witnessParties, @NonNull String eventId, @NonNull Identifier templateId, @NonNull String contractId, @NonNull Record arguments) {
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) {
this.witnessParties = witnessParties;
this.eventId = eventId;
this.templateId = templateId;
this.contractId = contractId;
this.arguments = arguments;
this.agreementText = agreementText;
}

@NonNull
Expand Down Expand Up @@ -58,6 +63,11 @@ public Record getArguments() {
return arguments;
}

@NonNull
public Optional<String> getAgreementText() {
return agreementText;
}

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

@Override
public int hashCode() {

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

@Override
Expand All @@ -84,17 +95,19 @@ public String toString() {
", templateId=" + templateId +
", contractId='" + contractId + '\'' +
", arguments=" + arguments +
", agreementText='" + agreementText + '\'' +
'}';
}

public EventOuterClass.@NonNull CreatedEvent toProto() {
return EventOuterClass.CreatedEvent.newBuilder()
EventOuterClass.CreatedEvent.Builder builder = EventOuterClass.CreatedEvent.newBuilder()
.setContractId(getContractId())
.setCreateArguments(getArguments().toProtoRecord())
.setEventId(getEventId())
.setTemplateId(getTemplateId().toProto())
.addAllWitnessParties(this.getWitnessParties())
.build();
.addAllWitnessParties(this.getWitnessParties());
agreementText.ifPresent(a -> builder.setAgreementText(StringValue.of(a)));
return builder.build();
}

public static CreatedEvent fromProto(EventOuterClass.CreatedEvent createdEvent) {
Expand All @@ -103,7 +116,8 @@ public static CreatedEvent fromProto(EventOuterClass.CreatedEvent createdEvent)
createdEvent.getEventId(),
Identifier.fromProto(createdEvent.getTemplateId()),
createdEvent.getContractId(),
Record.fromProto(createdEvent.getCreateArguments()));
Record.fromProto(createdEvent.getCreateArguments()),
createdEvent.hasAgreementText() ? Optional.of(createdEvent.getAgreementText().getValue()) : Optional.empty());

}
}
Expand Up @@ -3,16 +3,17 @@

package com.digitalasset;

import com.daml.ledger.javaapi.data.CreateAndExerciseCommand;
import com.daml.ledger.javaapi.data.CreateCommand;
import com.daml.ledger.javaapi.data.ExerciseCommand;
import com.daml.ledger.javaapi.data.*;
import org.junit.jupiter.api.Test;
import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith;
import tests.template1.SimpleTemplate;
import tests.template1.TestTemplate_Int;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.util.Collections;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;

@RunWith(JUnitPlatform.class)
public class TemplateMethodTest {
Expand All @@ -21,6 +22,8 @@ public class TemplateMethodTest {
// at compilation time in case any of the methods are generated differently
// or not at all

private static Record simpleTemplateRecord = new Record(new Record.Field(new Party("Bob")));

@Test
void templateHasCreateMethods() {
CreateCommand fromStatic = SimpleTemplate.create("Bob");
Expand Down Expand Up @@ -50,5 +53,34 @@ void templateHasCreateAndExerciseMethods() {

assertNotNull(fromSplatted, "CreateAndExerciseCommand from splatted choice was null");
assertNotNull(fromRecord, "CreateAndExerciseCommand from record choice was null");
assertEquals(fromRecord, fromSplatted, "CreateAndExerciseCommands from both methods are not the same");
}

@Test
void contractHasDeprecatedFromIdAndRecord() {
SimpleTemplate.Contract contract = SimpleTemplate.Contract.fromIdAndRecord("SomeId", simpleTemplateRecord);
assertFalse(contract.agreementText.isPresent(), "Field agreementText should not be present");
}

@Test
void contractHasFromIdAndRecord() {
SimpleTemplate.Contract emptyAgreement = SimpleTemplate.Contract.fromIdAndRecord("SomeId", simpleTemplateRecord, Optional.empty());
assertFalse(emptyAgreement.agreementText.isPresent(), "Field agreementText should not be present");

SimpleTemplate.Contract nonEmptyAgreement = SimpleTemplate.Contract.fromIdAndRecord("SomeId", simpleTemplateRecord, Optional.of("I agree"));
assertTrue(nonEmptyAgreement.agreementText.isPresent(), "Field agreementText should be present");
assertEquals(nonEmptyAgreement.agreementText, Optional.of("I agree"), "Unexpected agreementText");
}

@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());

SimpleTemplate.Contract withAgreement = SimpleTemplate.Contract.fromCreatedEvent(agreementEvent);
assertTrue(withAgreement.agreementText.isPresent(), "AgreementText was not present");

SimpleTemplate.Contract withoutAgreement = SimpleTemplate.Contract.fromCreatedEvent(noAgreementEvent);
assertFalse(withoutAgreement.agreementText.isPresent(), "AgreementText was present");
}
}
Expand Up @@ -119,8 +119,7 @@ class CodegenLedgerTest extends FlatSpec with Matchers {
.foldLeft(Map[String, Wolpertinger.Contract]())((acc, event) =>
event match {
case e: CreatedEvent =>
acc + (e.getContractId -> Wolpertinger.Contract
.fromIdAndRecord(e.getContractId, e.getArguments))
acc + (e.getContractId -> Wolpertinger.Contract.fromCreatedEvent(e))
case a: ArchivedEvent => acc - a.getContractId
})
.toList
Expand Down
Expand Up @@ -3,8 +3,6 @@

package com.digitalasset.daml.lf.codegen.backend.java.inner

import java.util.function.BiFunction

import com.daml.ledger.javaapi.data._
import com.squareup.javapoet._
import javax.lang.model.element.Modifier
Expand All @@ -29,9 +27,8 @@ object DecoderClass {
)

private val decoderFunctionType = ParameterizedTypeName.get(
ClassName.get(classOf[BiFunction[_, _, _]]),
ClassName.get(classOf[String]),
ClassName.get(classOf[Record]),
ClassName.get(classOf[java.util.function.Function[_, _]]),
ClassName.get(classOf[CreatedEvent]),
ClassName.get(classOf[Contract])
)

Expand All @@ -52,10 +49,10 @@ object DecoderClass {
.builder()
.addStatement("Identifier templateId = event.getTemplateId()")
.addStatement(
"BiFunction<String, Record, Contract> fromIdAndRecord = getDecoder(templateId).orElseThrow(() -> new IllegalArgumentException(\"No template found for identifier \" + templateId))")
.addStatement("String contractId = event.getContractId()")
.addStatement("Record arguments = event.getArguments()")
.addStatement("return fromIdAndRecord.apply(contractId, arguments)")
"$T decoderFunc = getDecoder(templateId).orElseThrow(() -> new IllegalArgumentException(\"No template found for identifier \" + templateId))",
decoderFunctionType
)
.addStatement("return decoderFunc.apply(event)")
.build())
.build()

Expand All @@ -78,7 +75,7 @@ object DecoderClass {
b.addStatement("$N = new $T()", decodersField, decodersMapType)
templateNames.foreach { template =>
b.addStatement(
"$N.put($T.TEMPLATE_ID, $T.Contract::fromIdAndRecord)",
"$N.put($T.TEMPLATE_ID, $T.Contract::fromCreatedEvent)",
decodersField,
template,
template)
Expand Down

0 comments on commit 2b00186

Please sign in to comment.