Skip to content

Commit

Permalink
Contract keys for kvutils (digital-asset#2304)
Browse files Browse the repository at this point in the history
* Support for contract keys to kvutils

* Fix handling of CreateEvents with contract keys in semantic tester

* Add ContractKeysIT to tests and fix issues related to fetchByKey

* Address code review
  • Loading branch information
Jussi Mäki authored Jul 29, 2019
1 parent ce5fe42 commit 3724a57
Show file tree
Hide file tree
Showing 15 changed files with 433 additions and 141 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ class SemanticTester(
val scenarioCreateEvent = CreateEvent(
nextScenarioCoidToLedgerCoid(scenarioCreateNode.coid),
scenarioCreateNode.coinst.template,
scenarioCreateNode.key,
// Unset maintainers as those are not made available over the ledger-api create event
scenarioCreateNode.key.map(k => k.copy(maintainers = Set.empty)),
scenarioCreateNode.coinst.arg.mapContractId(nextScenarioCoidToLedgerCoid),
scenarioCreateNode.coinst.agreementText,
scenarioCreateNode.signatories,
Expand Down
1 change: 1 addition & 0 deletions ledger/api-server-damlonx/reference-v2/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ conformance_tests_to_run = [
"DivulgenceIT",
"TransactionBackpressureIT",
"PartyManagementServiceIT",
"ContractKeysIT",
]

client_server_test(
Expand Down
5 changes: 5 additions & 0 deletions ledger/api-server-damlonx/reference/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,18 @@ client_server_test(
name = "conformance-test",
timeout = "short",
client = "//ledger/ledger-api-test-tool:ledger-api-test-tool",
client_args = [
"--exclude=*",
"--include=SemanticTests,ContractKeysIT",
],
data = [
"//ledger/ledger-api-integration-tests:SemanticTests.dar",
"//ledger/sandbox:Test.dar",
],
server = ":reference",
server_args = [
"$(rlocation $TEST_WORKSPACE/$(rootpath //ledger/ledger-api-integration-tests:SemanticTests.dar))",
"$(rlocation $TEST_WORKSPACE/$(rootpath //ledger/sandbox:Test.dar))",
],
tags = [
# NOTE(JM): As this test is somewhat heavy and has timeouts, run it without competition to avoid flakyness.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import com.digitalasset.daml.lf.archive.Decode
import com.digitalasset.daml.lf.data.Ref
import com.digitalasset.daml.lf.data.Time.Timestamp
import com.digitalasset.daml.lf.engine.{
Engine,
Blinding,
Engine,
Result,
ResultDone,
ResultError,
Expand All @@ -34,6 +34,7 @@ import com.digitalasset.ledger.api.v1.command_submission_service.{
CommandSubmissionServiceGrpc,
CommandSubmissionServiceLogging
}
import com.digitalasset.daml.lf.transaction.Node.GlobalKey
import io.grpc.BindableService
import org.slf4j.LoggerFactory
import scalaz.syntax.tag._
Expand Down Expand Up @@ -117,10 +118,13 @@ class DamlOnXSubmissionService private (
val getContract =
(coid: AbsoluteContractId) => indexService.lookupActiveContract(commands.submitter, coid)

consume(engine.submit(commands.commands))(getPackage, getContract)
val lookupKey =
(key: GlobalKey) => indexService.lookupKey(commands.submitter, key)

consume(engine.submit(commands.commands))(getPackage, getContract, lookupKey)
.flatMap {
case Left(err) =>
Future.failed(invalidArgument("error: " + err.detailMsg))
Future.failed(invalidArgument("error: " + err.msg))

case Right(updateTx) =>
// NOTE(JM): Authorizing the transaction here so that we do not submit
Expand Down Expand Up @@ -161,7 +165,8 @@ class DamlOnXSubmissionService private (
private def consume[A](result: Result[A])(
getPackage: Ref.PackageId => Future[Option[Ast.Package]],
getContract: Value.AbsoluteContractId => Future[
Option[Value.ContractInst[TxValue[Value.AbsoluteContractId]]]])(
Option[Value.ContractInst[TxValue[Value.AbsoluteContractId]]]],
lookupKey: GlobalKey => Future[Option[Value.AbsoluteContractId]])(
implicit ec: ExecutionContext): Future[Either[DamlLfError, A]] = {

def resolveStep(result: Result[A]): Future[Either[DamlLfError, A]] = {
Expand All @@ -171,7 +176,7 @@ class DamlOnXSubmissionService private (
case ResultDone(r) =>
Future.successful(Right(r))
case ResultNeedKey(key, resume) =>
Future.failed(new IllegalArgumentException("Contract keys not implemented yet"))
lookupKey(key).flatMap(optCoid => resolveStep(resume(optCoid)))
case ResultNeedContract(acoid, resume) =>
getContract(acoid).flatMap(o => resolveStep(resume(o)))
case ResultError(err) => Future.successful(Left(err))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ object domain {
final case class PartyNotKnownOnLedger(description: String) extends RejectionReason

final case class SubmitterCannotActViaParticipant(description: String) extends RejectionReason

}

type Value = Lf[Lf.AbsoluteContractId]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ daml 1.2
--
module Test where

--import DA.Time
--import DA.Date
import DA.Assert

------------------------------------------------------------
-- Consistency
--
Expand Down Expand Up @@ -482,3 +486,46 @@ wrongDivulgeTokenTo tokenId p = do
t <- fetch tokenId
delegationId <- create Delegation with owner = t.owner; delegate = p
exercise delegationId Delegation_Wrong_Divulge_Token with tokenId


----

template AccountInvitation with
account : Account
where
signatory account.bank

controller account.accountHolder can
Accept : ContractId Account
do create account

template Account with
bank : Party
accountHolder : Party
accountNumber : (Text, Int)
where
signatory [bank, accountHolder]
key (bank, accountNumber._1 <> show (this.accountNumber._2)) : (Party, Text)
maintainer key._1

contract_keys_test = scenario do
bank <- getParty "Bank"
alice <- getParty "Alice"
let account = Account with
bank
accountHolder = alice
accountNumber = ("CH", 123)
let accountKey = key account
invitationCid <- submit bank do
create AccountInvitation with account
accountCid <- submit alice do
exercise invitationCid Accept

accountCid' <- submit bank do
lookupByKey accountKey
accountCid' === Some accountCid

(accountCid', account') <- submit bank do
fetchByKey accountKey
accountCid' === accountCid
account' === account
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,11 @@ import com.digitalasset.platform.tests.integration.ledger.api.commands.{
CommandServiceIT,
CommandSubmissionTtlIT,
CommandTransactionChecksHighLevelIT,
CommandTransactionChecksLowLevelIT
}
import com.digitalasset.platform.tests.integration.ledger.api.{
ActiveContractsServiceIT,
DivulgenceIT,
PackageManagementServiceIT,
PartyManagementServiceIT,
TransactionServiceIT,
WitnessesIT
CommandTransactionChecksLowLevelIT,
ContractKeysIT
}
import com.digitalasset.platform.tests.integration.ledger.api.transaction.TransactionBackpressureIT
import com.digitalasset.platform.tests.integration.ledger.api._
import org.scalatest.time.{Seconds, Span}
import org.scalatest.{Args, Suite}

Expand Down Expand Up @@ -303,6 +297,20 @@ object LedgerApiTestTool {
}
)

val contractKeysIT = lazyInit(
"ContractKeysIT",
name =>
new ContractKeysIT {
override def suiteName: String = name
override protected def actorSystemName: String = s"${name}ToolActorSystem"
override protected def fixtureIdsEnabled: Set[LedgerBackend] =
Set(LedgerBackend.RemoteApiProxy)
override def spanScaleFactor: Double = toolConfig.timeoutScaleFactor
override protected def config: Config =
commonConfig.withDarFile(resourceAsFile(integrationTestResource))
}
)

Map(
transactionServiceIT,
transactionBackpressureIT,
Expand All @@ -314,7 +322,8 @@ object LedgerApiTestTool {
commandSubmissionTtlIT,
commandServiceIT,
activeContractsServiceIT,
witnessesIT
witnessesIT,
contractKeysIT
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import com.daml.ledger.participant.state.index.v1._
import com.daml.ledger.participant.state.v1._
import com.digitalasset.daml.lf.data.Ref.{PackageId, Party, TransactionIdString}
import com.digitalasset.daml.lf.data.{Ref, Time}
import com.digitalasset.daml.lf.transaction.Node.{NodeCreate, NodeExercises}
import com.digitalasset.daml.lf.transaction.Node.{GlobalKey, NodeCreate, NodeExercises}
import com.digitalasset.daml.lf.value.Value
import com.digitalasset.daml_lf.DamlLf
import com.digitalasset.ledger.api.domain.{LedgerOffset, PartyDetails, TransactionFilter}
Expand Down Expand Up @@ -347,6 +347,27 @@ final case class ReferenceIndexService(
}
}

override def lookupKey(
submitter: Party,
key: GlobalKey): Future[Option[Value.AbsoluteContractId]] =
futureWithState { state =>
Future {

state.activeContracts.keys
.get(key)
.flatMap { cid =>
logger.debug(s"lookupKey: $submitter, $key: $cid")

// note that we need to check visibility for keys, too, otherwise we leak the existence of a non-divulged
// contract if we return `Some`.
state.activeContracts.lookupContract(cid).flatMap {
case ac if canSeeContract(submitter, ac) => Some(cid)
case _ => None
}
}
}
}

private def getOffset: TransactionUpdate => Offset = {
case (offset, _) => offset
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package com.daml.ledger.participant.state.index.v1

import com.daml.ledger.participant.state.v1.Party
import com.digitalasset.daml.lf.transaction.Node.GlobalKey
import com.digitalasset.daml.lf.value.Value
import com.digitalasset.daml.lf.value.Value.{AbsoluteContractId, ContractInst}

Expand All @@ -18,4 +19,5 @@ trait ContractStore {
contractId: AbsoluteContractId
): Future[Option[ContractInst[Value.VersionedValue[AbsoluteContractId]]]]

def lookupKey(submitter: Party, key: GlobalKey): Future[Option[AbsoluteContractId]]
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@
// These messages must only be produced and consumed by the methods in
// `KeyValueCommitting`, `KeyValueConsumption` and `KeyValueSubmission` objects.
// You must not embed these messages in other protocol buffer messages.
//
// TODO(JM): We're consistently using the 'Daml' prefix to allow unqualified import
// of the generated classes and to distinguish these from the scala types in participant-state.
// Might want to use a more telling prefix.

syntax = "proto3";
package com.daml.ledger.participant.state.kvutils;
Expand All @@ -22,6 +18,7 @@ import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "da/daml_lf.proto";
import "daml-lf/transaction/src/main/protobuf/transaction.proto";
import "daml-lf/transaction/src/main/protobuf/com/digitalasset/daml/lf/value.proto";


// A submission to the ledger: a payload and its inputs if any.
Expand Down Expand Up @@ -281,6 +278,7 @@ message DamlStateKey {
DamlContractId contract_id = 2;
DamlCommandDedupKey command_dedup = 3;
string party = 4;
DamlContractKey contract_key = 5;
}
}

Expand All @@ -292,6 +290,7 @@ message DamlStateValue {
DamlContractState contract_state = 2;
DamlCommandDedupValue command_dedup = 3;
DamlPartyAllocation party = 4;
DamlContractKeyState contract_key_state = 5;
}
}

Expand All @@ -309,6 +308,7 @@ message DamlCommandDedupValue {
// NOTE(JM): Currently no content. Could store pointer to log entry.
}


// DAML contract state, recording the activeness state of a contract.
// The contract instance itself is stored within the transaction in a log entry.
// See https://github.com/digital-asset/daml/issues/734 for future work on contract
Expand All @@ -332,6 +332,16 @@ message DamlContractState {
// https://docs.daml.com/concepts/ledger-model/ledger-privacy.html#divulgence-when-non-stakeholders-see-contracts
repeated string divulged_to = 5;

// The contract key set by the contract. Optional.
DamlContractKey contract_key = 6;
}

message DamlContractKey {
// The DAML template identifier of the contract that created this key.
com.digitalasset.daml.lf.value.Identifier template_id = 1;

// The contract key itself.
com.digitalasset.daml.lf.value.VersionedValue key = 2;
}

// Stored information about a given party.
Expand All @@ -344,4 +354,11 @@ message DamlPartyAllocation {
string participant_id = 1;
// A display name associated with the given party.
string display_name = 2;
}
}

// The state of a contract key.
message DamlContractKeyState {
// The contract to which the key points to.
// If unset the key is inactive.
DamlContractId contract_id = 1;
}
Loading

0 comments on commit 3724a57

Please sign in to comment.