mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-20 09:17:43 +03:00
Prohibit contract IDs in contract keys and add key maintainers to exercises (#4048)
Prohibit contract IDs in contract keys and add key maintainers to exercises CHANGELOG_BEGIN - [DAML-LF] Prohibit contract IDs in contract keys completely. Previously, creating keys containing absolute (but not relative) contract IDs was allowed, but `lookupByKey` on such a key would crash. CHANGELOG_END Co-authored-by: Remy <remy.haemmerle@daml.com> Co-authored-by: Stephen Compall <scompall@nocandysw.com>
This commit is contained in:
parent
8811006617
commit
589f710313
@ -9,7 +9,7 @@ class EngineInfoTest extends WordSpec with Matchers {
|
||||
EngineInfo.getClass.getSimpleName should {
|
||||
"show supported LF, Transaction and Value versions" in {
|
||||
EngineInfo.show shouldBe
|
||||
"DAML LF Engine supports LF versions: 0, 0.dev, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.dev; Transaction versions: 1, 2, 3, 4, 5, 6, 7, 8; Value versions: 1, 2, 3, 4, 5, 6, 7"
|
||||
"DAML LF Engine supports LF versions: 0, 0.dev, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.dev; Transaction versions: 1, 2, 3, 4, 5, 6, 7, 8, 9; Value versions: 1, 2, 3, 4, 5, 6, 7"
|
||||
}
|
||||
|
||||
"toString returns the same value as show" in {
|
||||
|
@ -507,35 +507,29 @@ final case class Compiler(packages: PackageId PartialFunction Package) {
|
||||
|
||||
case UpdateLookupByKey(retrieveByKey) =>
|
||||
// Translates 'lookupByKey Foo <key>' into:
|
||||
// let key = <key>
|
||||
// let maintainers = keyMaintainers key
|
||||
// let keyWithMaintainers = {key: <key>, maintainers: <key maintainers> <key>}
|
||||
// in \token ->
|
||||
// let mbContractId = $lookupKey key
|
||||
// _ = $insertLookup Foo key
|
||||
// let mbContractId = $lookupKey keyWithMaintainers
|
||||
// _ = $insertLookup Foo keyWithMaintainers
|
||||
// in mbContractId
|
||||
val template = lookupTemplate(retrieveByKey.templateId)
|
||||
withEnv { _ =>
|
||||
val key = translate(retrieveByKey.key)
|
||||
val keyMaintainers = template.key match {
|
||||
case None =>
|
||||
throw CompileError(
|
||||
s"Expecting to find key for template ${retrieveByKey.templateId}, but couldn't")
|
||||
case Some(tplKey) => translate(tplKey.maintainers)
|
||||
}
|
||||
SELet(key, SEApp(keyMaintainers, Array(SEVar(1)))) in {
|
||||
env = env.incrPos // key
|
||||
env = env.incrPos // keyMaintainers
|
||||
val templateKey = template.key.getOrElse(
|
||||
throw CompileError(
|
||||
s"Expecting to find key for template ${retrieveByKey.templateId}, but couldn't")
|
||||
)
|
||||
SELet(encodeKeyWithMaintainers(key, templateKey)) in {
|
||||
env = env.incrPos // keyWithM
|
||||
SEAbs(1) {
|
||||
env = env.incrPos // token
|
||||
SELet(
|
||||
SBULookupKey(retrieveByKey.templateId)(
|
||||
SEVar(3), // key
|
||||
SEVar(2), // maintainers
|
||||
SEVar(2), // key with maintainers
|
||||
SEVar(1) // token
|
||||
),
|
||||
SBUInsertLookupNode(retrieveByKey.templateId)(
|
||||
SEVar(4), // key
|
||||
SEVar(3), // maintainers
|
||||
SEVar(3), // key with maintainers
|
||||
SEVar(1), // mb contract id
|
||||
SEVar(2) // token
|
||||
)
|
||||
@ -546,25 +540,21 @@ final case class Compiler(packages: PackageId PartialFunction Package) {
|
||||
|
||||
case UpdateFetchByKey(retrieveByKey) =>
|
||||
// Translates 'fetchByKey Foo <key>' into:
|
||||
// let key = <key>
|
||||
// let maintainers = keyMaintainers key
|
||||
// let keyWithMaintainers = {key: <key>, maintainers: <key maintainers> <key>}
|
||||
// in \token ->
|
||||
// let coid = $fetchKey key maintainers token
|
||||
// let coid = $fetchKey keyWithMaintainers token
|
||||
// contract = $fetch coid token
|
||||
// _ = $insertFetch coid <signatories> <observers>
|
||||
// in { contractId: ContractId Foo, contract: Foo }
|
||||
val template = lookupTemplate(retrieveByKey.templateId)
|
||||
withEnv { _ =>
|
||||
val key = translate(retrieveByKey.key)
|
||||
val keyMaintainers = template.key match {
|
||||
case None =>
|
||||
throw CompileError(
|
||||
s"Expecting to find key for template ${retrieveByKey.templateId}, but couldn't")
|
||||
case Some(tplKey) => translate(tplKey.maintainers)
|
||||
}
|
||||
SELet(key, SEApp(keyMaintainers, Array(SEVar(1)))) in {
|
||||
env = env.incrPos // key
|
||||
.incrPos // keyMaintainers
|
||||
val keyTemplate = template.key.getOrElse(
|
||||
throw CompileError(
|
||||
s"Expecting to find key for template ${retrieveByKey.templateId}, but couldn't")
|
||||
)
|
||||
SELet(encodeKeyWithMaintainers(key, keyTemplate)) in {
|
||||
env = env.incrPos // key with maintainers
|
||||
SEAbs(1) {
|
||||
env = env.incrPos // token
|
||||
env = env.addExprVar(template.param)
|
||||
@ -574,8 +564,7 @@ final case class Compiler(packages: PackageId PartialFunction Package) {
|
||||
val observers = translate(template.observers)
|
||||
SELet(
|
||||
SBUFetchKey(retrieveByKey.templateId)(
|
||||
SEVar(3), // key
|
||||
SEVar(2), // maintainers
|
||||
SEVar(2), // key with maintainers
|
||||
SEVar(1) // token
|
||||
),
|
||||
SBUFetch(retrieveByKey.templateId)(
|
||||
@ -788,12 +777,21 @@ final case class Compiler(packages: PackageId PartialFunction Package) {
|
||||
}
|
||||
}
|
||||
|
||||
private def encodeKeyWithMaintainers(key: SExpr, tmplKey: TemplateKey): SExpr =
|
||||
SELet(key) in
|
||||
SBStructCon(Name.Array(keyFieldName, maintainersFieldName))(
|
||||
SEVar(1), // key
|
||||
SEApp(translate(tmplKey.maintainers), Array(SEVar(1) /* key */ )))
|
||||
|
||||
private def translateKeyWithMaintainers(tmplKey: TemplateKey): SExpr =
|
||||
encodeKeyWithMaintainers(translate(tmplKey.body), tmplKey)
|
||||
|
||||
/** Compile a choice into a top-level function for exercising that choice */
|
||||
private def compileChoice(tmplId: TypeConName, tmpl: Template, choice: TemplateChoice): SExpr =
|
||||
// Compiles a choice into:
|
||||
// SomeTemplate$SomeChoice = \actors cid arg token ->
|
||||
// let targ = fetch cid
|
||||
// _ = $beginExercise[tmplId, chId] arg cid actors <sigs> <obs> <ctrls> token
|
||||
// _ = $beginExercise[tmplId, chId] arg cid actors <sigs> <obs> <ctrls> <mbKey> token
|
||||
// result = <updateE>
|
||||
// _ = $endExercise[tmplId]
|
||||
// in result
|
||||
@ -819,11 +817,7 @@ final case class Compiler(packages: PackageId PartialFunction Package) {
|
||||
val controllers = translate(choice.controllers)
|
||||
val mbKey: SExpr = tmpl.key match {
|
||||
case None => SEValue.None
|
||||
case Some(k) =>
|
||||
SEApp(
|
||||
SEBuiltin(SBSome),
|
||||
Array(translate(k.body)),
|
||||
)
|
||||
case Some(k) => SEApp(SEBuiltin(SBSome), Array(translateKeyWithMaintainers(k)))
|
||||
}
|
||||
env = env.incrPos // beginExercise's ()
|
||||
|
||||
@ -1164,12 +1158,7 @@ final case class Compiler(packages: PackageId PartialFunction Package) {
|
||||
|
||||
val key = tmpl.key match {
|
||||
case None => SEValue.None
|
||||
case Some(tmplKey) =>
|
||||
SELet(translate(tmplKey.body)) in
|
||||
SBSome(
|
||||
SBStructCon(Name.Array(keyFieldName, maintainersFieldName))(
|
||||
SEVar(1), // key
|
||||
SEApp(translate(tmplKey.maintainers), Array(SEVar(1) /* key */ ))))
|
||||
case Some(k) => SEApp(SEBuiltin(SBSome), Array(translateKeyWithMaintainers(k)))
|
||||
}
|
||||
|
||||
env = env.incrPos // key
|
||||
@ -1237,18 +1226,15 @@ final case class Compiler(packages: PackageId PartialFunction Package) {
|
||||
// in exerciseResult
|
||||
val template = lookupTemplate(tmplId)
|
||||
withEnv { _ =>
|
||||
val keyMaintainers = template.key match {
|
||||
case None =>
|
||||
throw CompileError(s"Expecting to find key for template ${tmplId}, but couldn't")
|
||||
case Some(tplKey) => translate(tplKey.maintainers)
|
||||
}
|
||||
SELet(key, SEApp(keyMaintainers, Array(SEVar(1)))) in {
|
||||
env = env.incrPos // key
|
||||
env = env.incrPos // keyMaintainers
|
||||
val tmplKey = template.key.getOrElse(
|
||||
throw CompileError(s"Expecting to find key for template ${tmplId}, but couldn't")
|
||||
)
|
||||
SELet(encodeKeyWithMaintainers(key, tmplKey)) in {
|
||||
env = env.incrPos // key with maintainers
|
||||
SEAbs(1) {
|
||||
env = env.incrPos // token
|
||||
SELet(
|
||||
SBUFetchKey(tmplId)(SEVar(3), SEVar(2), SEVar(1)),
|
||||
SBUFetchKey(tmplId)(SEVar(2), SEVar(1)),
|
||||
SEApp(compileExercise(tmplId, SEVar(1), choiceId, optActors, argument), Array(SEVar(2)))
|
||||
) in SEVar(1)
|
||||
}
|
||||
|
@ -24,7 +24,6 @@ import com.digitalasset.daml.lf.transaction.Transaction._
|
||||
import com.digitalasset.daml.lf.value.{Value => V}
|
||||
import com.digitalasset.daml.lf.value.ValueVersions.asVersionedValue
|
||||
import com.digitalasset.daml.lf.transaction.Node.{GlobalKey, KeyWithMaintainers}
|
||||
import com.digitalasset.daml.lf.value.Value.{AbsoluteContractId, RelativeContractId}
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
@ -818,16 +817,10 @@ object SBuiltin {
|
||||
val sigs = extractParties(args.get(2))
|
||||
val obs = extractParties(args.get(3))
|
||||
val key = args.get(4) match {
|
||||
case SOptional(None) => None
|
||||
case SOptional(Some(SStruct(flds, vals)))
|
||||
if flds.length == 2 && flds(0) == "key" && flds(1) == "maintainers" =>
|
||||
asVersionedValue(vals.get(0).toValue) match {
|
||||
case Left(err) => crash(err)
|
||||
case Right(keyVal) =>
|
||||
Some(KeyWithMaintainers(key = keyVal, maintainers = extractParties(vals.get(1))))
|
||||
}
|
||||
case _ => crash("Bad key")
|
||||
case SOptional(mbKey) => mbKey.map(extractKeyWithMaintainers)
|
||||
case v => crash(s"Expected optional key with maintainers, got: $v")
|
||||
}
|
||||
|
||||
val (coid, newPtx) = machine.ptx
|
||||
.insertCreate(
|
||||
coinst = V.ContractInst(template = templateId, arg = createArg, agreementText = agreement),
|
||||
@ -845,13 +838,13 @@ object SBuiltin {
|
||||
}
|
||||
|
||||
/** $beginExercise
|
||||
* :: arg (choice argument)
|
||||
* -> ContractId arg (contract to exercise)
|
||||
* -> List Party (actors)
|
||||
* -> List Party (signatories)
|
||||
* -> List Party (observers)
|
||||
* -> List Party (choice controllers)
|
||||
* -> Optional key (template key)
|
||||
* :: arg (choice argument)
|
||||
* -> ContractId arg (contract to exercise)
|
||||
* -> List Party (actors)
|
||||
* -> List Party (signatories)
|
||||
* -> List Party (observers)
|
||||
* -> List Party (choice controllers)
|
||||
* -> Optional {key: key, maintainers: List Party} (template key, if present)
|
||||
* -> Token
|
||||
* -> ()
|
||||
*/
|
||||
@ -875,9 +868,10 @@ object SBuiltin {
|
||||
val sigs = extractParties(args.get(3))
|
||||
val obs = extractParties(args.get(4))
|
||||
val ctrls = extractParties(args.get(5))
|
||||
|
||||
val mbKey = args.get(6) match {
|
||||
case SOptional(mbKey) => mbKey.map(_.toValue)
|
||||
case _ => crash("Bad key, expected optional")
|
||||
case SOptional(mbKey) => mbKey.map(extractKeyWithMaintainers)
|
||||
case v => crash(s"Expected optional key with maintainers, got: $v")
|
||||
}
|
||||
|
||||
machine.ptx = machine.ptx
|
||||
@ -891,16 +885,7 @@ object SBuiltin {
|
||||
signatories = sigs,
|
||||
stakeholders = sigs union obs,
|
||||
controllers = ctrls,
|
||||
mbKey = mbKey.map { k =>
|
||||
asVersionedValue(k) match {
|
||||
case Left(err) => crash(err)
|
||||
case Right(x) =>
|
||||
x.mapContractId {
|
||||
case RelativeContractId(rcoid) => crash(s"got relative contract id $rcoid in key")
|
||||
case coid: AbsoluteContractId => coid
|
||||
}
|
||||
}
|
||||
},
|
||||
mbKey = mbKey,
|
||||
chosenValue = asVersionedValue(arg) match {
|
||||
case Left(err) => crash(err)
|
||||
case Right(x) => x
|
||||
@ -1023,23 +1008,16 @@ object SBuiltin {
|
||||
}
|
||||
|
||||
/** $lookupKey[T]
|
||||
* :: key
|
||||
* -> List Party (maintainers)
|
||||
* :: { key: key, maintainers: List Party }
|
||||
* -> Token
|
||||
* -> Maybe (ContractId T)
|
||||
*/
|
||||
final case class SBULookupKey(templateId: TypeConName) extends SBuiltin(3) {
|
||||
final case class SBULookupKey(templateId: TypeConName) extends SBuiltin(2) {
|
||||
def execute(args: util.ArrayList[SValue], machine: Machine): Unit = {
|
||||
checkToken(args.get(2))
|
||||
val key = asVersionedValue(args.get(0).toValue.mapContractId[Nothing] { cid =>
|
||||
crash(s"Unexpected contract id in key: $cid")
|
||||
}) match {
|
||||
case Left(err) => crash(err)
|
||||
case Right(x) => x
|
||||
}
|
||||
val maintainers = extractParties(args.get(1))
|
||||
checkLookupMaintainers(templateId, machine, maintainers)
|
||||
val gkey = GlobalKey(templateId, key)
|
||||
checkToken(args.get(1))
|
||||
val keyWithMaintainers = extractKeyWithMaintainers(args.get(0))
|
||||
checkLookupMaintainers(templateId, machine, keyWithMaintainers.maintainers)
|
||||
val gkey = GlobalKey(templateId, keyWithMaintainers.key)
|
||||
// check if we find it locally
|
||||
machine.ptx.keys.get(gkey) match {
|
||||
case Some(mbCoid) =>
|
||||
@ -1068,26 +1046,16 @@ object SBuiltin {
|
||||
}
|
||||
|
||||
/** $insertLookup[T]
|
||||
* :: key
|
||||
* -> List Party (maintainers)
|
||||
* :: { key : key, maintainers: List Party}
|
||||
* -> Maybe (ContractId T)
|
||||
* -> Token
|
||||
* -> ()
|
||||
*/
|
||||
final case class SBUInsertLookupNode(templateId: TypeConName) extends SBuiltin(4) {
|
||||
final case class SBUInsertLookupNode(templateId: TypeConName) extends SBuiltin(3) {
|
||||
def execute(args: util.ArrayList[SValue], machine: Machine): Unit = {
|
||||
checkToken(args.get(3))
|
||||
val key =
|
||||
asVersionedValue(
|
||||
args
|
||||
.get(0)
|
||||
.toValue
|
||||
.mapContractId(coid => crash(s"Unexpected contract id in key: $coid"))) match {
|
||||
case Left(err) => crash(err)
|
||||
case Right(v) => v
|
||||
}
|
||||
val maintainers = extractParties(args.get(1))
|
||||
val mbCoid = args.get(2) match {
|
||||
checkToken(args.get(2))
|
||||
val keyWithMaintainers = extractKeyWithMaintainers(args.get(0))
|
||||
val mbCoid = args.get(1) match {
|
||||
case SOptional(mb) =>
|
||||
mb.map {
|
||||
case SContractId(coid) => coid
|
||||
@ -1098,7 +1066,9 @@ object SBuiltin {
|
||||
machine.ptx = machine.ptx.insertLookup(
|
||||
templateId,
|
||||
machine.lastLocation,
|
||||
KeyWithMaintainers(key = key, maintainers = maintainers),
|
||||
KeyWithMaintainers(
|
||||
key = keyWithMaintainers.key,
|
||||
maintainers = keyWithMaintainers.maintainers),
|
||||
mbCoid)
|
||||
machine.ctrl = CtrlValue.Unit
|
||||
checkAborted(machine.ptx)
|
||||
@ -1106,23 +1076,16 @@ object SBuiltin {
|
||||
}
|
||||
|
||||
/** $fetchKey[T]
|
||||
* :: key
|
||||
* -> List Party (maintainers)
|
||||
* :: { key: key, maintainers: List Party }
|
||||
* -> Token
|
||||
* -> ContractId T
|
||||
*/
|
||||
final case class SBUFetchKey(templateId: TypeConName) extends SBuiltin(3) {
|
||||
final case class SBUFetchKey(templateId: TypeConName) extends SBuiltin(2) {
|
||||
def execute(args: util.ArrayList[SValue], machine: Machine): Unit = {
|
||||
checkToken(args.get(2))
|
||||
val key = asVersionedValue(args.get(0).toValue.mapContractId[Nothing] { cid =>
|
||||
crash(s"Unexpected contract id in key: $cid")
|
||||
}) match {
|
||||
case Left(err) => crash(err)
|
||||
case Right(x) => x
|
||||
}
|
||||
val maintainers = extractParties(args.get(1))
|
||||
checkLookupMaintainers(templateId, machine, maintainers)
|
||||
val gkey = GlobalKey(templateId, key)
|
||||
checkToken(args.get(1))
|
||||
val keyWithMaintainers = extractKeyWithMaintainers(args.get(0))
|
||||
checkLookupMaintainers(templateId, machine, keyWithMaintainers.maintainers)
|
||||
val gkey = GlobalKey(templateId, keyWithMaintainers.key)
|
||||
// check if we find it locally
|
||||
machine.ptx.keys.get(gkey) match {
|
||||
case Some(None) =>
|
||||
@ -1524,6 +1487,19 @@ object SBuiltin {
|
||||
crash(s"value not a list of parties or party: $v")
|
||||
}
|
||||
|
||||
private def extractKeyWithMaintainers(v: SValue): KeyWithMaintainers[Value[Nothing]] = v match {
|
||||
case SStruct(flds, vals)
|
||||
if flds.length == 2 && flds(0) == keyFieldName && flds(1) == maintainersFieldName =>
|
||||
asVersionedValue(vals.get(0).toValue) match {
|
||||
case Left(err) => crash(err)
|
||||
case Right(keyVal) =>
|
||||
val keyWithoutContractIds =
|
||||
keyVal.mapContractId(coid => crash(s"Unexpected contract id in key: $coid"))
|
||||
KeyWithMaintainers(key = keyWithoutContractIds, maintainers = extractParties(vals.get(1)))
|
||||
}
|
||||
case _ => crash(s"Invalid key with maintainers: $v")
|
||||
}
|
||||
|
||||
private def checkLookupMaintainers(
|
||||
templateId: Identifier,
|
||||
machine: Machine,
|
||||
|
@ -59,12 +59,8 @@ object Ledger {
|
||||
}
|
||||
|
||||
@inline
|
||||
def assertAbsoluteContractId(cid: ContractId): AbsoluteContractId =
|
||||
cid match {
|
||||
case acoid: AbsoluteContractId => acoid
|
||||
case _: RelativeContractId =>
|
||||
crash("Unexpected relative contract id '$cid'")
|
||||
}
|
||||
def assertNoContractId(cid: ContractId): Nothing =
|
||||
crash(s"Not expecting to find a contract id here, but found '$cid'")
|
||||
|
||||
private val `:` = LedgerString.assertFromString(":")
|
||||
|
||||
@ -220,7 +216,7 @@ object Ledger {
|
||||
controllers = nex.controllers,
|
||||
children = nex.children.map(ScenarioNodeId(commitPrefix, _)),
|
||||
exerciseResult = nex.exerciseResult.map(makeAbsolute(commitPrefix, _)),
|
||||
key = nex.key.map(_.mapContractId(assertAbsoluteContractId))
|
||||
key = nex.key.map(_.mapValue(_.mapContractId(assertNoContractId)))
|
||||
)
|
||||
case nlbk: NodeLookupByKey.WithTxValue[ContractId] =>
|
||||
NodeLookupByKey(
|
||||
@ -1136,7 +1132,9 @@ object Ledger {
|
||||
val mbNewCache2 = nc.key match {
|
||||
case None => Right(newCache1)
|
||||
case Some(keyWithMaintainers) =>
|
||||
val gk = GlobalKey(nc.coinst.template, keyWithMaintainers.key)
|
||||
val gk = GlobalKey(
|
||||
nc.coinst.template,
|
||||
keyWithMaintainers.key.mapContractId(assertNoContractId))
|
||||
newCache1.activeKeys.get(gk) match {
|
||||
case None => Right(newCache1.addKey(gk, nc.coid))
|
||||
case Some(_) => Left(UniqueKeyViolation(gk))
|
||||
@ -1171,7 +1169,8 @@ object Ledger {
|
||||
nc.key match {
|
||||
case None => newCache0_1
|
||||
case Some(key) =>
|
||||
newCache0_1.removeKey(GlobalKey(ex.templateId, key.key))
|
||||
newCache0_1.removeKey(
|
||||
GlobalKey(ex.templateId, key.key.mapContractId(assertNoContractId)))
|
||||
}
|
||||
} else newCache0
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
DAML-LF Transaction Specification
|
||||
=================================
|
||||
|
||||
**version 8, 26 June 2019**
|
||||
**version 9, 13 January 2020**
|
||||
|
||||
This specification, in concert with the ``transaction.proto``
|
||||
machine-readable definition, defines a format for _transactions_, to be
|
||||
@ -161,6 +161,8 @@ This table lists every version of this specification in ascending order
|
||||
+--------------------+-----------------+
|
||||
| 8 | 2019-06-26 |
|
||||
+--------------------+-----------------+
|
||||
| 9 | 2020-01-13 |
|
||||
+--------------------+-----------------+
|
||||
|
||||
message Transaction
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
@ -282,6 +284,9 @@ In this version, these fields are included:
|
||||
|
||||
``maintainers`` must be non-empty.
|
||||
|
||||
The key may not contain contract IDs.
|
||||
|
||||
|
||||
message NodeCreate
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@ -501,7 +506,14 @@ Containing the result of the exercised choice.
|
||||
*since version 8*
|
||||
|
||||
New optional field `contract_key` is now set when the exercised
|
||||
contract has a contract key defined.
|
||||
contract has a contract key defined. The key may not contain contract IDs.
|
||||
|
||||
*since version 9*
|
||||
|
||||
New optional field `key_with_maintainers` is now set when the exercised
|
||||
contract has a contract key defined. The `contract_key` field is
|
||||
not used any more.
|
||||
|
||||
|
||||
message NodeLookupByKey
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -0,0 +1 @@
|
||||
Error: CRASH: Unexpected contract id in key: AbsoluteContractId(0:0)
|
@ -0,0 +1,28 @@
|
||||
-- Copyright (c) 2020 The DAML Authors. All rights reserved.
|
||||
-- SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
daml 1.2
|
||||
module Test where
|
||||
|
||||
-- Test that values of contract keys may not contain contract IDs
|
||||
|
||||
template Simple
|
||||
with
|
||||
p: Party
|
||||
where
|
||||
signatory p
|
||||
|
||||
template KeyWithContractId
|
||||
with
|
||||
p: Party
|
||||
k: ContractId Simple
|
||||
where
|
||||
signatory p
|
||||
key (p, k): (Party, ContractId Simple)
|
||||
maintainer key._1
|
||||
|
||||
run = scenario do
|
||||
alice <- getParty "alice"
|
||||
cid <- submit alice $ create Simple with p = alice
|
||||
-- This should fail
|
||||
submit alice $ create KeyWithContractId with p = alice, k = cid
|
@ -338,6 +338,7 @@ object ValueGenerators {
|
||||
.map(ImmArray(_))
|
||||
exerciseResultValue <- versionedValueGen
|
||||
key <- versionedValueGen
|
||||
maintainers <- genNonEmptyParties
|
||||
} yield
|
||||
NodeExercises(
|
||||
targetCoid,
|
||||
@ -352,7 +353,7 @@ object ValueGenerators {
|
||||
actingParties,
|
||||
children,
|
||||
Some(exerciseResultValue),
|
||||
Some(key)
|
||||
Some(KeyWithMaintainers(key, maintainers))
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
// * 6: removal of controllers in exercise nodes
|
||||
// * 7: new field return_value in NodeExercise
|
||||
// * 8: new field contract_key in NodeExercise
|
||||
// * 9: new field key_maintainers in NodeExercise
|
||||
syntax = "proto3";
|
||||
|
||||
package com.digitalasset.daml.lf.transaction;
|
||||
@ -90,6 +91,7 @@ message NodeExercise {
|
||||
com.digitalasset.daml.lf.value.ContractId contract_id_struct = 11;
|
||||
com.digitalasset.daml.lf.value.VersionedValue return_value = 12;
|
||||
com.digitalasset.daml.lf.value.VersionedValue contract_key = 13; // optional
|
||||
KeyWithMaintainers key_with_maintainers = 14; // optional
|
||||
}
|
||||
|
||||
message NodeLookupByKey {
|
||||
|
@ -4,7 +4,7 @@
|
||||
package com.digitalasset.daml.lf.transaction
|
||||
import com.digitalasset.daml.lf.data.{ImmArray, ScalazEqual}
|
||||
import com.digitalasset.daml.lf.data.Ref._
|
||||
import com.digitalasset.daml.lf.value.Value.{AbsoluteContractId, ContractInst, VersionedValue}
|
||||
import com.digitalasset.daml.lf.value.Value.{ContractInst, VersionedValue}
|
||||
|
||||
import scala.language.higherKinds
|
||||
import scalaz.Equal
|
||||
@ -109,7 +109,7 @@ object Node {
|
||||
controllers: Set[Party],
|
||||
children: ImmArray[Nid],
|
||||
exerciseResult: Option[Val],
|
||||
key: Option[Val])
|
||||
key: Option[KeyWithMaintainers[Val]])
|
||||
extends GenNode[Nid, Cid, Val] {
|
||||
override def mapContractIdAndValue[Cid2, Val2](
|
||||
f: Cid => Cid2,
|
||||
@ -118,7 +118,7 @@ object Node {
|
||||
targetCoid = f(targetCoid),
|
||||
chosenValue = g(chosenValue),
|
||||
exerciseResult = exerciseResult.map(g),
|
||||
key = key.map(g))
|
||||
key = key.map(_.mapValue(g)))
|
||||
|
||||
override def mapNodeId[Nid2](f: Nid => Nid2): NodeExercises[Nid2, Cid, Val] =
|
||||
copy(
|
||||
@ -147,7 +147,7 @@ object Node {
|
||||
signatories: Set[Party],
|
||||
children: ImmArray[Nid],
|
||||
exerciseResult: Option[Val],
|
||||
key: Option[Val]): NodeExercises[Nid, Cid, Val] =
|
||||
key: Option[KeyWithMaintainers[Val]]): NodeExercises[Nid, Cid, Val] =
|
||||
NodeExercises(
|
||||
targetCoid,
|
||||
templateId,
|
||||
@ -255,7 +255,7 @@ object Node {
|
||||
/** Useful in various circumstances -- basically this is what a ledger implementation must use as
|
||||
* a key.
|
||||
*/
|
||||
case class GlobalKey(templateId: Identifier, key: VersionedValue[AbsoluteContractId])
|
||||
case class GlobalKey(templateId: Identifier, key: VersionedValue[Nothing])
|
||||
|
||||
sealed trait WithTxValue2[F[+ _, + _]] {
|
||||
type WithTxValue[+Cid] = F[Cid, Transaction.Value[Cid]]
|
||||
|
@ -16,7 +16,6 @@ import scala.annotation.tailrec
|
||||
import scala.annotation.unchecked.uncheckedVariance
|
||||
import scala.collection.breakOut
|
||||
import scala.collection.immutable.{SortedMap, TreeMap}
|
||||
import scala.util.Try
|
||||
|
||||
case class VersionedTransaction[Nid, Cid](
|
||||
version: TransactionVersion,
|
||||
@ -455,7 +454,7 @@ object Transaction {
|
||||
case class ExercisesContext(
|
||||
targetId: TContractId,
|
||||
templateId: TypeConName,
|
||||
contractKey: Option[Value[TContractId]],
|
||||
contractKey: Option[KeyWithMaintainers[Value[Nothing]]],
|
||||
choiceId: ChoiceName,
|
||||
optLocation: Option[Location],
|
||||
consuming: Boolean,
|
||||
@ -587,7 +586,7 @@ object Transaction {
|
||||
optLocation: Option[Location],
|
||||
signatories: Set[Party],
|
||||
stakeholders: Set[Party],
|
||||
key: Option[KeyWithMaintainers[Value[TContractId]]]
|
||||
key: Option[KeyWithMaintainers[Value[Nothing]]]
|
||||
): Either[String, (TContractId, PartialTransaction)] = {
|
||||
val serializableErrs = serializable(coinst.arg)
|
||||
if (serializableErrs.nonEmpty) {
|
||||
@ -610,19 +609,9 @@ object Transaction {
|
||||
// active keys
|
||||
key match {
|
||||
case None => Right((cid, ptx))
|
||||
case Some(k) =>
|
||||
// TODO is there a nicer way of doing this?
|
||||
val mbNoRels =
|
||||
Try(k.key.mapContractId {
|
||||
case abs: AbsoluteContractId => abs
|
||||
case rel: RelativeContractId =>
|
||||
throw new RuntimeException(
|
||||
s"Trying to create contract key with relative contract id $rel")
|
||||
}).toEither.left.map(_.getMessage)
|
||||
mbNoRels.map { noRels =>
|
||||
val gk = GlobalKey(coinst.template, noRels)
|
||||
(cid, ptx.copy(keys = ptx.keys.updated(gk, Some(cid))))
|
||||
}
|
||||
case Some(kWithM) =>
|
||||
val ck = GlobalKey(coinst.template, kWithM.key)
|
||||
Right((cid, ptx.copy(keys = ptx.keys.updated(ck, Some(cid)))))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -654,7 +643,7 @@ object Transaction {
|
||||
def insertLookup(
|
||||
templateId: TypeConName,
|
||||
optLocation: Option[Location],
|
||||
key: KeyWithMaintainers[Value.VersionedValue[Nothing]],
|
||||
key: KeyWithMaintainers[Value[Nothing]],
|
||||
result: Option[TContractId]
|
||||
): PartialTransaction =
|
||||
insertLeafNode(_ => NodeLookupByKey(templateId, optLocation, key, result))._2
|
||||
@ -669,7 +658,7 @@ object Transaction {
|
||||
signatories: Set[Party],
|
||||
stakeholders: Set[Party],
|
||||
controllers: Set[Party],
|
||||
mbKey: Option[Value[AbsoluteContractId]],
|
||||
mbKey: Option[KeyWithMaintainers[Value[Nothing]]],
|
||||
chosenValue: Value[TContractId]
|
||||
): Either[String, PartialTransaction] = {
|
||||
val serializableErrs = serializable(chosenValue)
|
||||
@ -705,7 +694,8 @@ object Transaction {
|
||||
// inactive as soon as you exercise it. therefore, mark it as consumed now.
|
||||
consumedBy = if (consuming) consumedBy.updated(targetId, nextNodeId) else consumedBy,
|
||||
keys = mbKey match {
|
||||
case Some(key) if consuming => keys.updated(GlobalKey(templateId, key), None)
|
||||
case Some(kWithM) if consuming =>
|
||||
keys.updated(GlobalKey(templateId, kWithM.key), None)
|
||||
case _ => keys
|
||||
},
|
||||
)
|
||||
|
@ -106,7 +106,8 @@ object TransactionCoder {
|
||||
minKeyOrLookupByKey,
|
||||
minNoControllers,
|
||||
minExerciseResult,
|
||||
minContractKeyInExercise
|
||||
minContractKeyInExercise,
|
||||
minMaintainersInExercise
|
||||
}
|
||||
node match {
|
||||
case c: NodeCreate[Cid, Val] =>
|
||||
@ -193,16 +194,22 @@ object TransactionCoder {
|
||||
s"Trying to encode transaction of version $transactionVersion, which requires the exercise return value, but did not get exercise return value in node."))
|
||||
case (_, true) => Right(())
|
||||
}
|
||||
_ <- (e.key, transactionVersion precedes minContractKeyInExercise) match {
|
||||
case (Some(k), false) =>
|
||||
encodeVal(k).map { encodedKey =>
|
||||
exBuilder.setContractKey(encodedKey._2)
|
||||
_ <- Right(
|
||||
e.key
|
||||
.map { kWithM =>
|
||||
if (transactionVersion precedes minContractKeyInExercise) ()
|
||||
else if (transactionVersion precedes minMaintainersInExercise) {
|
||||
encodeVal(kWithM.key).map { encodedKey =>
|
||||
exBuilder.setContractKey(encodedKey._2)
|
||||
}
|
||||
} else
|
||||
encodeKeyWithMaintainers(encodeVal, kWithM).map {
|
||||
case (_, encodedKey) =>
|
||||
exBuilder.setKeyWithMaintainers(encodedKey)
|
||||
}
|
||||
()
|
||||
}
|
||||
case (None, _) | (Some(_), true) =>
|
||||
Right(())
|
||||
|
||||
}
|
||||
.getOrElse(()))
|
||||
} yield nodeBuilder.setExercise(exBuilder).build()
|
||||
|
||||
case nlbk: NodeLookupByKey[Cid, Val] =>
|
||||
@ -232,9 +239,9 @@ object TransactionCoder {
|
||||
keyWithMaintainers: TransactionOuterClass.KeyWithMaintainers)
|
||||
: Either[DecodeError, KeyWithMaintainers[Val]] =
|
||||
for {
|
||||
mainteners <- toPartySet(keyWithMaintainers.getMaintainersList)
|
||||
maintainers <- toPartySet(keyWithMaintainers.getMaintainersList)
|
||||
key <- decodeVal(keyWithMaintainers.getKey())
|
||||
} yield KeyWithMaintainers(key, mainteners)
|
||||
} yield KeyWithMaintainers(key, maintainers)
|
||||
|
||||
/**
|
||||
* read a [[GenNode[Nid, Cid]] from protobuf
|
||||
@ -258,7 +265,8 @@ object TransactionCoder {
|
||||
minKeyOrLookupByKey,
|
||||
minNoControllers,
|
||||
minExerciseResult,
|
||||
minContractKeyInExercise
|
||||
minContractKeyInExercise,
|
||||
minMaintainersInExercise
|
||||
}
|
||||
protoNode.getNodeTypeCase match {
|
||||
case NodeTypeCase.CREATE =>
|
||||
@ -310,11 +318,23 @@ object TransactionCoder {
|
||||
Left(DecodeError(txVersion, isTooOldFor = "exercise result"))
|
||||
else Right(None)
|
||||
} else decodeVal(protoExe.getReturnValue).map(Some(_))
|
||||
contractKey <- if (protoExe.hasContractKey) {
|
||||
hasKeyWithMaintainersField = (protoExe.getKeyWithMaintainers != TransactionOuterClass.KeyWithMaintainers.getDefaultInstance)
|
||||
keyWithMaintainers <- if (protoExe.hasContractKey) {
|
||||
if (txVersion precedes minContractKeyInExercise)
|
||||
Left(DecodeError(txVersion, isTooOldFor = "contract key in exercise"))
|
||||
else if (!(txVersion precedes minMaintainersInExercise))
|
||||
Left(DecodeError(
|
||||
s"contract key field in exercise must not be present for transactions of version $txVersion"))
|
||||
else if (hasKeyWithMaintainersField)
|
||||
Left(DecodeError(
|
||||
"an exercise may not contain both contract key and contract key with maintainers"))
|
||||
else
|
||||
decodeVal(protoExe.getContractKey).map(Some(_))
|
||||
decodeVal(protoExe.getContractKey).map(k => Some(KeyWithMaintainers(k, Set.empty)))
|
||||
} else if (hasKeyWithMaintainersField) {
|
||||
if (txVersion precedes minMaintainersInExercise)
|
||||
Left(DecodeError(txVersion, isTooOldFor = "NodeExercises maintainers"))
|
||||
else
|
||||
decodeKeyWithMaintainers(decodeVal, protoExe.getKeyWithMaintainers).map(k => Some(k))
|
||||
} else Right(None)
|
||||
|
||||
ni <- nodeId
|
||||
@ -354,7 +374,7 @@ object TransactionCoder {
|
||||
controllers = controllers,
|
||||
children = children,
|
||||
exerciseResult = rv,
|
||||
key = contractKey
|
||||
key = keyWithMaintainers
|
||||
))
|
||||
case NodeTypeCase.LOOKUP_BY_KEY =>
|
||||
val protoLookupByKey = protoNode.getLookupByKey
|
||||
|
@ -4,6 +4,7 @@
|
||||
package com.digitalasset.daml.lf
|
||||
package transaction
|
||||
|
||||
import com.digitalasset.daml.lf.transaction.Node.KeyWithMaintainers
|
||||
import com.digitalasset.daml.lf.value.Value.VersionedValue
|
||||
import com.digitalasset.daml.lf.value.ValueVersion
|
||||
|
||||
@ -22,6 +23,7 @@ object TransactionVersions
|
||||
private[transaction] val minNoControllers = TransactionVersion("6")
|
||||
private[transaction] val minExerciseResult = TransactionVersion("7")
|
||||
private[transaction] val minContractKeyInExercise = TransactionVersion("8")
|
||||
private[transaction] val minMaintainersInExercise = TransactionVersion("9")
|
||||
|
||||
def assignVersion(a: GenTransaction[_, _, _ <: VersionedValue[_]]): TransactionVersion = {
|
||||
require(a != null)
|
||||
@ -60,6 +62,17 @@ object TransactionVersions
|
||||
})
|
||||
minContractKeyInExercise
|
||||
else minVersion,
|
||||
if (a.nodes.values
|
||||
.exists {
|
||||
case ne: Node.NodeExercises[_, _, _] =>
|
||||
ne.key match {
|
||||
case Some(KeyWithMaintainers(key @ _, maintainers)) => maintainers.nonEmpty
|
||||
case _ => false
|
||||
}
|
||||
case _ => false
|
||||
})
|
||||
minMaintainersInExercise
|
||||
else minVersion,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ private[digitalasset] object VersionTimeline {
|
||||
This(That(TransactionVersion("8"))),
|
||||
Both(This(ValueVersion("5")), LanguageVersion(LMV.V1, "6")),
|
||||
Both(This(ValueVersion("6")), LanguageVersion(LMV.V1, "7")),
|
||||
This(That(TransactionVersion("9"))),
|
||||
// FIXME https://github.com/digital-asset/daml/issues/2256
|
||||
// * change the following line when LF 1.8 is frozen.
|
||||
// * do not insert line after this once until 1.8 is frozen.
|
||||
|
@ -301,6 +301,13 @@ class TransactionCoderSpec
|
||||
case _ => gn
|
||||
}
|
||||
|
||||
def withoutMaintainersInExercise[Nid, Cid, Val](
|
||||
gn: GenNode[Nid, Cid, Val]): GenNode[Nid, Cid, Val] =
|
||||
gn match {
|
||||
case ne: NodeExercises[Nid, Cid, Val] =>
|
||||
ne copy (key = ne.key.map(_.copy(maintainers = Set.empty)))
|
||||
case _ => gn
|
||||
}
|
||||
def transactionWithout[Nid: Ordering, Cid, Val](
|
||||
t: GenTransaction[Nid, Cid, Val],
|
||||
f: GenNode[Nid, Cid, Val] => GenNode[Nid, Cid, Val]): GenTransaction[Nid, Cid, Val] =
|
||||
@ -308,13 +315,17 @@ class TransactionCoderSpec
|
||||
|
||||
def minimalistTx[Nid: Ordering, Cid, Val](
|
||||
txvMin: TransactionVersion,
|
||||
tx: GenTransaction[Nid, Cid, Val]): GenTransaction[Nid, Cid, Val] =
|
||||
if (txvMin precedes minExerciseResult)
|
||||
transactionWithout(
|
||||
tx,
|
||||
(x: GenNode[Nid, Cid, Val]) => withoutExerciseResult(withoutContractKeyInExercise(x)))
|
||||
else if (txvMin precedes minContractKeyInExercise)
|
||||
transactionWithout(tx, (x: GenNode[Nid, Cid, Val]) => withoutContractKeyInExercise(x))
|
||||
else tx
|
||||
tx: GenTransaction[Nid, Cid, Val]): GenTransaction[Nid, Cid, Val] = {
|
||||
def condApply(before: TransactionVersion, f: GenNode[Nid, Cid, Val] => GenNode[Nid, Cid, Val])
|
||||
: GenNode[Nid, Cid, Val] => GenNode[Nid, Cid, Val] =
|
||||
if (txvMin precedes before) f else identity
|
||||
|
||||
transactionWithout(
|
||||
tx,
|
||||
condApply(minMaintainersInExercise, withoutMaintainersInExercise)
|
||||
.compose(condApply(minContractKeyInExercise, withoutContractKeyInExercise))
|
||||
.compose(condApply(minExerciseResult, withoutExerciseResult))
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ Here's an example of setting up a contract key for a bank account, to act as a b
|
||||
What can be a contract key
|
||||
**************************
|
||||
|
||||
The key can be an arbitrary expression but it **must** include every party that you want to use as a ``maintainer`` (see `Specifying maintainers`_ below).
|
||||
The key can be an arbitrary expression that does **not** contain contract IDs. However, it **must** include every party that you want to use as a ``maintainer`` (see `Specifying maintainers`_ below).
|
||||
|
||||
It's best to use simple types for your keys like ``Text`` or ``Int``, rather than a list or more complex type.
|
||||
|
||||
|
@ -121,7 +121,7 @@ private[state] object Conversions {
|
||||
throw Err
|
||||
.DecodeError("ContractKey", s"Cannot decode template id: ${key.getTemplateId}")
|
||||
),
|
||||
forceAbsoluteContractIds(
|
||||
forceNoContractIds(
|
||||
valDecoder(key.getKey)
|
||||
.fold(
|
||||
err =>
|
||||
@ -264,6 +264,10 @@ private[state] object Conversions {
|
||||
case acoid: AbsoluteContractId => acoid
|
||||
}
|
||||
|
||||
def forceNoContractIds(v: VersionedValue[ContractId]): VersionedValue[Nothing] =
|
||||
v.mapContractId(coid =>
|
||||
throw Err.InternalError(s"Contract identifier encountered in contract key! $coid"))
|
||||
|
||||
def contractIdStructOrStringToStateKey(
|
||||
entryId: DamlLogEntryId,
|
||||
coidString: String,
|
||||
|
@ -84,7 +84,7 @@ private[kvutils] object InputsAndEffects {
|
||||
case create: NodeCreate[ContractId, VersionedValue[ContractId]] =>
|
||||
create.key.fold(inputs) { keyWithM =>
|
||||
inputs + contractKeyToStateKey(
|
||||
GlobalKey(create.coinst.template, forceAbsoluteContractIds(keyWithM.key)))
|
||||
GlobalKey(create.coinst.template, forceNoContractIds(keyWithM.key)))
|
||||
} ++ partyInputs(create.signatories) ++ partyInputs(create.stakeholders)
|
||||
case exe: NodeExercises[_, ContractId, _] =>
|
||||
inputs ++ contractInputs(exe.targetCoid) ++ partyInputs(exe.stakeholders) ++ partyInputs(
|
||||
@ -93,7 +93,7 @@ private[kvutils] object InputsAndEffects {
|
||||
// We need both the contract key state and the contract state. The latter is used to verify
|
||||
// that the submitter can access the contract.
|
||||
l.result.fold(inputs)(inputs ++ contractInputs(_)) +
|
||||
contractKeyToStateKey(GlobalKey(l.templateId, forceAbsoluteContractIds(l.key.key)))
|
||||
contractKeyToStateKey(GlobalKey(l.templateId, forceNoContractIds(l.key.key)))
|
||||
}
|
||||
}
|
||||
.toList
|
||||
@ -120,7 +120,7 @@ private[kvutils] object InputsAndEffects {
|
||||
(contractKeyToStateKey(
|
||||
GlobalKey(
|
||||
create.coinst.template,
|
||||
forceAbsoluteContractIds(keyWithMaintainers.key))) ->
|
||||
forceNoContractIds(keyWithMaintainers.key))) ->
|
||||
DamlContractKeyState.newBuilder
|
||||
.setContractId(encodeRelativeContractId(
|
||||
entryId,
|
||||
@ -143,7 +143,7 @@ private[kvutils] object InputsAndEffects {
|
||||
key =>
|
||||
effects.updatedContractKeys +
|
||||
(contractKeyToStateKey(
|
||||
GlobalKey(exe.templateId, forceAbsoluteContractIds(key))) ->
|
||||
GlobalKey(exe.templateId, forceNoContractIds(key.key))) ->
|
||||
DamlContractKeyState.newBuilder.build)
|
||||
)
|
||||
)
|
||||
|
@ -202,13 +202,13 @@ object KeyValueCommitting {
|
||||
case TransactionOuterClass.Node.NodeTypeCase.EXERCISE =>
|
||||
val exe = node.getExercise
|
||||
val ckeyOrEmpty =
|
||||
if (exe.getConsuming && exe.hasContractKey)
|
||||
if (exe.getConsuming && exe.hasKeyWithMaintainers)
|
||||
List(
|
||||
DamlStateKey.newBuilder
|
||||
.setContractKey(
|
||||
DamlContractKey.newBuilder
|
||||
.setTemplateId(exe.getTemplateId)
|
||||
.setKey(exe.getContractKey))
|
||||
.setKey(exe.getKeyWithMaintainers.getKey))
|
||||
.build)
|
||||
else
|
||||
List.empty
|
||||
|
@ -185,7 +185,7 @@ private[kvutils] case class ProcessTransactionSubmission(
|
||||
(_nodeId, exe: NodeExercises[_, _, VersionedValue[ContractId]]))
|
||||
if exe.key.isDefined && exe.consuming =>
|
||||
val stateKey = Conversions.contractKeyToStateKey(
|
||||
GlobalKey(exe.templateId, Conversions.forceAbsoluteContractIds(exe.key.get)))
|
||||
GlobalKey(exe.templateId, Conversions.forceNoContractIds(exe.key.get.key)))
|
||||
(allUnique, existingKeys - stateKey)
|
||||
|
||||
case (
|
||||
@ -193,9 +193,7 @@ private[kvutils] case class ProcessTransactionSubmission(
|
||||
(_nodeId, create: NodeCreate[_, VersionedValue[ContractId]]))
|
||||
if create.key.isDefined =>
|
||||
val stateKey = Conversions.contractKeyToStateKey(
|
||||
GlobalKey(
|
||||
create.coinst.template,
|
||||
Conversions.forceAbsoluteContractIds(create.key.get.key)))
|
||||
GlobalKey(create.coinst.template, Conversions.forceNoContractIds(create.key.get.key)))
|
||||
|
||||
(allUnique && !existingKeys.contains(stateKey), existingKeys + stateKey)
|
||||
|
||||
@ -242,7 +240,7 @@ private[kvutils] case class ProcessTransactionSubmission(
|
||||
Conversions.encodeContractKey(
|
||||
GlobalKey(
|
||||
createNode.coinst.template,
|
||||
Conversions.forceAbsoluteContractIds(keyWithMaintainers.key)
|
||||
Conversions.forceNoContractIds(keyWithMaintainers.key)
|
||||
)
|
||||
))
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ object ActiveLedgerState {
|
||||
contract: ContractInst[VersionedValue[AbsoluteContractId]],
|
||||
witnesses: Set[Party],
|
||||
divulgences: Map[Party, TransactionIdString], // for each party, the transaction id at which the contract was divulged
|
||||
key: Option[KeyWithMaintainers[VersionedValue[AbsoluteContractId]]],
|
||||
key: Option[KeyWithMaintainers[VersionedValue[Nothing]]],
|
||||
signatories: Set[Party],
|
||||
observers: Set[Party],
|
||||
agreementText: String)
|
||||
|
@ -160,7 +160,8 @@ class ActiveLedgerStateManager[ALS](initialState: => ALS)(
|
||||
.getOrElse(nodeId, Set.empty) diff nc.stakeholders).toList
|
||||
.map(p => p -> transactionId)
|
||||
.toMap,
|
||||
key = nc.key,
|
||||
key = nc.key.map(_.mapValue(_.mapContractId(coid =>
|
||||
throw new IllegalStateException(s"Contract ID $coid found in contract key")))),
|
||||
signatories = nc.signatories,
|
||||
observers = nc.stakeholders.diff(nc.signatories),
|
||||
agreementText = nc.coinst.agreementText
|
||||
@ -202,7 +203,9 @@ class ActiveLedgerStateManager[ALS](initialState: => ALS)(
|
||||
)
|
||||
case nlkup: N.NodeLookupByKey.WithTxValue[AbsoluteContractId] =>
|
||||
// Check that the stored lookup result matches the current result
|
||||
val gk = GlobalKey(nlkup.templateId, nlkup.key.key)
|
||||
val key = nlkup.key.key.mapContractId(coid =>
|
||||
throw new IllegalStateException(s"Contract ID $coid found in contract key"))
|
||||
val gk = GlobalKey(nlkup.templateId, key)
|
||||
val nodeParties = nlkup.key.maintainers
|
||||
|
||||
submitter match {
|
||||
|
@ -1356,6 +1356,7 @@ private class JdbcLedgerDao(
|
||||
val keyValue = valueSerializer
|
||||
.deserializeValue(ByteStreams.toByteArray(keyStream))
|
||||
.getOrElse(sys.error(s"failed to deserialize key value! cid:$coid"))
|
||||
.mapContractId(coid => sys.error(s"Found contract ID $coid in a contract key"))
|
||||
KeyWithMaintainers(keyValue, keyMaintainers)
|
||||
}),
|
||||
signatories,
|
||||
|
@ -61,6 +61,8 @@ class V3__Recompute_Key_Hash extends BaseJavaMigration {
|
||||
val key = ValueSerializer
|
||||
.deserializeValue(rows.getBytes("contract_key"))
|
||||
.fold(err => throw new IllegalArgumentException(err.errorMessage), identity)
|
||||
.mapContractId(coid =>
|
||||
throw new IllegalArgumentException(s"Found contract ID $coid in contract key"))
|
||||
|
||||
hasNext = rows.next()
|
||||
|
||||
|
@ -830,7 +830,10 @@ class JdbcLedgerDaoSpec
|
||||
children = ImmArray.empty,
|
||||
exerciseResult =
|
||||
Some(VersionedValue(ValueVersions.acceptedVersions.head, ValueUnit)),
|
||||
key = Some(VersionedValue(ValueVersions.acceptedVersions.head, ValueText(key)))
|
||||
key = Some(
|
||||
KeyWithMaintainers(
|
||||
VersionedValue(ValueVersions.acceptedVersions.head, ValueText(key)),
|
||||
Set(party)))
|
||||
)),
|
||||
ImmArray[EventId](s"event$id"),
|
||||
None
|
||||
|
@ -25,7 +25,7 @@ class KeyHasherSpec extends WordSpec with Matchers {
|
||||
)
|
||||
|
||||
private[this] def complexValue = {
|
||||
val builder = ImmArray.newBuilder[(Option[Name], Value[AbsoluteContractId])]
|
||||
val builder = ImmArray.newBuilder[(Option[Name], Value[Nothing])]
|
||||
builder += None -> ValueInt64(0)
|
||||
builder += None -> ValueInt64(123456)
|
||||
builder += None -> ValueInt64(-1)
|
||||
|
Loading…
Reference in New Issue
Block a user