mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-17 15:57:21 +03:00
Expose signatories and observers throughout the platform (#1814)
* Add signatories and observers to Ledger API Cascade fixes deriving from compilation and test errors. * Add signatories and observers to Java bindings and codegen * Add signatories and observers to Scala bindings and codegen * Add signatories and observers to navigator * Add signatories and observers to the Haskell bindings * Add integration test * Comply with formatting standard * Add release notes * Apply edits from CI feedback and self-review * Fix wrong input on test case for observers sorting Co-Authored-By: Robert Autenrieth <31539813+rautenrieth-da@users.noreply.github.com> * Read JSON as strings from DB, parse them as parties Address https://github.com/digital-asset/daml/pull/1814#discussion_r296405252 * Change parties rendering and observer display condition Address https://github.com/digital-asset/daml/pull/1814#discussion_r296390104 * Fix sorting tests, implement sorting by agreement Address https://github.com/digital-asset/daml/pull/1814#discussion_r296397390 FIXME notes have been added for suspicious test cases * Prevent any mutation of CreatedEvent arguments Address https://github.com/digital-asset/daml/pull/1814#discussion_r296572639 * Comply with formatting standard * Stakeholders are filtered up to the witnesses Addresses https://github.com/digital-asset/daml/pull/1814#discussion_r297102521
This commit is contained in:
parent
798e96c9b9
commit
f8bebffb4e
@ -26,7 +26,8 @@ sealed trait Event[+Nid, +Cid, +Val] extends Product with Serializable {
|
||||
* @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 signatories as defined by the template
|
||||
* @param observers as defined by the template or implicitly as choice controllers
|
||||
* @param witnesses additional witnesses induced by parent exercises
|
||||
*/
|
||||
final case class CreateEvent[Cid, Val](
|
||||
@ -35,9 +36,21 @@ final case class CreateEvent[Cid, Val](
|
||||
contractKey: Option[KeyWithMaintainers[Val]],
|
||||
argument: Val,
|
||||
agreementText: String,
|
||||
stakeholders: Set[Party],
|
||||
signatories: Set[Party],
|
||||
observers: Set[Party],
|
||||
witnesses: Set[Party])
|
||||
extends Event[Nothing, Cid, Val] {
|
||||
|
||||
/**
|
||||
* Note that the stakeholders of each event node will always be a subset of the event witnesses. We perform this
|
||||
* narrowing since usually when consuming these events we only care about the parties that were included in the
|
||||
* disclosure information. Consumers should be aware that the stakeholders stored are _not_ all the stakeholders of
|
||||
* the contract, but just the stakeholders "up to witnesses".
|
||||
*
|
||||
* For broader and more detailed information, the consumer can use [[signatories]] and/or [[observers]].
|
||||
*/
|
||||
val stakeholders = signatories.union(observers).intersect(witnesses)
|
||||
|
||||
override def mapContractId[Cid2, Val2](f: Cid => Cid2, g: Val => Val2): CreateEvent[Cid2, Val2] =
|
||||
copy(
|
||||
contractId = f(contractId),
|
||||
@ -120,11 +133,6 @@ object Event {
|
||||
}
|
||||
|
||||
/** Use Blinding to get the blinding which will contain the disclosure
|
||||
*
|
||||
* Note that the stakeholders of each event node will always be a subset of the event witnesses. We perform this
|
||||
* narrowing since usually when consuming these events we only care about the parties that were included in the
|
||||
* disclosure information. Consumers should be aware that the stakeholders stored are _not_ all the stakeholders of
|
||||
* the contract, but just the stakeholders "up to witnesses".
|
||||
*/
|
||||
def collectEvents[Nid, Cid, Val](
|
||||
tx: GenTransaction[Nid, Cid, Val],
|
||||
@ -147,17 +155,17 @@ object Event {
|
||||
val node = tx.nodes(nodeId)
|
||||
node match {
|
||||
case nc: NodeCreate[Cid, Val] =>
|
||||
val templateId = nc.coinst.template
|
||||
val stakeholders = nc.stakeholders
|
||||
val evt =
|
||||
CreateEvent(
|
||||
nc.coid,
|
||||
templateId,
|
||||
nc.key,
|
||||
nc.coinst.arg,
|
||||
nc.coinst.agreementText,
|
||||
stakeholders intersect disclosure(nodeId),
|
||||
disclosure(nodeId))
|
||||
contractId = nc.coid,
|
||||
templateId = nc.coinst.template,
|
||||
contractKey = nc.key,
|
||||
argument = nc.coinst.arg,
|
||||
agreementText = nc.coinst.agreementText,
|
||||
signatories = nc.signatories,
|
||||
observers = nc.stakeholders diff nc.signatories,
|
||||
witnesses = disclosure(nodeId)
|
||||
)
|
||||
evts += (nodeId -> evt)
|
||||
go(remaining)
|
||||
case ne: NodeExercises[Nid, Cid, Val] =>
|
||||
|
@ -1197,9 +1197,11 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
|
||||
(Some[Name]("receiver"), ValueParty("Clara")))
|
||||
)),
|
||||
"",
|
||||
Set("Clara", "Alice"),
|
||||
Set("Bob", "Clara", "Alice"),
|
||||
signatories = Set("Alice"),
|
||||
observers = Set("Clara"), // Clara is implicitly an observer because she controls a choice
|
||||
witnesses = Set("Bob", "Clara", "Alice"),
|
||||
)
|
||||
bobVisibleCreate.asInstanceOf[CreateEvent[_, _]].stakeholders == Set("Alice", "Clara")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,12 +196,14 @@ class SemanticTester(
|
||||
scenarioCreateNode.key,
|
||||
scenarioCreateNode.coinst.arg.mapContractId(nextScenarioCoidToLedgerCoid),
|
||||
scenarioCreateNode.coinst.agreementText,
|
||||
scenarioCreateNode.stakeholders intersect scenarioWitnesses(scenarioNodeId),
|
||||
scenarioCreateNode.signatories,
|
||||
(scenarioCreateNode.stakeholders diff scenarioCreateNode.signatories),
|
||||
scenarioWitnesses(scenarioNodeId),
|
||||
)
|
||||
val ledgerCreateEventToCompare = ledgerCreateEvent.copy(stakeholders = Set.empty)
|
||||
val ledgerCreateEventToCompare =
|
||||
ledgerCreateEvent.copy(signatories = Set.empty, observers = Set.empty)
|
||||
val scenarioCreateEventToCompare =
|
||||
scenarioCreateEvent.copy(stakeholders = Set.empty)
|
||||
scenarioCreateEvent.copy(signatories = Set.empty, observers = Set.empty)
|
||||
// check that they're the same
|
||||
if (scenarioCreateEventToCompare != ledgerCreateEventToCompare) {
|
||||
throw SemanticTesterError(
|
||||
|
@ -174,14 +174,18 @@ raiseEvent = \case
|
||||
createdEventTemplateId,
|
||||
createdEventContractKey,
|
||||
createdEventCreateArguments,
|
||||
createdEventWitnessParties})) -> do
|
||||
createdEventWitnessParties,
|
||||
createdEventSignatories,
|
||||
createdEventObservers})) -> do
|
||||
eid <- raiseEventId createdEventEventId
|
||||
cid <- raiseContractId createdEventContractId
|
||||
tid <- perhaps "createdEventTemplateId" createdEventTemplateId >>= raiseTemplateId
|
||||
let key = createdEventContractKey >>= optional . raiseValue
|
||||
createArgs <- perhaps "createdEventCreateArguments" createdEventCreateArguments >>= raiseRecord
|
||||
witness <- raiseList raiseParty createdEventWitnessParties
|
||||
return CreatedEvent{eid,cid,tid,key,createArgs,witness}
|
||||
signatories <- raiseList raiseParty createdEventSignatories
|
||||
observers <- raiseList raiseParty createdEventObservers
|
||||
return CreatedEvent{eid,cid,tid,key,createArgs,witness,signatories,observers}
|
||||
|
||||
raiseRecord :: LL.Record -> Perhaps Record
|
||||
raiseRecord = \case
|
||||
|
@ -9,7 +9,7 @@
|
||||
-- (The lowlevel types are generated by compile-proto, and have verbose record-field and constructor names.)
|
||||
-- (2) These types are stronger: distinguishing various identifier classes, instead of everywhere being `Text`.
|
||||
-- (3) These types capture required-field invariants.
|
||||
-- (4) These types form a barrior against changes to names & representation in the .proto files.
|
||||
-- (4) These types form a barrier against changes to names & representation in the .proto files.
|
||||
|
||||
module DA.Ledger.Types( -- High Level types for communication over Ledger API
|
||||
|
||||
@ -104,12 +104,14 @@ data Transaction
|
||||
|
||||
data Event
|
||||
= CreatedEvent {
|
||||
eid :: EventId,
|
||||
cid :: ContractId,
|
||||
tid :: TemplateId,
|
||||
createArgs :: Record,
|
||||
witness :: [Party],
|
||||
key :: Maybe Value }
|
||||
eid :: EventId,
|
||||
cid :: ContractId,
|
||||
tid :: TemplateId,
|
||||
createArgs :: Record,
|
||||
witness :: [Party],
|
||||
key :: Maybe Value,
|
||||
signatories :: [Party],
|
||||
observers :: [Party] }
|
||||
|
||||
| ArchivedEvent {
|
||||
eid :: EventId,
|
||||
|
@ -48,6 +48,7 @@ tests = testGroupWithSandbox "Ledger Bindings"
|
||||
, tSubmitComplete
|
||||
, tCreateWithKey
|
||||
, tCreateWithoutKey
|
||||
, tStakeholders
|
||||
, tPastFuture
|
||||
]
|
||||
|
||||
@ -126,7 +127,6 @@ tSubmitComplete withSandbox = testCase "submit/complete" $ run withSandbox $ \pi
|
||||
Right Completion{cid=cidB} <- liftIO $ takeStream completions
|
||||
liftIO $ assertEqual "same cid sent/completed" cidA cidB
|
||||
|
||||
|
||||
tCreateWithKey :: SandboxTest
|
||||
tCreateWithKey withSandbox = testCase "createWithKey" $ run withSandbox $ \pid -> do
|
||||
lid <- getLedgerIdentity
|
||||
@ -151,6 +151,19 @@ tCreateWithoutKey withSandbox = testCase "createWithoutKey" $ run withSandbox $
|
||||
closeStream txs gone
|
||||
where gone = Abnormal "client gone"
|
||||
|
||||
tStakeholders :: WithSandbox -> Tasty.TestTree
|
||||
tStakeholders withSandbox = testCase "stakeholders are exposed correctly" $ run withSandbox $ \pid -> do
|
||||
lid <- getLedgerIdentity
|
||||
PastAndFuture{future=txs} <- Ledger.getTransactionsPF lid alice
|
||||
let command = createIOU pid alice "alice-in-chains" 100
|
||||
_ <- submitCommand lid alice command
|
||||
liftIO $ do
|
||||
Just (Right Transaction{events=[CreatedEvent{signatories,observers}]}) <- timeout 1 (takeStream txs)
|
||||
assertEqual "the only signatory" signatories [ alice ]
|
||||
assertEqual "observers are empty" observers []
|
||||
closeStream txs gone
|
||||
where gone = Abnormal "client gone"
|
||||
|
||||
tPastFuture :: SandboxTest
|
||||
tPastFuture withSandbox = testCase "past/future" $ run withSandbox $ \pid -> do
|
||||
lid <- getLedgerIdentity
|
||||
|
@ -428,7 +428,9 @@ class BotTest extends FlatSpec with Matchers with DataLayerHelpers {
|
||||
s"cid_$id",
|
||||
new Record(List.empty[Record.Field].asJava),
|
||||
Optional.empty(),
|
||||
Optional.empty()
|
||||
Optional.empty(),
|
||||
Collections.emptySet(),
|
||||
Collections.emptySet()
|
||||
)
|
||||
|
||||
def archive(event: CreatedEvent): ArchivedEvent =
|
||||
|
@ -187,6 +187,8 @@ object TransactionGenerator {
|
||||
contractKey <- Gen.option(valueGen(0))
|
||||
(scalaTemplateId, javaTemplateId) <- identifierGen
|
||||
(scalaRecord, javaRecord) <- Gen.sized(recordGen)
|
||||
signatories <- Gen.listOf(nonEmptyId)
|
||||
observers <- Gen.listOf(nonEmptyId)
|
||||
parties <- Gen.listOf(nonEmptyId)
|
||||
} yield
|
||||
(
|
||||
@ -197,16 +199,21 @@ object TransactionGenerator {
|
||||
Some(scalaTemplateId),
|
||||
contractKey.map(_._1),
|
||||
Some(scalaRecord),
|
||||
parties,
|
||||
agreementText = agreementText)),
|
||||
signatories ++ observers,
|
||||
signatories,
|
||||
observers,
|
||||
agreementText
|
||||
)),
|
||||
new data.CreatedEvent(
|
||||
parties.asJava,
|
||||
(signatories ++ observers).asJava,
|
||||
eventId,
|
||||
javaTemplateId,
|
||||
contractId,
|
||||
javaRecord,
|
||||
agreementText.map(Optional.of[String]).getOrElse(Optional.empty()),
|
||||
contractKey.fold(Optional.empty[data.Value])(c => Optional.of[data.Value](c._2))
|
||||
contractKey.fold(Optional.empty[data.Value])(c => Optional.of[data.Value](c._2)),
|
||||
signatories.toSet.asJava,
|
||||
observers.toSet.asJava
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -7,9 +7,7 @@ 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;
|
||||
import java.util.*;
|
||||
|
||||
public final class CreatedEvent implements Event, TreeEvent {
|
||||
|
||||
@ -27,14 +25,20 @@ public final class CreatedEvent implements Event, TreeEvent {
|
||||
|
||||
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;
|
||||
private final @NonNull Set<@NonNull String> signatories;
|
||||
|
||||
private final @NonNull Set<@NonNull String> observers;
|
||||
|
||||
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, @NonNull Collection<@NonNull String> signatories, @NonNull Collection<@NonNull String> observers) {
|
||||
this.witnessParties = Collections.unmodifiableList(new ArrayList<>(witnessParties));
|
||||
this.eventId = eventId;
|
||||
this.templateId = templateId;
|
||||
this.contractId = contractId;
|
||||
this.arguments = arguments;
|
||||
this.agreementText = agreementText;
|
||||
this.contractKey = contractKey;
|
||||
this.signatories = Collections.unmodifiableSet(new HashSet<>(signatories));
|
||||
this.observers = Collections.unmodifiableSet(new HashSet<>(observers));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -74,6 +78,12 @@ public final class CreatedEvent implements Event, TreeEvent {
|
||||
@NonNull
|
||||
public Optional<Value> getContractKey() { return contractKey; }
|
||||
|
||||
@NonNull
|
||||
public Set<@NonNull String> getSignatories() { return signatories; }
|
||||
|
||||
@NonNull
|
||||
public Set<@NonNull String> getObservers() { return observers; }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
@ -85,12 +95,14 @@ public final class CreatedEvent implements Event, TreeEvent {
|
||||
Objects.equals(contractId, that.contractId) &&
|
||||
Objects.equals(arguments, that.arguments) &&
|
||||
Objects.equals(agreementText, that.agreementText) &&
|
||||
Objects.equals(contractKey, that.contractKey);
|
||||
Objects.equals(contractKey, that.contractKey) &&
|
||||
Objects.equals(signatories, that.signatories) &&
|
||||
Objects.equals(observers, that.observers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(witnessParties, eventId, templateId, contractId, arguments, agreementText, contractKey);
|
||||
return Objects.hash(witnessParties, eventId, templateId, contractId, arguments, agreementText, contractKey, signatories, observers);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -103,16 +115,20 @@ public final class CreatedEvent implements Event, TreeEvent {
|
||||
", arguments=" + arguments +
|
||||
", agreementText='" + agreementText + '\'' +
|
||||
", contractKey=" + contractKey +
|
||||
", signatories=" + signatories +
|
||||
", observers=" + observers +
|
||||
'}';
|
||||
}
|
||||
|
||||
public EventOuterClass.@NonNull CreatedEvent toProto() {
|
||||
EventOuterClass.CreatedEvent.Builder builder = EventOuterClass.CreatedEvent.newBuilder()
|
||||
.setContractId(getContractId())
|
||||
.setCreateArguments(getArguments().toProtoRecord())
|
||||
.setEventId(getEventId())
|
||||
.setTemplateId(getTemplateId().toProto())
|
||||
.addAllWitnessParties(this.getWitnessParties());
|
||||
.setContractId(this.getContractId())
|
||||
.setCreateArguments(this.getArguments().toProtoRecord())
|
||||
.setEventId(this.getEventId())
|
||||
.setTemplateId(this.getTemplateId().toProto())
|
||||
.addAllWitnessParties(this.getWitnessParties())
|
||||
.addAllSignatories(this.getSignatories())
|
||||
.addAllObservers(this.getObservers());
|
||||
agreementText.ifPresent(a -> builder.setAgreementText(StringValue.of(a)));
|
||||
contractKey.ifPresent(a -> builder.setContractKey(a.toProto()));
|
||||
return builder.build();
|
||||
@ -126,7 +142,9 @@ public final class CreatedEvent implements Event, TreeEvent {
|
||||
createdEvent.getContractId(),
|
||||
Record.fromProto(createdEvent.getCreateArguments()),
|
||||
createdEvent.hasAgreementText() ? Optional.of(createdEvent.getAgreementText().getValue()) : Optional.empty(),
|
||||
createdEvent.hasContractKey() ? Optional.of(Value.fromProto(createdEvent.getContractKey())) : Optional.empty());
|
||||
createdEvent.hasContractKey() ? Optional.of(Value.fromProto(createdEvent.getContractKey())) : Optional.empty(),
|
||||
createdEvent.getSignatoriesList(),
|
||||
createdEvent.getObserversList());
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -17,4 +17,54 @@ class EventSpec extends FlatSpec with Matchers with GeneratorDrivenPropertyCheck
|
||||
val converted = Event.fromProtoEvent(event)
|
||||
Event.fromProtoEvent(converted.toProtoEvent) shouldEqual converted
|
||||
}
|
||||
|
||||
"CreatedEvents" should "be protected from mutations of the parameters" in forAll(createdEventGen) {
|
||||
e =>
|
||||
val mutatingWitnesses = new java.util.ArrayList[String](e.getWitnessPartiesList)
|
||||
val mutatingSignatories = new java.util.ArrayList[String](e.getSignatoriesList)
|
||||
val mutatingObservers = new java.util.ArrayList[String](e.getObserversList)
|
||||
|
||||
val event = new CreatedEvent(
|
||||
mutatingWitnesses,
|
||||
e.getEventId,
|
||||
Identifier.fromProto(e.getTemplateId),
|
||||
e.getContractId,
|
||||
Record.fromProto(e.getCreateArguments),
|
||||
java.util.Optional.empty(),
|
||||
java.util.Optional.empty(),
|
||||
mutatingSignatories,
|
||||
mutatingObservers
|
||||
)
|
||||
|
||||
mutatingWitnesses.add("INTRUDER!")
|
||||
mutatingSignatories.add("INTRUDER!")
|
||||
mutatingObservers.add("INTRUDER!")
|
||||
|
||||
event.getWitnessParties should not contain "INTRUDER!"
|
||||
event.getSignatories should not contain "INTRUDER!"
|
||||
event.getObservers should not contain "INTRUDER!"
|
||||
}
|
||||
|
||||
"CreatedEvents" should "disallow mutation of its mutable fields" in forAll(createdEventGen) { e =>
|
||||
val event = new CreatedEvent(
|
||||
e.getWitnessPartiesList,
|
||||
e.getEventId,
|
||||
Identifier.fromProto(e.getTemplateId),
|
||||
e.getContractId,
|
||||
Record.fromProto(e.getCreateArguments),
|
||||
java.util.Optional.empty(),
|
||||
java.util.Optional.empty(),
|
||||
e.getSignatoriesList,
|
||||
e.getObserversList
|
||||
)
|
||||
|
||||
an[UnsupportedOperationException] shouldBe thrownBy(event.getWitnessParties.add("INTRUDER!"))
|
||||
an[UnsupportedOperationException] shouldBe thrownBy(event.getSignatories.add("INTRUDER!"))
|
||||
an[UnsupportedOperationException] shouldBe thrownBy(event.getObservers.add("INTRUDER!"))
|
||||
|
||||
an[UnsupportedOperationException] shouldBe thrownBy(event.getWitnessParties.remove(0))
|
||||
an[UnsupportedOperationException] shouldBe thrownBy(event.getSignatories.remove(0))
|
||||
an[UnsupportedOperationException] shouldBe thrownBy(event.getObservers.remove(0))
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -193,6 +193,8 @@ object Generators {
|
||||
createArgument <- recordGen
|
||||
eventId <- Arbitrary.arbString.arbitrary
|
||||
witnessParties <- Gen.listOf(Arbitrary.arbString.arbitrary)
|
||||
signatories <- Gen.listOf(Gen.asciiPrintableStr)
|
||||
observers <- Gen.listOf(Gen.asciiPrintableStr)
|
||||
} yield
|
||||
EventOuterClass.CreatedEvent
|
||||
.newBuilder()
|
||||
@ -201,6 +203,8 @@ object Generators {
|
||||
.setCreateArguments(createArgument)
|
||||
.setEventId(eventId)
|
||||
.addAllWitnessParties(witnessParties.asJava)
|
||||
.addAllSignatories(signatories.asJava)
|
||||
.addAllObservers(observers.asJava)
|
||||
.build()
|
||||
|
||||
val archivedEventGen: Gen[EventOuterClass.ArchivedEvent] =
|
||||
|
@ -239,7 +239,7 @@ daml_compile(
|
||||
daml_compile(
|
||||
name = "ledger-tests-model",
|
||||
srcs = glob(["src/ledger-tests/daml/**/*.daml"]),
|
||||
main_src = "src/ledger-tests/daml/Wolpertinger.daml",
|
||||
main_src = "src/ledger-tests/daml/AllTests.daml",
|
||||
)
|
||||
|
||||
dar_to_java(
|
||||
|
@ -12,6 +12,7 @@ import org.junit.platform.runner.JUnitPlatform;
|
||||
import org.junit.runner.RunWith;
|
||||
import tests.contractkeys.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
@ -21,11 +22,11 @@ public class ContractKeysTest {
|
||||
|
||||
// NOTE: the tests are mostly here to make sure the code compiles
|
||||
|
||||
NoKey.Contract noKey = new NoKey.Contract(new NoKey.ContractId("no-key"), new NoKey("Alice"), Optional.empty());
|
||||
PartyKey.Contract partyKey = new PartyKey.Contract(new PartyKey.ContractId("party-key"), new PartyKey("Alice"), Optional.empty(), Optional.of("Alice"));
|
||||
RecordKey.Contract recordKey = new RecordKey.Contract(new RecordKey.ContractId("record-key"), new RecordKey("Alice", 42L), Optional.empty(), Optional.of(new PartyAndInt("Alice", 42L)));
|
||||
TupleKey.Contract tupleKey = new TupleKey.Contract(new TupleKey.ContractId("tuple-key"), new TupleKey("Alice", 42L), Optional.empty(), Optional.of(new Tuple2<>("Alice", 42L)));
|
||||
NestedTupleKey.Contract nestedTupleKey = new NestedTupleKey.Contract(new NestedTupleKey.ContractId("nested-tuple-key"), new NestedTupleKey("Alice", 42L, "blah", 47L, true, "foobar", 0L), Optional.empty(), Optional.of(new Tuple2<>(new Tuple3<>("Alice", 42L, "blah"), new Tuple4<>(47L, true, "foobar", 0L))));
|
||||
NoKey.Contract noKey = new NoKey.Contract(new NoKey.ContractId("no-key"), new NoKey("Alice"), Optional.empty(), Collections.emptySet(), Collections.emptySet());
|
||||
PartyKey.Contract partyKey = new PartyKey.Contract(new PartyKey.ContractId("party-key"), new PartyKey("Alice"), Optional.empty(), Optional.of("Alice"), Collections.emptySet(), Collections.emptySet());
|
||||
RecordKey.Contract recordKey = new RecordKey.Contract(new RecordKey.ContractId("record-key"), new RecordKey("Alice", 42L), Optional.empty(), Optional.of(new PartyAndInt("Alice", 42L)), Collections.emptySet(), Collections.emptySet());
|
||||
TupleKey.Contract tupleKey = new TupleKey.Contract(new TupleKey.ContractId("tuple-key"), new TupleKey("Alice", 42L), Optional.empty(), Optional.of(new Tuple2<>("Alice", 42L)), Collections.emptySet(), Collections.emptySet());
|
||||
NestedTupleKey.Contract nestedTupleKey = new NestedTupleKey.Contract(new NestedTupleKey.ContractId("nested-tuple-key"), new NestedTupleKey("Alice", 42L, "blah", 47L, true, "foobar", 0L), Optional.empty(), Optional.of(new Tuple2<>(new Tuple3<>("Alice", 42L, "blah"), new Tuple4<>(47L, true, "foobar", 0L))), Collections.emptySet(), Collections.emptySet());
|
||||
|
||||
@Test
|
||||
void noKeyHasNoKey() {
|
||||
|
@ -64,18 +64,18 @@ public class TemplateMethodTest {
|
||||
|
||||
@Test
|
||||
void contractHasFromIdAndRecord() {
|
||||
SimpleTemplate.Contract emptyAgreement = SimpleTemplate.Contract.fromIdAndRecord("SomeId", simpleTemplateRecord, Optional.empty());
|
||||
SimpleTemplate.Contract emptyAgreement = SimpleTemplate.Contract.fromIdAndRecord("SomeId", simpleTemplateRecord, Optional.empty(), Collections.emptySet(), Collections.emptySet());
|
||||
assertFalse(emptyAgreement.agreementText.isPresent(), "Field agreementText should not be present");
|
||||
|
||||
SimpleTemplate.Contract nonEmptyAgreement = SimpleTemplate.Contract.fromIdAndRecord("SomeId", simpleTemplateRecord, Optional.of("I agree"));
|
||||
SimpleTemplate.Contract nonEmptyAgreement = SimpleTemplate.Contract.fromIdAndRecord("SomeId", simpleTemplateRecord, Optional.of("I agree"), Collections.emptySet(), Collections.emptySet());
|
||||
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"), Optional.empty());
|
||||
CreatedEvent noAgreementEvent = new CreatedEvent(Collections.emptyList(), "eventId", SimpleTemplate.TEMPLATE_ID, "cid", simpleTemplateRecord, Optional.empty(), Optional.empty());
|
||||
CreatedEvent agreementEvent = new CreatedEvent(Collections.emptyList(), "eventId", SimpleTemplate.TEMPLATE_ID, "cid", simpleTemplateRecord, Optional.of("I agree"), Optional.empty(), Collections.emptySet(), Collections.emptySet());
|
||||
CreatedEvent noAgreementEvent = new CreatedEvent(Collections.emptyList(), "eventId", SimpleTemplate.TEMPLATE_ID, "cid", simpleTemplateRecord, Optional.empty(), Optional.empty(), Collections.emptySet(), Collections.emptySet());
|
||||
|
||||
SimpleTemplate.Contract withAgreement = SimpleTemplate.Contract.fromCreatedEvent(agreementEvent);
|
||||
assertTrue(withAgreement.agreementText.isPresent(), "AgreementText was not present");
|
||||
|
@ -0,0 +1,8 @@
|
||||
-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
-- SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
daml 1.2
|
||||
module AllTests where
|
||||
|
||||
import Stakeholders
|
||||
import Wolpertinger
|
@ -0,0 +1,51 @@
|
||||
-- Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
-- SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
daml 1.2
|
||||
module Stakeholders where
|
||||
|
||||
template OnlySignatories
|
||||
with
|
||||
owner: Party
|
||||
where
|
||||
signatory owner
|
||||
|
||||
controller owner can
|
||||
OnlySignatories_Choice : ()
|
||||
do return ()
|
||||
|
||||
template ImplicitObservers
|
||||
with
|
||||
owner: Party
|
||||
thirdParty: Party
|
||||
where
|
||||
signatory owner
|
||||
|
||||
controller thirdParty can
|
||||
ImplicitObservers_Choice : ()
|
||||
do return ()
|
||||
|
||||
template ExplicitObservers
|
||||
with
|
||||
owner: Party
|
||||
thirdParty: Party
|
||||
where
|
||||
signatory owner
|
||||
observer thirdParty
|
||||
|
||||
controller owner can
|
||||
ExplicitObservers_Choice : ()
|
||||
do return ()
|
||||
|
||||
template MixedObservers
|
||||
with
|
||||
owner: Party
|
||||
thirdParty1: Party
|
||||
thirdParty2: Party
|
||||
where
|
||||
signatory owner
|
||||
observer thirdParty1
|
||||
|
||||
controller thirdParty2 can
|
||||
MixedObservers_Choice : ()
|
||||
do return ()
|
@ -2,7 +2,7 @@
|
||||
-- SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
daml 1.2
|
||||
module Tests.Wolpertinger where
|
||||
module Wolpertinger where
|
||||
|
||||
import DA.Date
|
||||
import DA.Time
|
||||
|
@ -3,130 +3,20 @@
|
||||
|
||||
package com.digitalasset
|
||||
|
||||
import java.io.File
|
||||
import java.time.temporal.ChronoField
|
||||
import java.time.{Instant, LocalDate, ZoneOffset}
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.stream.{Collectors, StreamSupport}
|
||||
|
||||
import com.daml.ledger.javaapi.data
|
||||
import com.daml.ledger.javaapi.data.{
|
||||
ArchivedEvent,
|
||||
Command,
|
||||
CreatedEvent,
|
||||
Event,
|
||||
Filter,
|
||||
FiltersByParty,
|
||||
GetTransactionsRequest,
|
||||
LedgerOffset,
|
||||
NoFilter,
|
||||
SubmitCommandsRequest,
|
||||
Transaction,
|
||||
Unit => DamlUnit
|
||||
}
|
||||
import com.digitalasset.daml.bazeltools.BazelRunfiles
|
||||
import com.digitalasset.ledger.api.v1.CommandServiceOuterClass.SubmitAndWaitRequest
|
||||
import com.digitalasset.ledger.api.v1.TransactionServiceOuterClass.{
|
||||
GetLedgerEndRequest,
|
||||
GetTransactionsResponse
|
||||
}
|
||||
import com.digitalasset.ledger.api.v1.{CommandServiceGrpc, TransactionServiceGrpc}
|
||||
import com.digitalasset.platform.common.LedgerIdMode
|
||||
import com.digitalasset.platform.sandbox.config.SandboxConfig
|
||||
import com.digitalasset.platform.sandbox.services.SandboxServerResource
|
||||
import com.digitalasset.platform.services.time.{TimeModel, TimeProviderType}
|
||||
import io.grpc.Channel
|
||||
import org.scalatest.{Assertion, FlatSpec, Matchers}
|
||||
import tests.wolpertinger.color.Grey
|
||||
import tests.wolpertinger.{Color, Wolpertinger}
|
||||
import com.daml.ledger.javaapi.data.{Unit => DamlUnit}
|
||||
import org.scalatest.{FlatSpec, Matchers}
|
||||
import wolpertinger.color.Grey
|
||||
import wolpertinger.{Color, Wolpertinger}
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.language.implicitConversions
|
||||
import com.digitalasset.ledger.api.domain.LedgerId
|
||||
|
||||
@SuppressWarnings(Array("org.wartremover.warts.Any"))
|
||||
class CodegenLedgerTest extends FlatSpec with Matchers with BazelRunfiles {
|
||||
class CodegenLedgerTest extends FlatSpec with Matchers {
|
||||
|
||||
def testDalf = new File(rlocation("language-support/java/codegen/ledger-tests-model.dar"))
|
||||
|
||||
val LedgerID = "ledger-test"
|
||||
def withClient(testCode: Channel => Assertion): Assertion = {
|
||||
val cfg = SandboxConfig.default.copy(
|
||||
port = 0,
|
||||
damlPackages = List(testDalf),
|
||||
ledgerIdMode = LedgerIdMode.Static(LedgerId(LedgerID)),
|
||||
timeProviderType = TimeProviderType.WallClock,
|
||||
timeModel = TimeModel.reasonableDefault
|
||||
)
|
||||
val sandbox = new SandboxServerResource(cfg)
|
||||
sandbox.setup()
|
||||
try {
|
||||
testCode(sandbox.value)
|
||||
} finally {
|
||||
sandbox.close()
|
||||
}
|
||||
}
|
||||
|
||||
// unfortunately this is needed to help with passing functions to rxjava methods like Flowable#map
|
||||
implicit def func2rxfunc[A, B](f: A => B): io.reactivex.functions.Function[A, B] = f(_)
|
||||
private def randomId = UUID.randomUUID().toString
|
||||
|
||||
val Alice = "Alice"
|
||||
val allTemplates = new FiltersByParty(Map[String, Filter](Alice -> NoFilter.instance).asJava)
|
||||
|
||||
def sendCmd(channel: Channel, cmds: Command*) = {
|
||||
CommandServiceGrpc
|
||||
.newBlockingStub(channel)
|
||||
.withDeadlineAfter(40, TimeUnit.SECONDS)
|
||||
.submitAndWait(
|
||||
SubmitAndWaitRequest
|
||||
.newBuilder()
|
||||
.setCommands(
|
||||
SubmitCommandsRequest.toProto(
|
||||
LedgerID,
|
||||
randomId,
|
||||
randomId,
|
||||
randomId,
|
||||
Alice,
|
||||
Instant.now,
|
||||
Instant.now.plusSeconds(10),
|
||||
cmds.asJava))
|
||||
.build)
|
||||
}
|
||||
|
||||
def readActiveContracts(channel: Channel) = {
|
||||
val txService = TransactionServiceGrpc.newBlockingStub(channel)
|
||||
val end = txService.getLedgerEnd(GetLedgerEndRequest.newBuilder().setLedgerId(LedgerID).build)
|
||||
val txs = txService.getTransactions(
|
||||
new GetTransactionsRequest(
|
||||
LedgerID,
|
||||
LedgerOffset.LedgerBegin.getInstance(),
|
||||
LedgerOffset.fromProto(end.getOffset),
|
||||
allTemplates,
|
||||
true).toProto)
|
||||
val iterable: java.lang.Iterable[GetTransactionsResponse] = () => txs
|
||||
StreamSupport
|
||||
.stream(iterable.spliterator(), false)
|
||||
.flatMap[Transaction](
|
||||
(r: GetTransactionsResponse) =>
|
||||
data.GetTransactionsResponse
|
||||
.fromProto(r)
|
||||
.getTransactions
|
||||
.stream())
|
||||
.flatMap[Event]((t: Transaction) => t.getEvents.stream)
|
||||
.collect(Collectors.toList[Event])
|
||||
.asScala
|
||||
.foldLeft(Map[String, Wolpertinger.Contract]())((acc, event) =>
|
||||
event match {
|
||||
case e: CreatedEvent =>
|
||||
acc + (e.getContractId -> Wolpertinger.Contract.fromCreatedEvent(e))
|
||||
case a: ArchivedEvent => acc - a.getContractId
|
||||
})
|
||||
.toList
|
||||
.sortBy(_._1)
|
||||
.map(_._2)
|
||||
}
|
||||
import TestUtil._
|
||||
|
||||
val glookofly = new Wolpertinger(
|
||||
Alice,
|
||||
@ -157,7 +47,8 @@ class CodegenLedgerTest extends FlatSpec with Matchers with BazelRunfiles {
|
||||
it should "create correct create commands" in withClient { client =>
|
||||
sendCmd(client, glookofly.create())
|
||||
|
||||
val glookoflyContract :: Nil = readActiveContracts(client)
|
||||
val glookoflyContract :: Nil =
|
||||
readActiveContracts(Wolpertinger.Contract.fromCreatedEvent)(client)
|
||||
|
||||
glookoflyContract.data shouldEqual glookofly
|
||||
}
|
||||
@ -165,7 +56,8 @@ class CodegenLedgerTest extends FlatSpec with Matchers with BazelRunfiles {
|
||||
it should "create correct exercise choice commands" in withClient { client =>
|
||||
sendCmd(client, glookofly.create(), sruquito.create())
|
||||
|
||||
val glookoflyContract :: sruquitoContract :: Nil = readActiveContracts(client)
|
||||
val glookoflyContract :: sruquitoContract :: Nil =
|
||||
readActiveContracts(Wolpertinger.Contract.fromCreatedEvent)(client)
|
||||
|
||||
glookoflyContract.data shouldEqual glookofly
|
||||
sruquitoContract.data shouldEqual sruquito
|
||||
@ -175,7 +67,7 @@ class CodegenLedgerTest extends FlatSpec with Matchers with BazelRunfiles {
|
||||
.exerciseReproduce(sruquitoContract.id, tob)
|
||||
sendCmd(client, reproduceCmd)
|
||||
|
||||
val wolpertingers = readActiveContracts(client)
|
||||
val wolpertingers = readActiveContracts(Wolpertinger.Contract.fromCreatedEvent)(client)
|
||||
wolpertingers should have length 2
|
||||
|
||||
println(wolpertingers)
|
||||
@ -190,7 +82,8 @@ class CodegenLedgerTest extends FlatSpec with Matchers with BazelRunfiles {
|
||||
it should "create correct createAndExercise choice commands" in withClient { client =>
|
||||
sendCmd(client, glookofly.create())
|
||||
|
||||
val glookoflyContract :: Nil = readActiveContracts(client)
|
||||
val glookoflyContract :: Nil =
|
||||
readActiveContracts(Wolpertinger.Contract.fromCreatedEvent)(client)
|
||||
|
||||
glookoflyContract.data shouldEqual glookofly
|
||||
|
||||
@ -198,7 +91,7 @@ class CodegenLedgerTest extends FlatSpec with Matchers with BazelRunfiles {
|
||||
val reproduceCmd = sruquito.createAndExerciseReproduce(glookoflyContract.id, tob)
|
||||
sendCmd(client, reproduceCmd)
|
||||
|
||||
val wolpertingers = readActiveContracts(client)
|
||||
val wolpertingers = readActiveContracts(Wolpertinger.Contract.fromCreatedEvent)(client)
|
||||
wolpertingers should have length 2
|
||||
|
||||
val glook :: glookosruq :: Nil = wolpertingers
|
||||
@ -211,7 +104,7 @@ class CodegenLedgerTest extends FlatSpec with Matchers with BazelRunfiles {
|
||||
it should "provide the agreement text" in withClient { client =>
|
||||
sendCmd(client, glookofly.create())
|
||||
|
||||
val wolpertinger :: _ = readActiveContracts(client)
|
||||
val wolpertinger :: _ = readActiveContracts(Wolpertinger.Contract.fromCreatedEvent)(client)
|
||||
|
||||
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."
|
||||
@ -220,7 +113,7 @@ class CodegenLedgerTest extends FlatSpec with Matchers with BazelRunfiles {
|
||||
it should "provide the key" in withClient { client =>
|
||||
sendCmd(client, glookofly.create())
|
||||
|
||||
val wolpertinger :: _ = readActiveContracts(client)
|
||||
val wolpertinger :: _ = readActiveContracts(Wolpertinger.Contract.fromCreatedEvent)(client)
|
||||
|
||||
wolpertinger.key.isPresent shouldBe true
|
||||
wolpertinger.key.get.owner shouldEqual "Alice"
|
||||
@ -231,14 +124,15 @@ class CodegenLedgerTest extends FlatSpec with Matchers with BazelRunfiles {
|
||||
sendCmd(client, glookofly.create(), sruquito.create())
|
||||
|
||||
// We'll exercise by key, no need to get the handles
|
||||
val glookoflyContract :: sruquitoContract :: Nil = readActiveContracts(client)
|
||||
val glookoflyContract :: sruquitoContract :: Nil =
|
||||
readActiveContracts(Wolpertinger.Contract.fromCreatedEvent)(client)
|
||||
|
||||
val tob = Instant.now().`with`(ChronoField.NANO_OF_SECOND, 0)
|
||||
val reproduceByKeyCmd =
|
||||
Wolpertinger.exerciseByKeyReproduce(glookoflyContract.key.get, sruquitoContract.id, tob)
|
||||
sendCmd(client, reproduceByKeyCmd)
|
||||
|
||||
val wolpertingers = readActiveContracts(client)
|
||||
val wolpertingers = readActiveContracts(Wolpertinger.Contract.fromCreatedEvent)(client)
|
||||
wolpertingers should have length 2
|
||||
|
||||
val sruq :: glookosruq :: Nil = wolpertingers
|
||||
@ -247,4 +141,23 @@ class CodegenLedgerTest extends FlatSpec with Matchers with BazelRunfiles {
|
||||
glookosruq.data.name shouldEqual s"${glookofly.name}-${sruquito.name}"
|
||||
glookosruq.data.timeOfBirth shouldEqual tob
|
||||
}
|
||||
|
||||
it should "provide the correct signatories" in withClient { client =>
|
||||
sendCmd(client, glookofly.create())
|
||||
|
||||
val wolpertinger :: _ = readActiveContracts(Wolpertinger.Contract.fromCreatedEvent)(client)
|
||||
|
||||
// as stated explicitly in src/ledger-tests/daml/Wolpertinger.daml
|
||||
wolpertinger.signatories should contain only glookofly.owner
|
||||
}
|
||||
|
||||
it should "provide the correct observers" in withClient { client =>
|
||||
sendCmd(client, glookofly.create())
|
||||
|
||||
val wolpertinger :: _ = readActiveContracts(Wolpertinger.Contract.fromCreatedEvent)(client)
|
||||
|
||||
// no explicit observers and the only choice controller is a signatory
|
||||
wolpertinger.observers shouldBe empty
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.digitalasset
|
||||
|
||||
import org.scalatest.{FlatSpec, Matchers}
|
||||
import stakeholders.{ExplicitObservers, ImplicitObservers, MixedObservers, OnlySignatories}
|
||||
|
||||
@SuppressWarnings(Array("org.wartremover.warts.Any"))
|
||||
class StakeholdersTest extends FlatSpec with Matchers {
|
||||
|
||||
import TestUtil._
|
||||
|
||||
val onlySignatories = new OnlySignatories(Alice)
|
||||
val implicitObservers = new ImplicitObservers(Alice, Bob)
|
||||
val explicitObservers = new ExplicitObservers(Alice, Bob)
|
||||
val mixedObservers = new MixedObservers(Alice, Bob, Charlie)
|
||||
|
||||
behavior of "Stakeholders"
|
||||
|
||||
they should "be exposed correctly for contracts without observers" in withClient { client =>
|
||||
sendCmd(client, onlySignatories.create())
|
||||
|
||||
val contract :: _ = readActiveContracts(OnlySignatories.Contract.fromCreatedEvent)(client)
|
||||
|
||||
contract.signatories should contain only onlySignatories.owner
|
||||
contract.observers shouldBe empty
|
||||
}
|
||||
|
||||
they should "be exposed correctly for contracts with only implicit observers" in withClient {
|
||||
client =>
|
||||
sendCmd(client, implicitObservers.create())
|
||||
|
||||
val contract :: _ = readActiveContracts(ImplicitObservers.Contract.fromCreatedEvent)(client)
|
||||
|
||||
contract.signatories should contain only implicitObservers.owner
|
||||
contract.observers should contain only implicitObservers.thirdParty
|
||||
}
|
||||
|
||||
they should "be exposed correctly for contracts with only explicit observers" in withClient {
|
||||
client =>
|
||||
sendCmd(client, explicitObservers.create())
|
||||
|
||||
val contract :: _ = readActiveContracts(ExplicitObservers.Contract.fromCreatedEvent)(client)
|
||||
|
||||
contract.signatories should contain only explicitObservers.owner
|
||||
contract.observers should contain only explicitObservers.thirdParty
|
||||
}
|
||||
|
||||
they should "be exposed correctly for contracts with both implicit and explicit observers" in withClient {
|
||||
client =>
|
||||
sendCmd(client, mixedObservers.create())
|
||||
|
||||
val contract :: _ = readActiveContracts(MixedObservers.Contract.fromCreatedEvent)(client)
|
||||
|
||||
contract.signatories should contain only mixedObservers.owner
|
||||
contract.observers should contain only (mixedObservers.thirdParty1, mixedObservers.thirdParty2)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.digitalasset
|
||||
|
||||
import java.io.File
|
||||
import java.time.Instant
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.stream.{Collectors, StreamSupport}
|
||||
|
||||
import com.daml.ledger.javaapi.data
|
||||
import com.daml.ledger.javaapi.data._
|
||||
import com.digitalasset.daml.bazeltools.BazelRunfiles
|
||||
import com.digitalasset.ledger.api.domain.LedgerId
|
||||
import com.digitalasset.ledger.api.v1.{CommandServiceGrpc, TransactionServiceGrpc}
|
||||
import com.digitalasset.ledger.api.v1.CommandServiceOuterClass.SubmitAndWaitRequest
|
||||
import com.digitalasset.ledger.api.v1.TransactionServiceOuterClass.{
|
||||
GetLedgerEndRequest,
|
||||
GetTransactionsResponse
|
||||
}
|
||||
import com.digitalasset.platform.common.LedgerIdMode
|
||||
import com.digitalasset.platform.sandbox.config.SandboxConfig
|
||||
import com.digitalasset.platform.sandbox.services.SandboxServerResource
|
||||
import com.digitalasset.platform.services.time.{TimeModel, TimeProviderType}
|
||||
import io.grpc.Channel
|
||||
import org.scalatest.Assertion
|
||||
|
||||
import scala.language.implicitConversions
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
object TestUtil extends BazelRunfiles {
|
||||
|
||||
def testDalf = new File(rlocation("language-support/java/codegen/ledger-tests-model.dar"))
|
||||
|
||||
val LedgerID = "ledger-test"
|
||||
def withClient(testCode: Channel => Assertion): Assertion = {
|
||||
val cfg = SandboxConfig.default.copy(
|
||||
port = 0,
|
||||
damlPackages = List(testDalf),
|
||||
ledgerIdMode = LedgerIdMode.Static(LedgerId(LedgerID)),
|
||||
timeProviderType = TimeProviderType.WallClock,
|
||||
timeModel = TimeModel.reasonableDefault
|
||||
)
|
||||
val sandbox = new SandboxServerResource(cfg)
|
||||
sandbox.setup()
|
||||
try {
|
||||
testCode(sandbox.value)
|
||||
} finally {
|
||||
sandbox.close()
|
||||
}
|
||||
}
|
||||
|
||||
// unfortunately this is needed to help with passing functions to rxjava methods like Flowable#map
|
||||
implicit def func2rxfunc[A, B](f: A => B): io.reactivex.functions.Function[A, B] = f(_)
|
||||
private def randomId = UUID.randomUUID().toString
|
||||
|
||||
val Alice = "Alice"
|
||||
val Bob = "Bob"
|
||||
val Charlie = "Charlie"
|
||||
val allTemplates = new FiltersByParty(Map[String, Filter](Alice -> NoFilter.instance).asJava)
|
||||
|
||||
def sendCmd(channel: Channel, cmds: Command*) = {
|
||||
CommandServiceGrpc
|
||||
.newBlockingStub(channel)
|
||||
.withDeadlineAfter(40, TimeUnit.SECONDS)
|
||||
.submitAndWait(
|
||||
SubmitAndWaitRequest
|
||||
.newBuilder()
|
||||
.setCommands(
|
||||
SubmitCommandsRequest.toProto(
|
||||
LedgerID,
|
||||
randomId,
|
||||
randomId,
|
||||
randomId,
|
||||
Alice,
|
||||
Instant.now,
|
||||
Instant.now.plusSeconds(10),
|
||||
cmds.asJava))
|
||||
.build)
|
||||
}
|
||||
|
||||
def readActiveContracts[C <: Contract](fromCreatedEvent: CreatedEvent => C)(channel: Channel) = {
|
||||
val txService = TransactionServiceGrpc.newBlockingStub(channel)
|
||||
val end = txService.getLedgerEnd(GetLedgerEndRequest.newBuilder().setLedgerId(LedgerID).build)
|
||||
val txs = txService.getTransactions(
|
||||
new GetTransactionsRequest(
|
||||
LedgerID,
|
||||
LedgerOffset.LedgerBegin.getInstance(),
|
||||
LedgerOffset.fromProto(end.getOffset),
|
||||
allTemplates,
|
||||
true).toProto)
|
||||
val iterable: java.lang.Iterable[GetTransactionsResponse] = () => txs
|
||||
StreamSupport
|
||||
.stream(iterable.spliterator(), false)
|
||||
.flatMap[Transaction](
|
||||
(r: GetTransactionsResponse) =>
|
||||
data.GetTransactionsResponse
|
||||
.fromProto(r)
|
||||
.getTransactions
|
||||
.stream())
|
||||
.flatMap[Event]((t: Transaction) => t.getEvents.stream)
|
||||
.collect(Collectors.toList[Event])
|
||||
.asScala
|
||||
.foldLeft(Map[String, C]())((acc, event) =>
|
||||
event match {
|
||||
case e: CreatedEvent =>
|
||||
acc + (e.getContractId -> fromCreatedEvent(e))
|
||||
case a: ArchivedEvent => acc - a.getContractId
|
||||
})
|
||||
.toList
|
||||
.sortBy(_._1)
|
||||
.map(_._2)
|
||||
}
|
||||
|
||||
}
|
@ -71,10 +71,13 @@ private[inner] object TemplateClass extends StrictLogging {
|
||||
private val dataFieldName = "data"
|
||||
private val agreementFieldName = "agreementText"
|
||||
private val contractKeyFieldName = "key"
|
||||
private val signatoriesFieldName = "signatories"
|
||||
private val observersFieldName = "observers"
|
||||
|
||||
private val optionalString = ParameterizedTypeName.get(classOf[Optional[_]], classOf[String])
|
||||
private def optional(name: TypeName) =
|
||||
ParameterizedTypeName.get(ClassName.get(classOf[Optional[_]]), name)
|
||||
private def setOfStrings = ParameterizedTypeName.get(classOf[java.util.Set[_]], classOf[String])
|
||||
|
||||
private def generateContractClass(
|
||||
templateClassName: ClassName,
|
||||
@ -91,6 +94,13 @@ private[inner] object TemplateClass extends StrictLogging {
|
||||
classBuilder.addField(templateClassName, dataFieldName, Modifier.PUBLIC, Modifier.FINAL)
|
||||
classBuilder.addField(optionalString, agreementFieldName, Modifier.PUBLIC, Modifier.FINAL)
|
||||
|
||||
contractKeyClassName.foreach { name =>
|
||||
classBuilder.addField(optional(name), contractKeyFieldName, Modifier.PUBLIC, Modifier.FINAL)
|
||||
}
|
||||
|
||||
classBuilder.addField(setOfStrings, signatoriesFieldName, Modifier.PUBLIC, Modifier.FINAL)
|
||||
classBuilder.addField(setOfStrings, observersFieldName, Modifier.PUBLIC, Modifier.FINAL)
|
||||
|
||||
classBuilder.addSuperinterface(ClassName.get(classOf[javaapi.data.Contract]))
|
||||
|
||||
val constructorBuilder = MethodSpec
|
||||
@ -100,22 +110,31 @@ private[inner] object TemplateClass extends StrictLogging {
|
||||
.addParameter(templateClassName, dataFieldName)
|
||||
.addParameter(optionalString, agreementFieldName)
|
||||
|
||||
contractKeyClassName.foreach { name =>
|
||||
constructorBuilder.addParameter(optional(name), contractKeyFieldName)
|
||||
}
|
||||
|
||||
constructorBuilder
|
||||
.addParameter(setOfStrings, signatoriesFieldName)
|
||||
.addParameter(setOfStrings, observersFieldName)
|
||||
|
||||
constructorBuilder.addStatement("this.$L = $L", idFieldName, idFieldName)
|
||||
constructorBuilder.addStatement("this.$L = $L", dataFieldName, dataFieldName)
|
||||
constructorBuilder.addStatement("this.$L = $L", agreementFieldName, agreementFieldName)
|
||||
|
||||
contractKeyClassName.foreach { name =>
|
||||
classBuilder.addField(optional(name), contractKeyFieldName, Modifier.PUBLIC, Modifier.FINAL)
|
||||
constructorBuilder.addParameter(optional(name), contractKeyFieldName)
|
||||
contractKeyClassName.foreach { _ =>
|
||||
constructorBuilder.addStatement("this.$L = $L", contractKeyFieldName, contractKeyFieldName)
|
||||
}
|
||||
constructorBuilder.addStatement("this.$L = $L", signatoriesFieldName, signatoriesFieldName)
|
||||
constructorBuilder.addStatement("this.$L = $L", observersFieldName, observersFieldName)
|
||||
|
||||
val constructor = constructorBuilder.build()
|
||||
|
||||
classBuilder.addMethod(constructor)
|
||||
|
||||
val contractClassName = ClassName.bestGuess("Contract")
|
||||
val fields = Array(idFieldName, dataFieldName, agreementFieldName)
|
||||
val fields = Vector(idFieldName, dataFieldName, agreementFieldName) ++ contractKeyClassName
|
||||
.map(_ => contractKeyFieldName)
|
||||
.toList ++ Vector(signatoriesFieldName, observersFieldName)
|
||||
classBuilder
|
||||
.addMethod(
|
||||
generateFromIdAndRecord(
|
||||
@ -153,7 +172,9 @@ private[inner] object TemplateClass extends StrictLogging {
|
||||
ParameterSpec.builder(optionalString, agreementFieldName).build()
|
||||
) ++ maybeContractKeyClassName
|
||||
.map(name => ParameterSpec.builder(optional(name), contractKeyFieldName).build)
|
||||
.toList
|
||||
.toList ++ Iterable(
|
||||
ParameterSpec.builder(setOfStrings, signatoriesFieldName).build(),
|
||||
ParameterSpec.builder(setOfStrings, observersFieldName).build())
|
||||
|
||||
val spec =
|
||||
MethodSpec
|
||||
@ -170,12 +191,13 @@ private[inner] object TemplateClass extends StrictLogging {
|
||||
|
||||
val callParameterNames = Vector(idFieldName, dataFieldName, agreementFieldName) ++ maybeContractKeyClassName
|
||||
.map(_ => contractKeyFieldName)
|
||||
.toList
|
||||
.toList ++ Vector(signatoriesFieldName, observersFieldName).toList
|
||||
val callParameters = CodeBlock.join(callParameterNames.map(CodeBlock.of(_)).asJava, ", ")
|
||||
spec.addStatement("return new $T($L)", className, callParameters).build()
|
||||
}
|
||||
|
||||
private val emptyOptional = CodeBlock.of("$T.empty()", classOf[Optional[_]])
|
||||
private val emptySet = CodeBlock.of("$T.emptySet()", classOf[java.util.Collections])
|
||||
|
||||
private[inner] def generateFromIdAndRecordDeprecated(
|
||||
className: ClassName,
|
||||
@ -200,7 +222,9 @@ private[inner] object TemplateClass extends StrictLogging {
|
||||
val callParameters = Vector(
|
||||
CodeBlock.of(idFieldName),
|
||||
CodeBlock.of(dataFieldName),
|
||||
emptyOptional) ++ maybeContractKeyClassName.map(_ => emptyOptional).toList
|
||||
emptyOptional) ++ maybeContractKeyClassName.map(_ => emptyOptional).toList ++ Vector(
|
||||
emptySet,
|
||||
emptySet)
|
||||
|
||||
spec
|
||||
.addStatement("return new $T($L)", className, CodeBlock.join(callParameters.asJava, ", "))
|
||||
@ -214,6 +238,8 @@ private[inner] object TemplateClass extends StrictLogging {
|
||||
CodeBlock.of(
|
||||
"event.getContractKey().map(e -> $L)",
|
||||
FromValueGenerator.extractor(t, "e", CodeBlock.of("e"), newNameGenerator, packagePrefixes))
|
||||
private val getSignatories = CodeBlock.of("event.getSignatories()")
|
||||
private val getObservers = CodeBlock.of("event.getObservers()")
|
||||
|
||||
private[inner] def generateFromCreatedEvent(
|
||||
className: ClassName,
|
||||
@ -231,7 +257,7 @@ private[inner] object TemplateClass extends StrictLogging {
|
||||
|
||||
val params = Vector(getContractId, getArguments, getAgreementText) ++ maybeContractKeyType
|
||||
.map(getContractKey(_, packagePrefixes))
|
||||
.toList
|
||||
.toList ++ Vector(getSignatories, getObservers)
|
||||
|
||||
spec.addStatement("return fromIdAndRecord($L)", CodeBlock.join(params.asJava, ", ")).build()
|
||||
}
|
||||
|
@ -25,14 +25,27 @@ final class TemplateClassSpec extends FlatSpec with Matchers with OptionValues w
|
||||
fromIdAndRecord.returnType shouldEqual className
|
||||
}
|
||||
|
||||
it should "generate a method taking exactly a template identifier, a record, an agreement text and a contract key" in {
|
||||
val parameters = fromIdAndRecord.parameters.asScala.map(p => p.name -> p.`type`)
|
||||
parameters should contain only ("contractId" -> string, "record$" -> record, "agreementText" -> optionalString, "key" -> optionalContractKey)
|
||||
it should "generate a method taking the expected parameters (with contract key)" in {
|
||||
val parameters = fromIdAndRecord.parameters.asScala.map(p => p.name -> p.`type`).toList
|
||||
parameters should contain theSameElementsInOrderAs Seq(
|
||||
"contractId" -> string,
|
||||
"record$" -> record,
|
||||
"agreementText" -> optionalString,
|
||||
"key" -> optionalContractKey,
|
||||
"signatories" -> setOfStrings,
|
||||
"observers" -> setOfStrings
|
||||
)
|
||||
}
|
||||
|
||||
it should "generate a method taking exactly a template identifier, a record and an agreement text if no key is defined" in {
|
||||
val parameters = fromIdAndRecordWithoutKey.parameters.asScala.map(p => p.name -> p.`type`)
|
||||
parameters should contain only ("contractId" -> string, "record$" -> record, "agreementText" -> optionalString)
|
||||
it should "generate a method taking the expected parameters (without contract key)" in {
|
||||
val parameters =
|
||||
fromIdAndRecordWithoutKey.parameters.asScala.map(p => p.name -> p.`type`).toList
|
||||
parameters should contain theSameElementsInOrderAs Seq(
|
||||
"contractId" -> string,
|
||||
"record$" -> record,
|
||||
"agreementText" -> optionalString,
|
||||
"signatories" -> setOfStrings,
|
||||
"observers" -> setOfStrings)
|
||||
}
|
||||
|
||||
private[this] val className = ClassName.bestGuess("Test")
|
||||
@ -53,5 +66,7 @@ final class TemplateClassSpec extends FlatSpec with Matchers with OptionValues w
|
||||
ParameterizedTypeName.get(classOf[Optional[_]], classOf[String])
|
||||
private[this] val optionalContractKey =
|
||||
ParameterizedTypeName.get(ClassName.get(classOf[Optional[_]]), ckClassName)
|
||||
private[this] val setOfStrings =
|
||||
ParameterizedTypeName.get(classOf[java.util.Set[_]], classOf[String])
|
||||
|
||||
}
|
||||
|
@ -20,7 +20,8 @@ import org.scalatest.{Matchers, WordSpec}
|
||||
import scala.collection.immutable
|
||||
|
||||
class DomainTransactionMapperUT extends WordSpec with Matchers with AkkaTest {
|
||||
private val mockContract = Contract(Primitive.ContractId("contractId"), MockTemplate(), None)
|
||||
private val mockContract =
|
||||
Contract(Primitive.ContractId("contractId"), MockTemplate(), None, Seq.empty, Seq.empty)
|
||||
private val transactionMapper = new DomainTransactionMapper(createdEvent => Right(mockContract))
|
||||
|
||||
private def getResult(source: immutable.Iterable[Transaction]): Seq[DomainTransaction] =
|
||||
|
@ -13,13 +13,18 @@ import com.digitalasset.ledger.api.v1.{value => rpcvalue}
|
||||
* @param agreementText Agreement text. `None` means that we did not receive the `agreementText` from the server.
|
||||
* `Some("")` is a valid case, this means that the contract has `agreementText` set to an empty string
|
||||
* or agreement was not defined in DAML, which defaults to an empty string.
|
||||
* @param signatories Signatories of the contract as defined in the DAML template
|
||||
* @param observers Observers of the contract, both explicitly as defined in the DAML template and implicitly as
|
||||
* choice controllers.
|
||||
*
|
||||
* @tparam T Contract template type parameter.
|
||||
*/
|
||||
final case class Contract[+T](
|
||||
contractId: Primitive.ContractId[T],
|
||||
value: T with Template[T],
|
||||
agreementText: Option[String]) {
|
||||
agreementText: Option[String],
|
||||
signatories: Seq[String],
|
||||
observers: Seq[String]) {
|
||||
def arguments: rpcvalue.Record = value.arguments
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,9 @@ abstract class EventDecoderApi(
|
||||
Contract(
|
||||
Primitive.substContractId[Id, Nothing](ApiTypes.ContractId(createdEvent.contractId)),
|
||||
tadt,
|
||||
createdEvent.agreementText)
|
||||
createdEvent.agreementText,
|
||||
createdEvent.signatories,
|
||||
createdEvent.observers
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -529,11 +529,12 @@ class ScalaCodeGenIT
|
||||
event.event.isCreated shouldBe true
|
||||
decoder(event.getCreated) match {
|
||||
case Left(e) => fail(e.toString)
|
||||
case Right(Contract(_, contract, agreementText)) =>
|
||||
case Right(Contract(_, contract, agreementText, signatories, observers)) =>
|
||||
contract shouldBe expectedContract
|
||||
agreementText shouldBe expectedAgreement
|
||||
agreementText shouldBe event.getCreated.agreementText
|
||||
|
||||
signatories shouldBe event.getCreated.signatories
|
||||
observers shouldBe event.getCreated.observers
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,13 @@ object DecodeUtil {
|
||||
for {
|
||||
record <- event.createArguments: Option[V.Record]
|
||||
a <- decoder.read(V.Value.Sum.Record(record)): Option[A]
|
||||
} yield Contract(P.ContractId(event.contractId), a, event.agreementText)
|
||||
} yield
|
||||
Contract(
|
||||
P.ContractId(event.contractId),
|
||||
a,
|
||||
event.agreementText,
|
||||
event.signatories,
|
||||
event.observers)
|
||||
}
|
||||
|
||||
def decodeArchived[A <: Template[A]](transaction: Transaction): Option[P.ContractId[A]] = {
|
||||
|
@ -64,6 +64,14 @@ message CreatedEvent {
|
||||
// Required
|
||||
repeated string witness_parties = 5;
|
||||
|
||||
// The signatories for this contract as specified by the template.
|
||||
// Required
|
||||
repeated string signatories = 8;
|
||||
|
||||
// The observers for this contract as specified explicitly by the template or implicitly as choice controllers.
|
||||
// Required
|
||||
repeated string observers = 9;
|
||||
|
||||
// The agreement text of the contract.
|
||||
// We use StringValue to properly reflect optionality on the wire for backwards compatibility.
|
||||
// This is necessary since the empty string is an acceptable (and in fact the default) agreement
|
||||
|
@ -43,7 +43,7 @@ object EventFilter {
|
||||
|
||||
def filterEvent(event: Event): Option[Event] = {
|
||||
val servedEvent = event.event match {
|
||||
case Created(CreatedEvent(_, _, Some(templateId), _, _, _, _)) =>
|
||||
case Created(CreatedEvent(_, _, Some(templateId), _, _, _, _, _, _)) =>
|
||||
applyRequestingWitnesses(event, templateId)
|
||||
|
||||
case Archived(ArchivedEvent(_, _, Some(templateId), _)) =>
|
||||
|
@ -104,6 +104,8 @@ trait TransactionConversion {
|
||||
LfEngineToApi.lfValueToApiRecord(verbose, create.argument.value))),
|
||||
if (includeParentWitnesses) convert(create.witnesses)
|
||||
else convert(create.stakeholders),
|
||||
convert(create.signatories),
|
||||
convert(create.observers),
|
||||
Some(create.agreementText)
|
||||
)
|
||||
}
|
||||
|
@ -224,6 +224,8 @@ class GrpcTransactionService(
|
||||
.lfValueToApiRecord(verbose, createArguments)
|
||||
.fold(_ => throw new RuntimeException("Expected value to be a record."), identity)),
|
||||
witnessParties.toSeq,
|
||||
signatories.toSeq,
|
||||
observers.toSeq,
|
||||
Some(agreementText)
|
||||
)
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ object TransientContractRemover {
|
||||
event: CreateOrArchiveEvent,
|
||||
indexInList: Int): Unit = {
|
||||
event match {
|
||||
case createdEvent @ CreatedEvent(_, contractId, _, _, witnessParties, _, _) =>
|
||||
case createdEvent @ CreatedEvent(_, contractId, _, _, witnessParties, _, _, _, _) =>
|
||||
if (witnessParties.nonEmpty) {
|
||||
resultBuilder.update(indexInList, Some(event))
|
||||
val _ = creationByContractId.put(contractId, indexInList -> createdEvent)
|
||||
|
@ -27,6 +27,8 @@ class TransientContractRemoverTest extends WordSpec with Matchers {
|
||||
templateId,
|
||||
Value.ValueRecord(None, ImmArray.empty),
|
||||
Set(),
|
||||
Set(),
|
||||
Set(),
|
||||
"",
|
||||
None)
|
||||
private val archiveE = domain.Event.ArchivedEvent(evId2, contractId, templateId, Set())
|
||||
|
@ -77,6 +77,8 @@ object domain {
|
||||
templateId: Ref.Identifier,
|
||||
createArguments: ValueRecord[AbsoluteContractId],
|
||||
witnessParties: immutable.Set[Ref.Party],
|
||||
signatories: immutable.Set[Ref.Party],
|
||||
observers: immutable.Set[Ref.Party],
|
||||
agreementText: String,
|
||||
contractKey: Option[Value])
|
||||
extends Event
|
||||
|
@ -94,7 +94,7 @@ class ActiveContractsServiceIT
|
||||
template: Identifier,
|
||||
occurrence: Int = 1): Assertion =
|
||||
events.collect {
|
||||
case ce @ CreatedEvent(_, _, Some(`template`), _, _, _, _) => ce
|
||||
case ce @ CreatedEvent(_, _, Some(`template`), _, _, _, _, _, _) => ce
|
||||
}.size should equal(occurrence)
|
||||
|
||||
def threeCommands(ledgerId: domain.LedgerId, commandId: String): SubmitAndWaitRequest =
|
||||
@ -176,7 +176,8 @@ class ActiveContractsServiceIT
|
||||
def extractContractId(acsResponse: Seq[GetActiveContractsResponse]) = {
|
||||
val events = acsResponse.flatMap(_.activeContracts).toSet
|
||||
events.collect {
|
||||
case CreatedEvent(contractId, _, Some(tid), _, _, _, _) if tid == templateIds.dummy =>
|
||||
case CreatedEvent(contractId, _, Some(tid), _, _, _, _, _, _)
|
||||
if tid == templateIds.dummy =>
|
||||
contractId
|
||||
}.head
|
||||
}
|
||||
|
@ -686,6 +686,23 @@ class TransactionServiceIT
|
||||
resultF.map(_.agreementText shouldBe Some(""))
|
||||
}
|
||||
|
||||
"expose the correct stakeholders" in allFixtures { c =>
|
||||
val resultF = c.submitCreate(
|
||||
s"Creating_CallablePayout_contract_for_stakeholders_test-${runSuffix}",
|
||||
templateIds.callablePayout,
|
||||
List(
|
||||
RecordField("giver", party1.asParty),
|
||||
RecordField("receiver", party2.asParty)
|
||||
),
|
||||
party1
|
||||
)
|
||||
|
||||
resultF.map(contract => {
|
||||
contract.signatories should contain only party1
|
||||
contract.observers should contain only party2
|
||||
})
|
||||
}
|
||||
|
||||
"not expose the contract key in CreatedEvents for templates that do not have them" in allFixtures {
|
||||
c =>
|
||||
val resultF = c.submitCreate(
|
||||
|
@ -105,7 +105,7 @@ class CommandStaticTimeIT
|
||||
} yield event.event
|
||||
|
||||
events.collect {
|
||||
case Created(CreatedEvent(contractId, _, _, _, _, _, _)) => contractId
|
||||
case Created(CreatedEvent(contractId, _, _, _, _, _, _, _, _)) => contractId
|
||||
}.head
|
||||
}
|
||||
|
||||
|
@ -578,7 +578,7 @@ abstract class CommandTransactionChecks
|
||||
.map(_.event)
|
||||
.collect {
|
||||
case Archived(ArchivedEvent(eventId, _, _, _)) => eventId
|
||||
case Created(CreatedEvent(eventId, _, _, _, _, _, _)) => eventId
|
||||
case Created(CreatedEvent(eventId, _, _, _, _, _, _, _, _)) => eventId
|
||||
})
|
||||
.takeWithin(5.seconds) //TODO: work around as ledger end is broken. see DEL-3151
|
||||
.runWith(Sink.seq)
|
||||
|
@ -92,6 +92,8 @@ class ApiScenarioTransform(ledgerId: String, packages: Map[Ref.PackageId, Ast.Pa
|
||||
def toLfCreated(p: Ast.Package, createdEvent: ApiCreatedEvent)
|
||||
: Either[StatusRuntimeException, P.CreateEvent[AbsoluteContractId]] = {
|
||||
val witnesses = P.parties(createdEvent.witnessParties)
|
||||
val signatories = P.parties(createdEvent.signatories)
|
||||
val observers = P.parties(createdEvent.observers)
|
||||
for {
|
||||
coid <- toContractId(createdEvent.contractId)
|
||||
value <- toLfVersionedValue(createdEvent.getCreateArguments)
|
||||
@ -109,8 +111,8 @@ class ApiScenarioTransform(ledgerId: String, packages: Map[Ref.PackageId, Ast.Pa
|
||||
toLfVersionedValue(key).fold(throw _, KeyWithMaintainers(_, Set.empty))),
|
||||
value,
|
||||
createdEvent.agreementText.getOrElse(""),
|
||||
// conversion is imperfect as stakeholders are not determinable from events yet
|
||||
witnesses,
|
||||
signatories,
|
||||
observers,
|
||||
witnesses
|
||||
)
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ class AcsBench extends TestCommands with InfAwait {
|
||||
template: Identifier): Option[String] = {
|
||||
val events = response.activeContracts.toSet
|
||||
events.collectFirst {
|
||||
case CreatedEvent(contractId, _, Some(id), _, _, _, _) if id == template => contractId
|
||||
case CreatedEvent(contractId, _, Some(id), _, _, _, _, _, _) if id == template => contractId
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,7 +132,7 @@ trait TestHelper {
|
||||
def extractContractId(templateId: Identifier)(
|
||||
response: GetActiveContractsResponse): List[String] =
|
||||
response.activeContracts.toList.collect {
|
||||
case CreatedEvent(_, contractId, Some(actualTemplateId), _, _, _, _)
|
||||
case CreatedEvent(_, contractId, Some(actualTemplateId), _, _, _, _, _, _)
|
||||
if IdentifierEqual.equal(actualTemplateId, templateId) =>
|
||||
contractId
|
||||
}
|
||||
|
@ -4,14 +4,15 @@ Note: If you encounter bugs. Please report issues you find in the #team-ledger-a
|
||||
|
||||
# DAML Sandbox
|
||||
|
||||
To build a snapshot version of the sandbox use `sbt packageAll` in the ledger root.
|
||||
Find `sandbox/target/universal/sandbox-<version>-SNAPSHOT.tgz` and unpack it to a fitting place.
|
||||
To build a fat JAR with the sandbox built from HEAD run
|
||||
|
||||
bazel build //ledger/sandbox:sandbox-binary
|
||||
|
||||
Sandbox application can be run from command line with the following command:
|
||||
|
||||
```
|
||||
java -jar sandbox-<version>-SNAPSHOT.jar [options] <archive>
|
||||
```
|
||||
java -jar bazel-bin/ledger/sandbox/sandbox-binary_deploy.jar [options] <archive>
|
||||
|
||||
as run from the main project root directory (adjust the location of the JAR according to your working directory).
|
||||
|
||||
## Command line arguments
|
||||
|
||||
|
@ -146,6 +146,8 @@ trait TransactionConversion {
|
||||
},
|
||||
if (includeParentWitnesses) create.witnesses
|
||||
else create.stakeholders,
|
||||
create.signatories,
|
||||
create.observers,
|
||||
create.agreementText,
|
||||
create.contractKey.map(_.key.value)
|
||||
)
|
||||
|
@ -78,7 +78,7 @@ abstract class ScenarioLoadingITBase
|
||||
present: Boolean = true): Unit = {
|
||||
val occurrence = if (present) 1 else 0
|
||||
val _ = events.collect {
|
||||
case ce @ CreatedEvent(_, _, Some(`template`), _, _, _, _) =>
|
||||
case ce @ CreatedEvent(_, _, Some(`template`), _, _, _, _, _, _) =>
|
||||
// the absolute contract ids are opaque -- they have no specified format. however, for now
|
||||
// we like to keep it consistent between the DAML studio and the sandbox. Therefore verify
|
||||
// that they have the same format.
|
||||
|
@ -111,6 +111,8 @@ class EventFilterSpec extends WordSpec with Matchers with ScalaFutures with Opti
|
||||
templateId,
|
||||
ValueRecord(None, ImmArray.empty),
|
||||
Set(party, otherPartyWhoSeesEvents),
|
||||
Set.empty,
|
||||
Set.empty,
|
||||
"",
|
||||
None
|
||||
)
|
||||
|
@ -22,7 +22,7 @@ We can build and run a basic the backend using the following commands:
|
||||
bazel build //navigator/backend:navigator-binary_deploy.jar
|
||||
|
||||
# Run without arguments to show usage:
|
||||
java -jar navigator-binary_deploy.jar --help
|
||||
java -jar bazel-bin/navigator/backend/navigator-binary_deploy.jar --help
|
||||
|
||||
# Create a dummy configuration file
|
||||
cat << EOF > navigator.conf
|
||||
@ -35,7 +35,7 @@ users {
|
||||
EOF
|
||||
|
||||
# Start the web server
|
||||
java -jar navigator-binary_deploy.jar server
|
||||
java -jar bazel-bin/navigator/backend/navigator-binary_deploy.jar server -c navigator.conf
|
||||
```
|
||||
|
||||
If you start the server and the configuration file doesn't exist, the server will
|
||||
@ -247,4 +247,4 @@ This section contains short notes for anyone that wishes to make changes to the
|
||||
- `model/Model`: Defines the internal representations of ledger objects
|
||||
- `model/Util`: (SDAML package) -> (internal model)
|
||||
- `graphql/JsonType`: (internal model) <-> (frontend API JSON format)
|
||||
- `store/platform/PlatformSubscriber`: (ledger API JSON format) <-> (internal model)
|
||||
- `store/platform/PlatformSubscriber`: (ledger API JSON format) <-> (internal model)
|
||||
|
@ -6,6 +6,7 @@ package com.digitalasset.navigator.data
|
||||
import com.digitalasset.ledger.api.refinements.ApiTypes
|
||||
import com.digitalasset.navigator.json.ApiCodecCompressed
|
||||
import com.digitalasset.navigator.json.ApiCodecCompressed.JsonImplicits._
|
||||
import com.digitalasset.navigator.json.ModelCodec.JsonImplicits._
|
||||
import com.digitalasset.navigator.model._
|
||||
|
||||
import scala.util.{Failure, Try}
|
||||
@ -17,7 +18,9 @@ final case class ContractRow(
|
||||
templateId: String,
|
||||
archiveTransactionId: Option[String],
|
||||
argument: String,
|
||||
agreementText: Option[String]
|
||||
agreementText: Option[String],
|
||||
signatories: String,
|
||||
observers: String
|
||||
) {
|
||||
|
||||
def toContract(types: PackageRegistry): Try[Contract] = {
|
||||
@ -28,8 +31,10 @@ final case class ContractRow(
|
||||
recArgAny <- Try(
|
||||
ApiCodecCompressed.jsValueToApiType(argument.parseJson, tid, types.damlLfDefDataType _))
|
||||
recArg <- Try(recArgAny.asInstanceOf[ApiRecord])
|
||||
sig <- Try(signatories.parseJson.convertTo[List[ApiTypes.Party]])
|
||||
obs <- Try(signatories.parseJson.convertTo[List[ApiTypes.Party]])
|
||||
} yield {
|
||||
Contract(id, template, recArg, agreementText)
|
||||
Contract(id, template, recArg, agreementText, sig, obs)
|
||||
}).recoverWith {
|
||||
case e: Throwable =>
|
||||
Failure(DeserializationFailed(s"Failed to deserialize Contract from row: $this. Error: $e"))
|
||||
@ -44,6 +49,9 @@ object ContractRow {
|
||||
c.template.id.asOpaqueString,
|
||||
None,
|
||||
c.argument.toJson.compactPrint,
|
||||
c.agreementText)
|
||||
c.agreementText,
|
||||
c.signatories.toJson.compactPrint,
|
||||
c.observers.toJson.compactPrint
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -29,13 +29,17 @@ final case class EventRow(
|
||||
argumentValue: Option[String],
|
||||
actingParties: Option[String],
|
||||
isConsuming: Option[Boolean],
|
||||
agreementText: Option[String]) {
|
||||
agreementText: Option[String],
|
||||
signatories: String,
|
||||
observers: String) {
|
||||
|
||||
def toEvent(types: PackageRegistry): Try[Event] = {
|
||||
subclassType match {
|
||||
case "ContractCreated" =>
|
||||
(for {
|
||||
wp <- Try(witnessParties.parseJson.convertTo[List[ApiTypes.Party]])
|
||||
sig <- Try(signatories.parseJson.convertTo[List[ApiTypes.Party]])
|
||||
obs <- Try(observers.parseJson.convertTo[List[ApiTypes.Party]])
|
||||
tpStr <- Try(templateId.get)
|
||||
tp <- Try(parseOpaqueIdentifier(tpStr).get)
|
||||
recArgJson <- Try(recordArgument.get)
|
||||
@ -53,7 +57,9 @@ final case class EventRow(
|
||||
ApiTypes.ContractId(contractId),
|
||||
tp,
|
||||
recArg,
|
||||
agreementText
|
||||
agreementText,
|
||||
sig,
|
||||
obs
|
||||
)
|
||||
}).recoverWith {
|
||||
case e: Throwable =>
|
||||
@ -124,7 +130,9 @@ object EventRow {
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
c.agreementText
|
||||
c.agreementText,
|
||||
c.signatories.toJson.compactPrint,
|
||||
c.observers.toJson.compactPrint
|
||||
)
|
||||
case e: ChoiceExercised =>
|
||||
EventRow(
|
||||
@ -142,7 +150,9 @@ object EventRow {
|
||||
Some(e.argument.toJson.compactPrint),
|
||||
Some(e.actingParties.toJson.compactPrint),
|
||||
Some(e.consuming),
|
||||
None
|
||||
None,
|
||||
"[]",
|
||||
"[]"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,9 @@ object Queries {
|
||||
template_id TEXT NOT NULL,
|
||||
archive_transaction_id TEXT DEFAULT NULL,
|
||||
argument JSON NOT NULL,
|
||||
agreement_text TEXT DEFAULT NULL
|
||||
agreement_text TEXT DEFAULT NULL,
|
||||
signatories JSON DEFAULT NULL,
|
||||
observers JSON DEFAULT NULL
|
||||
)
|
||||
"""
|
||||
|
||||
@ -49,7 +51,9 @@ object Queries {
|
||||
argument_value JSON DEFAULT NULL,
|
||||
acting_parties JSON DEFAULT NULL,
|
||||
is_consuming INTEGER DEFAULT NULL,
|
||||
agreement_text TEXT DEFAULT NULL
|
||||
agreement_text TEXT DEFAULT NULL,
|
||||
signatories JSON DEFAULT NULL,
|
||||
observers JSON DEFAULT NULL
|
||||
)
|
||||
"""
|
||||
|
||||
@ -222,9 +226,9 @@ object Queries {
|
||||
sql"""
|
||||
INSERT INTO
|
||||
contract
|
||||
(id, template_id, archive_transaction_id, argument, agreement_text)
|
||||
(id, template_id, archive_transaction_id, argument, agreement_text, signatories, observers)
|
||||
VALUES
|
||||
(${row.id}, ${row.templateId}, ${row.archiveTransactionId}, ${row.argument}, ${row.agreementText})
|
||||
(${row.id}, ${row.templateId}, ${row.archiveTransactionId}, ${row.argument}, ${row.agreementText}, ${row.signatories}, ${row.observers})
|
||||
"""
|
||||
|
||||
def archiveContract(contractId: String, archiveTransactionId: String): Fragment =
|
||||
|
@ -318,7 +318,9 @@ final class GraphQLSchema(customEndpoints: Set[CustomEndpoint[_]]) {
|
||||
resolve =
|
||||
context => context.ctx.ledger.exercisedEventsOf(context.value, context.ctx.templates)),
|
||||
Field("argument", JsonType.ApiRecordType, resolve = _.value.argument),
|
||||
Field("agreementText", OptionType(StringType), resolve = _.value.agreementText)
|
||||
Field("agreementText", OptionType(StringType), resolve = _.value.agreementText),
|
||||
Field("signatories", ListType(StringType), resolve = _.value.signatories.map(Tag.unwrap)),
|
||||
Field("observers", ListType(StringType), resolve = _.value.observers.map(Tag.unwrap))
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -110,7 +110,13 @@ case class Ledger(
|
||||
event match {
|
||||
case event: ContractCreated =>
|
||||
packageRegistry.template(event.templateId).fold(this) { template =>
|
||||
val contract = Contract(event.contractId, template, event.argument, event.agreementText)
|
||||
val contract = Contract(
|
||||
event.contractId,
|
||||
template,
|
||||
event.argument,
|
||||
event.agreementText,
|
||||
event.signatories,
|
||||
event.observers)
|
||||
withContractCreatedInEvent(contract, event)
|
||||
}
|
||||
|
||||
|
@ -147,7 +147,9 @@ final case class ContractCreated(
|
||||
contractId: ApiTypes.ContractId,
|
||||
templateId: DamlLfIdentifier,
|
||||
argument: ApiRecord,
|
||||
agreementText: Option[String]
|
||||
agreementText: Option[String],
|
||||
signatories: List[ApiTypes.Party],
|
||||
observers: List[ApiTypes.Party]
|
||||
) extends Event
|
||||
|
||||
final case class ChoiceExercised(
|
||||
@ -174,7 +176,9 @@ final case class Contract(
|
||||
id: ApiTypes.ContractId,
|
||||
template: Template,
|
||||
argument: ApiRecord,
|
||||
agreementText: Option[String]
|
||||
agreementText: Option[String],
|
||||
signatories: List[ApiTypes.Party],
|
||||
observers: List[ApiTypes.Party]
|
||||
) extends TaggedNode[ApiTypes.ContractIdTag]
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
@ -171,10 +171,12 @@ case object LedgerApiV1 {
|
||||
ctx: Context
|
||||
): Result[Model.Event] = {
|
||||
val witnessParties = ApiTypes.Party.subst(event.witnessParties.toList)
|
||||
val signatories = ApiTypes.Party.subst(event.signatories.toList)
|
||||
val observers = ApiTypes.Party.subst(event.observers.toList)
|
||||
for {
|
||||
templateId <- Converter.checkExists("CreatedEvent.templateId", event.templateId)
|
||||
templateIdentifier = templateId.asDaml
|
||||
template <- getTemplate(templateIdentifier, ctx)
|
||||
_ <- getTemplate(templateIdentifier, ctx)
|
||||
arguments <- Converter.checkExists("CreatedEvent.arguments", event.createArguments)
|
||||
arg <- readRecordArgument(arguments, templateIdentifier, ctx)
|
||||
} yield
|
||||
@ -187,7 +189,9 @@ case object LedgerApiV1 {
|
||||
contractId = ApiTypes.ContractId(event.contractId),
|
||||
templateId = templateIdentifier,
|
||||
argument = arg,
|
||||
agreementText = event.agreementText
|
||||
agreementText = event.agreementText,
|
||||
signatories = signatories,
|
||||
observers = observers
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -211,6 +211,20 @@ package object filter {
|
||||
.onAnyValue
|
||||
.perform[String]((contract, agree) => checkOptionalContained(contract.agreementText, agree))
|
||||
.onTree
|
||||
.onLeaf("signatories")
|
||||
.onValue("*")
|
||||
.const(true)
|
||||
.onAnyValue
|
||||
.perform[String]((contract, signatory) =>
|
||||
contract.signatories.map(Tag.unwrap).exists(checkContained(_, signatory)))
|
||||
.onTree
|
||||
.onLeaf("observers")
|
||||
.onValue("*")
|
||||
.const(true)
|
||||
.onAnyValue
|
||||
.perform[String]((contract, observer) =>
|
||||
contract.observers.map(Tag.unwrap).exists(checkContained(_, observer)))
|
||||
.onTree
|
||||
// .onStar(check all fields)
|
||||
|
||||
lazy val choicesFilter =
|
||||
|
@ -202,6 +202,16 @@ object project {
|
||||
.perform[String]((contract, _) => StringValue(contract.id.unwrap))
|
||||
.onBranch("template", _.template, templateProject)
|
||||
.onBranch("argument", _.argument, argumentProject)
|
||||
.onLeaf("agreementText")
|
||||
.onAnyValue
|
||||
.perform[String]((contract, _) => StringValue(contract.agreementText.getOrElse("")))
|
||||
.onLeaf("signatories")
|
||||
.onAnyValue
|
||||
.perform[String]((contract, _) => StringValue(contract.signatories.mkString))
|
||||
.onLeaf("observers")
|
||||
.onAnyValue
|
||||
.perform[String]((contract, _) => StringValue(contract.observers.mkString))
|
||||
.onTree
|
||||
|
||||
lazy val choicesProject =
|
||||
root[Seq[Choice], ProjectValue, DamlLfTypeLookup]("choices")
|
||||
|
@ -52,6 +52,8 @@ type Contract implements Node {
|
||||
exerciseEvents: [ExercisedEvent!]!
|
||||
argument: DamlLfValueRecord!
|
||||
agreementText: String
|
||||
signatories: [String!]!
|
||||
observers: [String!]!
|
||||
}
|
||||
|
||||
type ContractEdge {
|
||||
|
@ -115,6 +115,11 @@ class RowSpec extends WordSpec with Matchers {
|
||||
}
|
||||
|
||||
"EventRow" when {
|
||||
|
||||
val alice = ApiTypes.Party("Alice")
|
||||
val bob = ApiTypes.Party("Bob")
|
||||
val charlie = ApiTypes.Party("Charlie")
|
||||
|
||||
"converting ContractCreated" should {
|
||||
val value = ContractCreated(
|
||||
ApiTypes.EventId("e01"),
|
||||
@ -125,7 +130,9 @@ class RowSpec extends WordSpec with Matchers {
|
||||
ApiTypes.ContractId("c01"),
|
||||
C.complexRecordId,
|
||||
C.complexRecordV,
|
||||
Some("agreement")
|
||||
Some("agreement"),
|
||||
List(alice),
|
||||
List(bob, charlie)
|
||||
)
|
||||
|
||||
"not change the value" in {
|
||||
|
@ -20,6 +20,9 @@ class LedgerSpec extends WordSpec with Matchers {
|
||||
private val templateId = C.simpleRecordTemplateId
|
||||
private val contractArgument = C.simpleRecordV
|
||||
private val template = templateRegistry.template(templateId).get
|
||||
private val alice = ApiTypes.Party("Alice")
|
||||
private val bob = ApiTypes.Party("Bob")
|
||||
private val charlie = ApiTypes.Party("Charlie")
|
||||
|
||||
def transaction(id: String): Transaction =
|
||||
Transaction(
|
||||
@ -29,7 +32,13 @@ class LedgerSpec extends WordSpec with Matchers {
|
||||
"0",
|
||||
List.empty)
|
||||
def contract(id: String): Contract =
|
||||
Contract(ApiTypes.ContractId(id), template, contractArgument, Option(""))
|
||||
Contract(
|
||||
ApiTypes.ContractId(id),
|
||||
template,
|
||||
contractArgument,
|
||||
Option(""),
|
||||
List(alice),
|
||||
List(bob, charlie))
|
||||
def error(commandId: String): CommandStatusError = CommandStatusError("code", "details")
|
||||
|
||||
"A ledger with existing contracts" when {
|
||||
@ -45,7 +54,9 @@ class LedgerSpec extends WordSpec with Matchers {
|
||||
ApiTypes.ContractId("C0"),
|
||||
templateId,
|
||||
contractArgument,
|
||||
Some("")
|
||||
Some(""),
|
||||
List(alice),
|
||||
List(bob, charlie)
|
||||
)
|
||||
)),
|
||||
templateRegistry
|
||||
@ -71,7 +82,9 @@ class LedgerSpec extends WordSpec with Matchers {
|
||||
contractId = ApiTypes.ContractId(contractId),
|
||||
templateId = templateId,
|
||||
argument = contractArgument,
|
||||
Some("")
|
||||
agreementText = Some(""),
|
||||
signatories = List(alice),
|
||||
observers = List(bob, charlie)
|
||||
)
|
||||
val created1 = contractCreated("E1", "C1")
|
||||
val created2 = contractCreated("E2", "C2")
|
||||
@ -184,7 +197,9 @@ class LedgerSpec extends WordSpec with Matchers {
|
||||
contractId = ApiTypes.ContractId("C3"),
|
||||
templateId = templateId,
|
||||
argument = contractArgument,
|
||||
agreementText = Some("")
|
||||
agreementText = Some(""),
|
||||
signatories = List(alice),
|
||||
observers = List(bob, charlie)
|
||||
)
|
||||
val exercisedEvent = ChoiceExercised(
|
||||
id = ApiTypes.EventId("E2"),
|
||||
|
@ -50,21 +50,31 @@ class ContractFilterSpec extends FlatSpec with Matchers {
|
||||
val template1 = Template(damlLfId0, List.empty)
|
||||
val template2 = Template(damlLfId1, List.empty)
|
||||
|
||||
val alice = ApiTypes.Party("Alice")
|
||||
val bob = ApiTypes.Party("Bob")
|
||||
val charlie = ApiTypes.Party("Charlie")
|
||||
|
||||
val contract1 = Contract(
|
||||
ApiTypes.ContractId("id1"),
|
||||
template1,
|
||||
ApiRecord(None, List(ApiRecordField("foo", ApiText("bar")))),
|
||||
None)
|
||||
None,
|
||||
List(alice),
|
||||
List(bob, charlie))
|
||||
val contract2 = Contract(
|
||||
ApiTypes.ContractId("id2"),
|
||||
template2,
|
||||
ApiRecord(None, List(ApiRecordField("int", ApiInt64(12)))),
|
||||
Some(""))
|
||||
Some(""),
|
||||
List(alice),
|
||||
List(bob, charlie))
|
||||
val contract3 = Contract(
|
||||
ApiTypes.ContractId("id3"),
|
||||
template1,
|
||||
ApiRecord(None, List(ApiRecordField("foo", ApiText("bar")))),
|
||||
Some("agreement"))
|
||||
Some("agreement"),
|
||||
List(alice),
|
||||
List(bob, charlie))
|
||||
|
||||
val templates = List(template1, template2)
|
||||
val contracts = List(contract1, contract2, contract3)
|
||||
@ -106,6 +116,14 @@ class ContractFilterSpec extends FlatSpec with Matchers {
|
||||
testAnd(List("agreementText" -> "gree"), List(contract3))
|
||||
testAnd(List("agreementText" -> "not-matching"), List())
|
||||
|
||||
testAnd(List("signatories" -> "Alice"), List(contract1, contract2, contract3))
|
||||
testAnd(List("signatories" -> "Bob"), List())
|
||||
|
||||
testAnd(List("observers" -> "Alice"), List())
|
||||
testAnd(List("observers" -> "Bob"), List(contract1, contract2, contract3))
|
||||
|
||||
testOr(List("agreementText" -> "gree", "observers" -> "Alice"), List(contract3))
|
||||
|
||||
val contractSearchFilterCriterion =
|
||||
new GraphQLSchema(Set()).contractSearchToFilter(template1.topLevelDecl)
|
||||
it should s"return contracts with id ${contract1.id} and ${contract3.id} on filter ($contractSearchFilterCriterion)" in {
|
||||
|
@ -9,6 +9,7 @@ import com.digitalasset.navigator.model._
|
||||
import scalaz.syntax.tag._
|
||||
import com.digitalasset.navigator.query.SortDirection.{ASCENDING, DESCENDING}
|
||||
import com.digitalasset.ledger.api.refinements.ApiTypes
|
||||
import scalaz.Tag
|
||||
|
||||
class ContractSortSpec extends FlatSpec with Matchers {
|
||||
|
||||
@ -48,26 +49,47 @@ class ContractSortSpec extends FlatSpec with Matchers {
|
||||
val template1 = Template(damlLfId0, List.empty)
|
||||
val template2 = Template(damlLfId1, List.empty)
|
||||
|
||||
val alice = ApiTypes.Party("Alice")
|
||||
val bob = ApiTypes.Party("Bob")
|
||||
val charlie = ApiTypes.Party("Charlie")
|
||||
val dana = ApiTypes.Party("Dana")
|
||||
val ernest = ApiTypes.Party("Ernest")
|
||||
val francis = ApiTypes.Party("Francis")
|
||||
val gloria = ApiTypes.Party("Gloria")
|
||||
val henry = ApiTypes.Party("Henry")
|
||||
val ivy = ApiTypes.Party("Ivy")
|
||||
val john = ApiTypes.Party("John")
|
||||
val kevin = ApiTypes.Party("Kevin")
|
||||
val louise = ApiTypes.Party("Louise")
|
||||
|
||||
val contract1 = Contract(
|
||||
ApiTypes.ContractId("id1"),
|
||||
template1,
|
||||
ApiRecord(None, List(ApiRecordField("foo", ApiText("bar")))),
|
||||
None)
|
||||
None,
|
||||
List(alice),
|
||||
List(bob, charlie))
|
||||
val contract2 = Contract(
|
||||
ApiTypes.ContractId("id2"),
|
||||
template2,
|
||||
ApiRecord(None, List(ApiRecordField("int", ApiInt64(1)))),
|
||||
Some(""))
|
||||
Some(""),
|
||||
List(gloria),
|
||||
List(ernest, francis))
|
||||
val contract3 = Contract(
|
||||
ApiTypes.ContractId("id3"),
|
||||
template1,
|
||||
ApiRecord(None, List(ApiRecordField("foo", ApiText("bar")))),
|
||||
Some("agreement"))
|
||||
Some("agreement"),
|
||||
List(dana),
|
||||
List(henry, ivy))
|
||||
val contract4 = Contract(
|
||||
ApiTypes.ContractId("id4"),
|
||||
template2,
|
||||
ApiRecord(None, List(ApiRecordField("int", ApiInt64(2)))),
|
||||
None)
|
||||
None,
|
||||
List(john),
|
||||
List(kevin, louise))
|
||||
|
||||
val contracts = List(contract1, contract2, contract3, contract4)
|
||||
|
||||
@ -77,25 +99,39 @@ class ContractSortSpec extends FlatSpec with Matchers {
|
||||
criteria.map { case (k, v) => new SortCriterion(k, v) },
|
||||
damlLfDefDataTypes.get,
|
||||
AllContractsPager)
|
||||
sorter.sort(contracts) should contain theSameElementsAs expected
|
||||
sorter.sort(contracts) should contain theSameElementsInOrderAs expected
|
||||
}
|
||||
}
|
||||
|
||||
implicit val sortParties: Ordering[List[ApiTypes.Party]] =
|
||||
Ordering
|
||||
.fromLessThan[List[ApiTypes.Party]](_.map(Tag.unwrap).mkString < _.map(Tag.unwrap).mkString)
|
||||
|
||||
test(List(), contracts)
|
||||
test(List("id" -> ASCENDING), contracts.sortBy(_.id.unwrap))
|
||||
test(List("id" -> DESCENDING), contracts.sortBy(_.id.unwrap)(Ordering[String].reverse))
|
||||
test(
|
||||
List("agreementText" -> ASCENDING),
|
||||
contracts.sortBy(_.agreementText)
|
||||
contracts.sortBy(_.agreementText.getOrElse(""))
|
||||
)
|
||||
test(List("argument.foo" -> ASCENDING), List(contract1, contract3, contract2, contract4))
|
||||
test(List("signatories" -> ASCENDING), contracts.sortBy(_.signatories))
|
||||
test(List("observers" -> ASCENDING), contracts.sortBy(_.observers))
|
||||
|
||||
// FIXME contract2 and contract4 are not compatible with the criteria and should go at the end
|
||||
test(List("argument.foo" -> ASCENDING), List(contract2, contract4, contract1, contract3))
|
||||
|
||||
// FIXME contract2 and contract4 are not compatible with the criteria and should go at the end
|
||||
test(
|
||||
List("argument.foo" -> ASCENDING, "id" -> DESCENDING),
|
||||
List(contract3, contract1, contract2, contract4))
|
||||
List(contract4, contract2, contract3, contract1))
|
||||
|
||||
// FIXME contract1 and contract3 are not compatible with the criteria and should go at the end
|
||||
test(
|
||||
List("argument.int" -> ASCENDING, "id" -> DESCENDING),
|
||||
List(contract2, contract4, contract3, contract1))
|
||||
List(contract3, contract1, contract2, contract4))
|
||||
|
||||
// FIXME check this test case according to the issues signaled above
|
||||
test(
|
||||
List("argument.int" -> DESCENDING, "id" -> DESCENDING),
|
||||
List(contract1, contract3, contract2, contract4))
|
||||
List(contract4, contract2, contract3, contract1))
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ class FilterSpec extends FlatSpec with Matchers {
|
||||
val commandId = ApiTypes.CommandId("Cmd")
|
||||
val workflowId = ApiTypes.WorkflowId("Workflow")
|
||||
|
||||
val contract = Contract(contractId, template, C.complexRecordV, Some(""))
|
||||
val contract = Contract(contractId, template, C.complexRecordV, Some(""), List.empty, List.empty)
|
||||
val command =
|
||||
CreateCommand(commandId, 1, workflowId, Instant.EPOCH, template.id, C.complexRecordV)
|
||||
|
||||
|
@ -28,7 +28,7 @@ lint: setup
|
||||
report-licenses: setup
|
||||
yarn license-checker --production --json --out $(OUT)
|
||||
|
||||
update-graphql-types:
|
||||
update-graphql-types: setup
|
||||
yarn run apollo client:codegen -c ./apollo.config.js --passthroughCustomScalars --customScalarsPrefix=OpaqueTypes. --outputFlat --target typescript --includes="./src/ui-core/**/*.ts*" ./src/ui-core/src/api/QueriesBody.txt
|
||||
echo "import * as OpaqueTypes from './OpaqueTypes'" > ./src/ui-core/src/api/Queries.ts
|
||||
cat ./src/ui-core/src/api/QueriesBody.txt >> ./src/ui-core/src/api/Queries.ts
|
||||
|
@ -35,6 +35,8 @@ export interface ContractDetailsById_node_Contract {
|
||||
argument: OpaqueTypes.DamlLfValueRecord;
|
||||
archiveEvent: ContractDetailsById_node_Contract_archiveEvent | null;
|
||||
agreementText: string | null;
|
||||
signatories: string[];
|
||||
observers: string[];
|
||||
template: ContractDetailsById_node_Contract_template;
|
||||
}
|
||||
|
||||
|
@ -84,6 +84,12 @@ const AgreementText = ({text}: {text: string}) => (
|
||||
</span>
|
||||
);
|
||||
|
||||
const Parties = ({title, parties}: {title: string, parties: string[]}) => (
|
||||
<span><SubHeader><Strong>{title}</Strong></SubHeader>
|
||||
<span>{parties.join(', ')}</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
interface Props {
|
||||
contract: Contract;
|
||||
choice?: string;
|
||||
@ -150,7 +156,9 @@ export default (props: Props) => {
|
||||
<p>{isArchived ? 'ARCHIVED' : null}</p>
|
||||
<ColumnContainer>
|
||||
<Column>
|
||||
{contract.agreementText && <AgreementText text={contract.agreementText} />}
|
||||
{contract.agreementText && <AgreementText text={contract.agreementText} />}
|
||||
{contract.signatories.length > 0 && <Parties title="Signatories" parties={contract.signatories} />}
|
||||
{contract.observers.length > 0 && <Parties title="Observers" parties={contract.observers} />}
|
||||
<SubHeader><Strong>Contract details</Strong></SubHeader>
|
||||
<ArgumentDisplay
|
||||
argument={contract.argument}
|
||||
|
@ -156,6 +156,8 @@ const query = gql`
|
||||
id
|
||||
}
|
||||
agreementText
|
||||
signatories
|
||||
observers
|
||||
template {
|
||||
id topLevelDecl
|
||||
choices { name parameter }
|
||||
|
@ -8,3 +8,14 @@ This page contains release notes for the SDK.
|
||||
|
||||
HEAD — ongoing
|
||||
--------------
|
||||
|
||||
- [Navigator] Contract details now show signatories and observers.
|
||||
See `#1269 <https://github.com/digital-asset/daml/issues/1269>`__.
|
||||
- [Scala Bindings] Reflect addition of signatories and observers to the bindings.
|
||||
See `#1269 <https://github.com/digital-asset/daml/issues/1269>`__.
|
||||
- [Java Codegen] Generated code supports signatories and observers as exposed by the bindings.
|
||||
See `#1269 <https://github.com/digital-asset/daml/issues/1269>`__.
|
||||
- [Java Bindings] Reflect addition of signatories and observers to the bindings.
|
||||
See `#1269 <https://github.com/digital-asset/daml/issues/1269>`__.
|
||||
- [Ledger API] Expose signatories and observers for a contract in ``CreatedEvent``.
|
||||
See `#1269 <https://github.com/digital-asset/daml/issues/1269>`__.
|
||||
|
Loading…
Reference in New Issue
Block a user