[DAML-LF] Version new contractId in Proto values (#4460)

* version new contractId

CHANGELOG_BEGIN
CHANGELOG_END
This commit is contained in:
Remy 2020-02-13 20:45:46 +01:00 committed by GitHub
parent a440a3fabd
commit 8394f4ba7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 464 additions and 376 deletions

View File

@ -251,10 +251,13 @@ private[data] final class IdStringImpl extends IdString {
Either.cond(isA(s), s, s)
override def fromString(s: String): Either[String, ContractIdString] =
toEither(s).fold(ContractIdStringV0.fromString, ContractIdStringV1.fromString)
if (isA(s))
ContractIdStringV0.fromString(s)
else
ContractIdStringV1.fromString(s)
override def isA(s: ContractIdString): Boolean =
s.startsWith("$0")
!isB(s)
override def toA(s: ContractIdString): Option[ContractIdStringV0] =
Some(s).filter(isA)
@ -263,7 +266,7 @@ private[data] final class IdStringImpl extends IdString {
toA(s).getOrElse(throw new IllegalArgumentException("expect V0 ContractId get V1"))
override def isB(s: ContractIdString): Boolean =
!isA(s)
s.startsWith("$0")
override def toB(s: ContractIdString): Option[ContractIdStringV1] =
Some(s).filter(isB)

View File

@ -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, 9; 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, 10; Value versions: 1, 2, 3, 4, 5, 6, 7"
}
"toString returns the same value as show" in {

View File

@ -1048,7 +1048,9 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
(Some[Name]("fetcher"), ValueParty(party)),
)
def makeContract[Cid](tid: Ref.QualifiedName, targs: ImmArray[(Option[Name], Value[Cid])]) =
def makeContract[Cid <: ContractId](
tid: Ref.QualifiedName,
targs: ImmArray[(Option[Name], Value[Cid])]) =
ContractInst(
TypeConName(basicTestsPkgId, tid),
assertAsVersionedValue(ValueRecord(Some(Identifier(basicTestsPkgId, tid)), targs)),

View File

@ -63,6 +63,7 @@ object LanguageVersion {
val typeRep = v1_7
val genMap = v1_dev
val typeSynonyms = v1_dev
val scenarioMustFailAtMsg = v1_dev
/** Unstable, experimental features. This should stay in 1.dev forever.
* Features implemented with this flag should be moved to a separate

View File

@ -1,7 +1,8 @@
// Copyright (c) 2020 The DAML Authors. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.digitalasset.daml.lf.value
package com.digitalasset.daml.lf
package value
import com.digitalasset.daml.lf.data.Ref._
import com.digitalasset.daml.lf.data._
@ -151,19 +152,31 @@ object ValueGenerators {
def valueGenMapGen: Gen[ValueGenMap[ContractId]] = valueGenMapGen(0)
def coidGen: Gen[ContractId] = {
val genRel: Gen[ContractId] =
Arbitrary.arbInt.arbitrary.map(i => RelativeContractId(Tx.NodeId(i)))
val genAbs: Gen[ContractId] =
Gen.zip(Gen.alphaChar, Gen.alphaStr) map {
case (h, t) => AbsoluteContractId(toContractId(h +: t))
}
Gen.frequency((1, genRel), (3, genAbs))
private val genRel: Gen[ContractId] =
Arbitrary.arbInt.arbitrary.map(i => RelativeContractId(Tx.NodeId(i)))
private val genAbsV0: Gen[ContractId] =
Gen.zip(Gen.alphaChar, Gen.alphaStr) map {
case (h, t) => AbsoluteContractId(toContractId(h +: t))
}
private val genAbsV1: Gen[ContractId] = {
val random = crypto.Hash.secureRandom
Gen.alphaNumStr.map(
suffix =>
AbsoluteContractId(
Ref.ContractIdStringV1.assertFromString("$0" + random().toHexaString + suffix)))
}
def coidValueGen: Gen[ValueContractId[ContractId]] = {
def coidGenV0: Gen[ContractId] =
Gen.frequency((1, genRel), (3, genAbsV0))
def coidValueGenV0: Gen[ValueContractId[ContractId]] =
coidGenV0.map(ValueContractId(_))
def coidGen: Gen[ContractId] =
Gen.frequency((2, genRel), (3, genAbsV0), (3, genAbsV1))
def coidValueGen: Gen[ValueContractId[ContractId]] =
coidGen.map(ValueContractId(_))
}
private def valueGen(nesting: Int): Gen[Value[ContractId]] = {
Gen.sized(sz => {
@ -214,7 +227,7 @@ object ValueGenerators {
def versionedValueGen: Gen[VersionedValue[ContractId]] =
for {
value <- valueGen
minVersion = ValueVersions.assertAssignVersion(value)
minVersion = ValueVersions.assertAssignVersion(ValueVersions.VersionCid, value)
version <- valueVersionGen(minVersion)
} yield VersionedValue(version, value)

View File

@ -9,7 +9,7 @@ import com.digitalasset.daml.lf.data.Ref.{Name, Party}
import com.digitalasset.daml.lf.transaction.Node._
import VersionTimeline.Implicits._
import com.digitalasset.daml.lf.value.Value
import com.digitalasset.daml.lf.value.Value.VersionedValue
import com.digitalasset.daml.lf.value.Value.{ContractId, VersionedValue}
import com.digitalasset.daml.lf.value.{ValueCoder, ValueOuterClass, ValueVersion}
import com.digitalasset.daml.lf.value.ValueCoder.{DecodeError, EncodeError}
import com.google.protobuf.ProtocolStringList
@ -24,8 +24,6 @@ import scala.collection.immutable.HashMap
object TransactionCoder {
import ValueCoder.{DecodeCid, EncodeCid, codecContractId}
abstract class EncodeNid[-Nid] private[lf] {
def asString(id: Nid): String
}
@ -77,7 +75,7 @@ object TransactionCoder {
* @return protobuf wire format contract instance
*/
def encodeContractInstance[Cid](
encodeCid: EncodeCid[Cid],
encodeCid: ValueCoder.EncodeCid[Cid],
coinst: Value.ContractInst[Value.VersionedValue[Cid]],
): Either[EncodeError, TransactionOuterClass.ContractInstance] =
encodeValue(encodeCid, coinst.arg).map {
@ -98,7 +96,7 @@ object TransactionCoder {
* @return contract instance value
*/
def decodeContractInstance[Cid](
decodeCid: DecodeCid[Cid],
decodeCid: ValueCoder.DecodeCid[Cid],
protoCoinst: TransactionOuterClass.ContractInstance,
): Either[DecodeError, Value.ContractInst[Value.VersionedValue[Cid]]] =
for {
@ -107,7 +105,7 @@ object TransactionCoder {
} yield Value.ContractInst(id, value, (protoCoinst.getAgreement))
private def encodeKeyWithMaintainers[Cid](
encodeCid: EncodeCid[Cid],
encodeCid: ValueCoder.EncodeCid[Cid],
key: KeyWithMaintainers[Value.VersionedValue[Cid]],
): Either[EncodeError, (ValueVersion, TransactionOuterClass.KeyWithMaintainers)] =
encodeValue(encodeCid, key.key).map {
@ -134,7 +132,7 @@ object TransactionCoder {
*/
def encodeNode[Nid, Cid](
encodeNid: EncodeNid[Nid],
encodeCid: EncodeCid[Cid],
encodeCid: ValueCoder.EncodeCid[Cid],
transactionVersion: TransactionVersion,
nodeId: Nid,
node: GenNode[Nid, Cid, Value.VersionedValue[Cid]],
@ -149,28 +147,31 @@ object TransactionCoder {
}
node match {
case nc @ NodeCreate(_, _, _, _, _, _, _) =>
encodeContractInstance(encodeCid, nc.coinst).flatMap { inst =>
val createBuilder = TransactionOuterClass.NodeCreate
val createBuilder =
TransactionOuterClass.NodeCreate
.newBuilder()
.setContractIdOrStruct(encodeCid, transactionVersion, nc.coid)(
_.setContractId(_),
_.setContractIdStruct(_),
)
.setContractInstance(inst)
.addAllStakeholders(nc.stakeholders.toSet[String].asJava)
.addAllSignatories(nc.signatories.toSet[String].asJava)
nc.key match {
case None => Right(nodeBuilder.setCreate(createBuilder).build())
for {
encodedCid <- encodeCid.encode(transactionVersion, nc.coid)
inst <- encodeContractInstance(encodeCid, nc.coinst)
optKey <- nc.key match {
case None => Right(None)
case Some(key) =>
if (transactionVersion precedes minKeyOrLookupByKey)
Left(EncodeError(transactionVersion, isTooOldFor = "NodeCreate's `key` field"))
else
encodeKeyWithMaintainers(encodeCid, key).map {
case (_, encodedKey) =>
createBuilder.setKeyWithMaintainers(encodedKey)
nodeBuilder.setCreate(createBuilder).build()
}
encodeKeyWithMaintainers(encodeCid, key).map(Some(_))
}
} yield {
encodedCid.fold(createBuilder.setContractId, createBuilder.setContractIdStruct)
createBuilder.setContractInstance(inst)
optKey.foreach {
case (_, encodedKey) => createBuilder.setKeyWithMaintainers(encodedKey)
}
nodeBuilder.setCreate(createBuilder).build()
}
case nf @ NodeFetch(_, _, _, _, _, _) =>
@ -180,23 +181,22 @@ object TransactionCoder {
)
val fetchBuilder = TransactionOuterClass.NodeFetch
.newBuilder()
.setContractIdOrStruct(encodeCid, transactionVersion, nf.coid)(
_.setContractId(_),
_.setContractIdStruct(_),
)
.setTemplateId(etid)
.setValueVersion(vversion.protoValue)
.addAllStakeholders(nf.stakeholders.toSet[String].asJava)
.addAllSignatories(nf.signatories.toSet[String].asJava)
if (transactionVersion precedes TransactionVersions.minFetchActors) {
if (nf.actingParties.nonEmpty)
Left(EncodeError(transactionVersion, isTooOldFor = "NodeFetch actors"))
else Right(nodeBuilder.setFetch(fetchBuilder).build())
} else {
val fetchBuilderWithActors =
fetchBuilder.addAllActors(nf.actingParties.getOrElse(Set.empty).toSet[String].asJava)
Right(nodeBuilder.setFetch(fetchBuilderWithActors).build())
for {
encodedCid <- encodeCid.encode(transactionVersion, nf.coid)
actors <- Either.cond(
nf.actingParties.isEmpty || !(transactionVersion precedes TransactionVersions.minFetchActors),
nf.actingParties.getOrElse(Set.empty),
EncodeError(transactionVersion, isTooOldFor = "NodeFetch actors")
)
} yield {
encodedCid.fold(fetchBuilder.setContractId, fetchBuilder.setContractIdStruct)
actors.foreach(fetchBuilder.addActors)
nodeBuilder.setFetch(fetchBuilder).build()
}
case ne @ NodeExercises(_, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
@ -210,38 +210,31 @@ object TransactionCoder {
.setTemplateId(ValueCoder.encodeIdentifier(ne.templateId, Some(vversion))._2)
.setChosenValue(arg)
.setConsuming(ne.consuming)
.setContractIdOrStruct(encodeCid, transactionVersion, ne.targetCoid)(
_.setContractId(_),
_.setContractIdStruct(_),
)
.addAllActors(ne.actingParties.toSet[String].asJava)
.addAllChildren(ne.children.map(encodeNid.asString).toList.asJava)
.addAllSignatories(ne.signatories.toSet[String].asJava)
.addAllStakeholders(ne.stakeholders.toSet[String].asJava)
_ <- if (transactionVersion precedes minNoControllers) {
if (ne.controllers == ne.actingParties) {
exBuilder.addAllControllers(ne.controllers.toSet[String].asJava)
Right(())
} else {
Left(
EncodeError(
s"As of version $minNoControllers, the controllers and actingParties of an exercise node _must_ be the same, but I got ${ne.controllers} as controllers and ${ne.actingParties} as actingParties.",
),
encodedCid <- encodeCid.encode(transactionVersion, ne.targetCoid)
controllers <- if (transactionVersion precedes minNoControllers)
Either.cond(
ne.controllers == ne.actingParties,
ne.controllers,
EncodeError(
s"As of version $minNoControllers, the controllers and actingParties of an exercise node _must_ be the same, but I got ${ne.controllers} as controllers and ${ne.actingParties} as actingParties.",
)
}
} else Right(())
_ <- (retValue, transactionVersion precedes minExerciseResult) match {
case (Some(rv), false) =>
exBuilder.setReturnValue(rv._2)
Right(())
case (None, false) =>
Left(
EncodeError(
s"Trying to encode transaction of version $transactionVersion, which requires the exercise return value, but did not get exercise return value in node.",
),
)
else
Right(Set.empty)
result <- if (transactionVersion precedes minExerciseResult)
Right(None)
else
Either.cond(
test = retValue.nonEmpty,
right = retValue,
left = EncodeError(
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(())
}
)
_ <- Right(
ne.key
.map { kWithM =>
@ -259,32 +252,36 @@ object TransactionCoder {
}
.getOrElse(()),
)
} yield nodeBuilder.setExercise(exBuilder).build()
} yield {
encodedCid.fold(exBuilder.setContractId, exBuilder.setContractIdStruct)
controllers.foreach(exBuilder.addControllers)
result.foreach { case (_, v) => exBuilder.setReturnValue(v) }
nodeBuilder.setExercise(exBuilder).build()
}
case nlbk @ NodeLookupByKey(_, _, _, _) =>
if (transactionVersion precedes minKeyOrLookupByKey)
Left(EncodeError(transactionVersion, isTooOldFor = "NodeLookupByKey transaction nodes"))
else
encodeKeyWithMaintainers(encodeCid, nlbk.key).map {
case (vversion, key) =>
val nlbkBuilder = TransactionOuterClass.NodeLookupByKey
.newBuilder()
.setTemplateId(ValueCoder.encodeIdentifier(nlbk.templateId, Some(vversion))._2)
.setKeyWithMaintainers(key)
nlbk.result match {
case None => ()
case Some(result) =>
nlbkBuilder.setContractIdOrStruct(encodeCid, transactionVersion, result)(
_.setContractId(_),
_.setContractIdStruct(_),
)
}
nodeBuilder.setLookupByKey(nlbkBuilder).build()
}
val nlbkBuilder = TransactionOuterClass.NodeLookupByKey.newBuilder()
for {
_ <- Either.cond(
test = !(transactionVersion precedes minKeyOrLookupByKey),
right = (),
left =
EncodeError(transactionVersion, isTooOldFor = "NodeLookupByKey transaction nodes")
)
versionAndKey <- encodeKeyWithMaintainers(encodeCid, nlbk.key)
encodedCid <- nlbk.result traverseU (cid => encodeCid.encode(transactionVersion, cid))
} yield {
val (vversion, key) = versionAndKey
nlbkBuilder
.setTemplateId(ValueCoder.encodeIdentifier(nlbk.templateId, Some(vversion))._2)
.setKeyWithMaintainers(key)
encodedCid.foreach(_.fold(nlbkBuilder.setContractId, nlbkBuilder.setContractIdStruct))
nodeBuilder.setLookupByKey(nlbkBuilder).build()
}
}
}
private def decodeKeyWithMaintainers[Cid](
decodeCid: DecodeCid[Cid],
decodeCid: ValueCoder.DecodeCid[Cid],
keyWithMaintainers: TransactionOuterClass.KeyWithMaintainers,
): Either[DecodeError, KeyWithMaintainers[Value.VersionedValue[Cid]]] =
for {
@ -303,7 +300,7 @@ object TransactionCoder {
*/
def decodeNode[Nid, Cid](
decodeNid: DecodeNid[Nid],
decodeCid: DecodeCid[Cid],
decodeCid: ValueCoder.DecodeCid[Cid],
txVersion: TransactionVersion,
protoNode: TransactionOuterClass.Node,
): Either[DecodeError, (Nid, GenNode[Nid, Cid, Value.VersionedValue[Cid]])] = {
@ -321,9 +318,10 @@ object TransactionCoder {
for {
ni <- nodeId
protoCreate = protoNode.getCreate
c <- protoCreate.decodeContractIdOrStruct(decodeCid, txVersion)(
_.getContractId,
_.getContractIdStruct,
c <- decodeCid.decode(
txVersion,
protoCreate.getContractId,
protoCreate.getContractIdStruct,
)
ci <- decodeContractInstance(decodeCid, protoCreate.getContractInstance)
stakeholders <- toPartySet(protoCreate.getStakeholdersList)
@ -339,10 +337,7 @@ object TransactionCoder {
for {
ni <- nodeId
templateId <- ValueCoder.decodeIdentifier(protoFetch.getTemplateId)
c <- protoFetch.decodeContractIdOrStruct(decodeCid, txVersion)(
_.getContractId,
_.getContractIdStruct,
)
c <- decodeCid.decode(txVersion, protoFetch.getContractId, protoFetch.getContractIdStruct)
actingPartiesSet <- toPartySet(protoFetch.getActorsList)
_ <- if ((txVersion precedes TransactionVersions.minFetchActors) && actingPartiesSet.nonEmpty)
Left(DecodeError(txVersion, isTooOldFor = "NodeFetch actors"))
@ -395,9 +390,10 @@ object TransactionCoder {
} else Right(None)
ni <- nodeId
targetCoid <- protoExe.decodeContractIdOrStruct(decodeCid, txVersion)(
_.getContractId,
_.getContractIdStruct,
targetCoid <- decodeCid.decode(
txVersion,
protoExe.getContractId,
protoExe.getContractIdStruct,
)
children <- childrenOrError
cv <- decodeValue(decodeCid, protoExe.getChosenValue)
@ -445,9 +441,10 @@ object TransactionCoder {
ni <- nodeId
templateId <- ValueCoder.decodeIdentifier(protoLookupByKey.getTemplateId)
key <- decodeKeyWithMaintainers(decodeCid, protoLookupByKey.getKeyWithMaintainers)
cid <- protoLookupByKey.decodeOptionalContractIdOrStruct(decodeCid, txVersion)(
_.getContractId,
_.getContractIdStruct,
cid <- decodeCid.decodeOptional(
txVersion,
protoLookupByKey.getContractId,
protoLookupByKey.getContractIdStruct,
)
} yield (ni, NodeLookupByKey[Cid, Value.VersionedValue[Cid]](templateId, None, key, cid))
case NodeTypeCase.NODETYPE_NOT_SET => Left(DecodeError("Unset Node type"))
@ -465,9 +462,9 @@ object TransactionCoder {
* @tparam Cid contract id type
* @return protobuf encoded transaction
*/
def encodeTransaction[Nid, Cid](
def encodeTransaction[Nid, Cid <: ContractId](
encodeNid: EncodeNid[Nid],
encodeCid: EncodeCid[Cid],
encodeCid: ValueCoder.EncodeCid[Cid],
tx: GenTransaction[Nid, Cid, VersionedValue[Cid]],
): Either[EncodeError, TransactionOuterClass.Transaction] =
encodeTransactionWithCustomVersion(
@ -488,7 +485,7 @@ object TransactionCoder {
*/
private[transaction] def encodeTransactionWithCustomVersion[Nid, Cid](
encodeNid: EncodeNid[Nid],
encodeCid: EncodeCid[Cid],
encodeCid: ValueCoder.EncodeCid[Cid],
transaction: VersionedTransaction[Nid, Cid],
): Either[EncodeError, TransactionOuterClass.Transaction] = {
val tx = transaction.transaction
@ -522,7 +519,7 @@ object TransactionCoder {
})
}
private def decodeVersion(vs: String): Either[DecodeError, TransactionVersion] =
def decodeVersion(vs: String): Either[DecodeError, TransactionVersion] =
TransactionVersions
.isAcceptedVersion(vs)
.fold[Either[DecodeError, TransactionVersion]](
@ -544,7 +541,7 @@ object TransactionCoder {
*/
def decodeVersionedTransaction[Nid, Cid](
decodeNid: DecodeNid[Nid],
decodeCid: DecodeCid[Cid],
decodeCid: ValueCoder.DecodeCid[Cid],
protoTx: TransactionOuterClass.Transaction,
): Either[DecodeError, VersionedTransaction[Nid, Cid]] =
for {
@ -571,7 +568,7 @@ object TransactionCoder {
*/
private def decodeTransaction[Nid, Cid](
decodeNid: DecodeNid[Nid],
decodeCid: DecodeCid[Cid],
decodeCid: ValueCoder.DecodeCid[Cid],
txVersion: TransactionVersion,
protoTx: TransactionOuterClass.Transaction,
): Either[DecodeError, GenTransaction[Nid, Cid, Value.VersionedValue[Cid]]] = {

View File

@ -4,9 +4,10 @@
package com.digitalasset.daml.lf
package transaction
import com.digitalasset.daml.lf.data.Ref
import com.digitalasset.daml.lf.transaction.Node.KeyWithMaintainers
import com.digitalasset.daml.lf.value.Value.VersionedValue
import com.digitalasset.daml.lf.value.ValueVersion
import com.digitalasset.daml.lf.value.{Value, ValueVersion}
final case class TransactionVersion(protoValue: String)
@ -25,8 +26,16 @@ object TransactionVersions
private[transaction] val minExerciseResult = TransactionVersion("7")
private[transaction] val minContractKeyInExercise = TransactionVersion("8")
private[transaction] val minMaintainersInExercise = TransactionVersion("9")
private[transaction] val minContractIdV1 = TransactionVersion("10")
def assignVersion(a: GenTransaction[_, _, _ <: VersionedValue[_]]): TransactionVersion = {
private def isV1(cid: Value.ContractId) = cid match {
case Value.AbsoluteContractId(coid) => Ref.ContractIdString.isB(coid)
case _ => false
}
def assignVersion(
a: GenTransaction[_, Value.ContractId, VersionedValue[Value.ContractId]],
): TransactionVersion = {
require(a != null)
import VersionTimeline.Implicits._
@ -43,26 +52,30 @@ object TransactionVersions
case _: Node.NodeLookupByKey[_, _] => true
case _: Node.NodeFetch[_] | _: Node.NodeExercises[_, _, _] => false
}) minKeyOrLookupByKey
else minVersion,
else
minVersion,
// a NodeFetch with actingParties implies minimum version 5
if (a.nodes.values
.exists { case nf: Node.NodeFetch[_] => nf.actingParties.nonEmpty; case _ => false })
minFetchActors
else minVersion,
else
minVersion,
if (a.nodes.values
.exists {
case ne: Node.NodeExercises[_, _, _] => ne.exerciseResult.isDefined
case _ => false
})
minExerciseResult
else minVersion,
else
minVersion,
if (a.nodes.values
.exists {
case ne: Node.NodeExercises[_, _, _] => ne.key.isDefined
case _ => false
})
minContractKeyInExercise
else minVersion,
else
minVersion,
if (a.nodes.values
.exists {
case ne: Node.NodeExercises[_, _, _] =>
@ -73,7 +86,20 @@ object TransactionVersions
case _ => false
})
minMaintainersInExercise
else minVersion,
else
minVersion,
if (a.transactionSeed.isDefined || a.nodes.values.exists {
case Node.NodeCreate(nodeSeed, coid, _, _, _, _, _) =>
nodeSeed.isDefined || isV1(coid)
case Node.NodeExercises(nodeSeed, coid, _, _, _, _, _, _, _, _, _, _, _, _) =>
nodeSeed.isDefined || isV1(coid)
case Node.NodeFetch(cid, _, _, _, _, _) =>
isV1(cid)
case _ => false
})
minContractIdV1
else
minVersion
)
}
}

View File

@ -26,7 +26,7 @@ import scala.language.higherKinds
* version picker uses the timeline in order to describe changes to
* that same timeline.
*/
private[digitalasset] object VersionTimeline {
object VersionTimeline {
import LanguageVersion.Minor.Dev
import \&/.{Both, That, This}
@ -37,7 +37,7 @@ private[digitalasset] object VersionTimeline {
* it appeared in an earlier engine release. If two versions occur at the
* same index, they were both added in the same engine release.
*/
private[transaction] val inAscendingOrder: NonEmptyList[Release] =
private[lf] val inAscendingOrder: NonEmptyList[Release] =
NonEmptyList(
That(LanguageVersion(LMV.V0, "")),
That(LanguageVersion(LMV.V0, Dev)),
@ -59,7 +59,7 @@ private[digitalasset] object VersionTimeline {
// 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.
This(This(ValueVersion("7"))),
This(Both(ValueVersion("7"), TransactionVersion("10"))),
// add new versions above this line (but see more notes below)
That(LanguageVersion(LMV.V1, Dev)),
// do *not* backfill to make more Boths, because such would
@ -74,7 +74,7 @@ private[digitalasset] object VersionTimeline {
// supported by this release".
)
def foldRelease[Z: Semigroup](
private[lf] def foldRelease[Z: Semigroup](
av: Release,
)(v: ValueVersion => Z, t: TransactionVersion => Z, l: LanguageVersion => Z): Z =
av.bifoldMap(_.bifoldMap(v)(t))(l)
@ -105,9 +105,9 @@ private[digitalasset] object VersionTimeline {
): Z =
sv fold (_ fold (v, t), l)
def showsVersion: String = foldVersion(_.toString, _.toString, _.toString)
private[lf] def showsVersion: String = foldVersion(_.toString, _.toString, _.toString)
def precedes(ov: SpecifiedVersion): Boolean = releasePrecedes(sv, ov)
private[lf] def precedes(ov: SpecifiedVersion): Boolean = releasePrecedes(sv, ov)
}
implicit def `any to SVOps`[A: SubVersion](vv: A): SpecifiedVersionOps =
@ -135,7 +135,9 @@ private[digitalasset] object VersionTimeline {
* @note We do not know the relative ordering of unlisted versions; so
* the meaning of "no index" is not "equal" but undefined.
*/
def compareReleaseTime(left: SpecifiedVersion, right: SpecifiedVersion): Option[Ordering] =
private[lf] def compareReleaseTime(
left: SpecifiedVersion,
right: SpecifiedVersion): Option[Ordering] =
(index get left, index get right) match {
case (Some(ixl), Some(ixr)) =>
import scalaz.std.anyVal._
@ -158,10 +160,11 @@ private[digitalasset] object VersionTimeline {
.getOrElse(sys.error("every SubVersion must have at least one entry in the timeline"))
// not antisymmetric, as unknown versions can't be compared
def maxVersion[A](left: A, right: A)(implicit ev: SubVersion[A]): A =
private[lf] def maxVersion[A](left: A, right: A)(implicit ev: SubVersion[A]): A =
if (releasePrecedes(ev.inject(left), ev.inject(right))) right else left
def latestWhenAllPresent[A](minimum: A, as: SpecifiedVersion*)(implicit A: SubVersion[A]): A = {
private[lf] def latestWhenAllPresent[A](minimum: A, as: SpecifiedVersion*)(
implicit A: SubVersion[A]): A = {
import scalaz.std.anyVal._
import scalaz.std.iterable._
// None means "after the end"

View File

@ -13,8 +13,6 @@ import com.google.protobuf
import scala.collection.JavaConverters._
import scalaz.std.either._
import scalaz.std.option._
import scalaz.syntax.traverse._
import scalaz.syntax.bifunctor._
/**
@ -46,61 +44,139 @@ object ValueCoder {
EncodeError(s"${version.showsVersion} is too old to support $isTooOldFor")
}
abstract class EncodeCid[-Cid] private[lf] {
def asString(cid: Cid): String
def asStruct(cid: Cid): (String, Boolean)
}
private val defaultVersion: SpecifiedVersion = ValueVersions.acceptedVersions.last
abstract class DecodeCid[Cid] private[lf] {
def fromString(s: String): Either[DecodeError, Cid]
def fromStruct(s: String, isRelative: Boolean): Either[DecodeError, Cid]
abstract class EncodeCid[-Cid] private[lf] {
private[lf] def encode(
version: SpecifiedVersion = defaultVersion,
contractId: Cid,
): Either[EncodeError, Either[String, (proto.ContractId)]]
}
object CidEncoder extends EncodeCid[ContractId] {
override def asString(cid: ContractId): String = cid match {
case RelativeContractId(nid, _) => "~" + nid.index.toString
case AbsoluteContractId(coid) => coid
}
override def asStruct(cid: ContractId): (String, Boolean) = cid match {
case RelativeContractId(nid, _) => nid.index.toString -> true
case AbsoluteContractId(coid) => coid -> false
}
private[lf] def encode(
sv: SpecifiedVersion,
cid: ContractId
): Either[EncodeError, Either[String, (proto.ContractId)]] =
if (useOldStringField(sv))
cid match {
case RelativeContractId(nid, _) =>
Right(Left("~" + nid.index.toString))
case AbsoluteContractId(s) if Ref.ContractIdString.isA(s) =>
Right(Left(s))
case AbsoluteContractId(_) =>
Left(EncodeError(s"absolute contractId v1 not supported by ${sv.showsVersion}"))
} else
cid match {
case RelativeContractId(nid, _) =>
Right(
Right(
proto.ContractId.newBuilder
.setRelative(true)
.setContractId(nid.index.toString)
.build))
case AbsoluteContractId(s) =>
if ((sv precedes ValueVersions.minContractIdV1) && (Ref.ContractIdString.isB(s)))
Left(EncodeError(s"absolute contractId v1 not supported by ${sv.showsVersion}"))
else
Right(Right(proto.ContractId.newBuilder.setRelative(false).setContractId(s).build))
}
}
object AbsCidDecoder extends DecodeCid[AbsoluteContractId] {
abstract class DecodeCid[Cid] private[lf] {
def decodeOptional(
sv: SpecifiedVersion,
stringForm: String,
structForm: proto.ContractId,
): Either[DecodeError, Option[Cid]]
override def fromString(s: String): Either[DecodeError, AbsoluteContractId] =
ContractIdString
.fromString(s)
.bimap(
_ => DecodeError(s"cannot parse absolute contractId $s"),
AbsoluteContractId
)
override def fromStruct(
s: String,
isRelative: Boolean): Either[DecodeError, AbsoluteContractId] =
if (isRelative)
Left(DecodeError(s"unexpected relative contractId $s"))
else
fromString(s)
final def decode(
sv: SpecifiedVersion,
stringForm: String,
structForm: proto.ContractId,
): Either[DecodeError, Cid] =
decodeOptional(sv, stringForm, structForm).flatMap {
case Some(cid) => Right(cid)
case None => Left(DecodeError("Missing required field contract_id"))
}
}
object CidDecoder extends DecodeCid[ContractId] {
val CidDecoder: DecodeCid[ContractId] = new DecodeCid[ContractId] {
private def fromStringToRelCid(s: String): Either[DecodeError, RelativeContractId] =
private def stringToRelativeCid(s: String) =
scalaz.std.string
.parseInt(s)
.toEither
.bimap(
_ => DecodeError(s"cannot parse relative contractId $s"),
idx => RelativeContractId(NodeId(idx), None)
_ => //
DecodeError(s"""cannot parse relative contractId "$s""""),
idx => Some(RelativeContractId(NodeId(idx), None))
)
override def fromString(s: String): Either[DecodeError, ContractId] =
if (s.startsWith("~")) fromStringToRelCid(s.drop(1)) else AbsCidDecoder.fromString(s)
private def stringToCidString(s: String): Either[DecodeError, Ref.ContractIdString] =
Ref.ContractIdString
.fromString(s)
.left
.map(_ => //
DecodeError(s"""cannot parse absolute contractId "$s""""))
override def fromStruct(s: String, isRelative: Boolean): Either[DecodeError, ContractId] =
if (isRelative) fromStringToRelCid(s) else AbsCidDecoder.fromStruct(s, isRelative)
override def decodeOptional(
sv: SpecifiedVersion,
stringForm: String,
structForm: proto.ContractId,
): Either[DecodeError, Option[ContractId]] =
if (useOldStringField(sv)) {
if (structForm != proto.ContractId.getDefaultInstance) {
Left(DecodeError(sv, isTooOldFor = "message ContractId"))
} else {
if (stringForm.isEmpty)
Right(None)
else if (stringForm.startsWith("~"))
stringToRelativeCid(stringForm.drop(1))
else
for {
coid <- stringToCidString(stringForm)
_ <- Either.cond(
test = Ref.ContractIdString.isA(coid),
right = (),
left = DecodeError(sv, s"absolute contractId V1"),
)
} yield Some(AbsoluteContractId(coid))
}
} else {
if (stringForm.nonEmpty)
Left(DecodeError(s"${sv.showsVersion} is too new to use string contract IDs"))
else if (structForm.getContractId.isEmpty)
Right(None)
else if (structForm.getRelative)
stringToRelativeCid(structForm.getContractId)
else
for {
coid <- stringToCidString(structForm.getContractId)
_ <- Either.cond(
test = Ref.ContractIdString.isA(coid) || !(sv precedes ValueVersions.minContractIdV1),
right = (),
left = DecodeError(sv, s"absolute contractId V1")
)
} yield Some(AbsoluteContractId(coid))
}
}
val AbsCidDecoder: DecodeCid[AbsoluteContractId] = new DecodeCid[AbsoluteContractId] {
override def decodeOptional(
sv: SpecifiedVersion,
stringForm: String,
structForm: ValueOuterClass.ContractId,
): Either[DecodeError, Option[AbsoluteContractId]] =
CidDecoder.decodeOptional(sv, stringForm, structForm).flatMap {
case Some(RelativeContractId(_, _)) =>
Left(DecodeError("Unexpected relative contractId"))
case Some(AbsoluteContractId(coid)) =>
Right(Some(AbsoluteContractId(coid)))
case None =>
Right(None)
}
}
/**
@ -158,92 +234,6 @@ object ValueCoder {
} yield Identifier(pkgId, QualifiedName(module, name))
/** Codecs for ContractId that don't break the method chain style
* of builders.
*/
private[lf] implicit final class codecContractId[A](private val self: A) extends AnyVal {
def setContractIdOrStruct[Cid, Z](
encodeCid: EncodeCid[Cid],
version: SpecifiedVersion,
contractId: Cid,
)(stringly: (A, String) => Z, structly: (A, proto.ContractId) => Z): Z =
if (useOldStringField(version)) stringly(self, encodeCid.asString(contractId))
else {
val (encCid, encRel) = encodeCid.asStruct(contractId)
structly(
self,
proto.ContractId.newBuilder().setContractId(encCid).setRelative(encRel).build(),
)
}
import proto.ContractId.{getDefaultInstance => placeholderContractId}
private[this] def requireUnset(v: SpecifiedVersion, s: String): Either[DecodeError, Unit] =
if (s == "") Right(())
else Left(DecodeError(s"${v.showsVersion} is too new to use string contract IDs"))
private[this] def requireUnset(
v: SpecifiedVersion,
c: proto.ContractId,
): Either[DecodeError, Unit] =
if (c == null || c == placeholderContractId) Right(())
else Left(DecodeError(v, isTooOldFor = "message ContractId"))
private[this] def decodeContractIdStruct[Cid](
decodeCid: DecodeCid[Cid],
p: proto.ContractId,
): Either[DecodeError, Cid] =
for {
str <- Either.cond(
p.getContractId.nonEmpty,
p.getContractId,
DecodeError("Missing required field contract_id"),
)
cid <- decodeCid.fromStruct(str, p.getRelative)
} yield cid
def decodeContractIdOrStruct[Cid, Z](
decodeCid: DecodeCid[Cid],
version: SpecifiedVersion,
)(stringly: A => String, structly: A => proto.ContractId): Either[DecodeError, Cid] = {
val stringForm = stringly(self)
val structForm = structly(self)
if (useOldStringField(version))
requireUnset(version, structForm) flatMap (
_ =>
if (stringForm == "")
Left(DecodeError("Missing required field contract_id"))
else decodeCid.fromString(stringForm),
)
else
requireUnset(version, stringForm) flatMap (
_ =>
decodeContractIdStruct(decodeCid, structForm),
)
}
def decodeOptionalContractIdOrStruct[Cid, Z](
decodeCid: DecodeCid[Cid],
version: SpecifiedVersion,
)(stringly: A => String, structly: A => proto.ContractId): Either[DecodeError, Option[Cid]] = {
val stringForm = stringly(self)
val structForm = structly(self)
if (useOldStringField(version))
requireUnset(version, structForm) flatMap (
_ =>
if (stringForm == "") Right(None)
else decodeCid.fromString(stringForm) map (Some(_)),
)
else
requireUnset(version, stringForm) flatMap (
_ =>
Option(structForm) filter (_ != placeholderContractId) traverseU (
sf => decodeContractIdStruct(decodeCid, sf)
),
)
}
}
private def decodeVersion(vs: String): Either[DecodeError, ValueVersion] =
ValueVersions
.isAcceptedVersion(vs)
@ -290,11 +280,12 @@ object ValueCoder {
* @return protocol buffer serialized values
*/
def encodeVersionedValue[Cid](
versionCid: ValueVersions.VersionCid[Cid],
encodeCid: EncodeCid[Cid],
value: Value[Cid],
): Either[EncodeError, proto.VersionedValue] =
ValueVersions
.assignVersion(value)
.assignVersion(versionCid, value)
.fold(
err => Left(EncodeError(err)),
version => encodeVersionedValueWithCustomVersion(encodeCid, VersionedValue(version, value)),
@ -383,11 +374,14 @@ object ValueCoder {
val party = Party.fromString(protoValue.getParty)
party.fold(e => throw Err("error decoding party: " + e), ValueParty)
case proto.Value.SumCase.CONTRACT_ID | proto.Value.SumCase.CONTRACT_ID_STRUCT =>
val cid = protoValue.decodeContractIdOrStruct(decodeCid, valueVersion)(
_.getContractId,
_.getContractIdStruct,
val cid = decodeCid.decode(
valueVersion,
protoValue.getContractId,
protoValue.getContractIdStruct
)
cid.fold(err => throw Err(err.errorMessage), ValueContractId(_))
cid.fold(
e => throw Err("error decoding contractId: " + e.errorMessage),
ValueContractId(_))
case proto.Value.SumCase.LIST =>
ValueList(
FrontStack(
@ -533,13 +527,11 @@ object ValueCoder {
case ValueTimestamp(t) =>
builder.setTimestamp(t.micros).build()
case ValueContractId(coid) =>
builder
.setContractIdOrStruct(encodeCid, valueVersion, coid)(
_.setContractId(_),
_.setContractIdStruct(_),
)
.build()
encodeCid.encode(valueVersion, coid).map {
case Left(string) => builder.setContractId(string)
case Right(struct) => builder.setContractIdStruct(struct)
}
builder.build()
case ValueList(elems) =>
val listBuilder = proto.List.newBuilder()
elems.foreach(elem => {
@ -632,10 +624,11 @@ object ValueCoder {
// general usage
private[value] def valueToBytes[Cid](
versionCid: ValueVersions.VersionCid[Cid],
encodeCid: EncodeCid[Cid],
v: Value[Cid],
): Either[EncodeError, Array[Byte]] = {
encodeVersionedValue(encodeCid, v).map(_.toByteArray)
encodeVersionedValue(versionCid, encodeCid, v).map(_.toByteArray)
}
private[value] def valueFromBytes[Cid](

View File

@ -5,7 +5,7 @@ package com.digitalasset.daml.lf.value
import com.digitalasset.daml.lf.value.Value._
import com.digitalasset.daml.lf.LfVersions
import com.digitalasset.daml.lf.data.{Decimal, FrontStack, FrontStackCons, ImmArray}
import com.digitalasset.daml.lf.data.{Decimal, FrontStack, FrontStackCons, ImmArray, Ref}
import com.digitalasset.daml.lf.transaction.VersionTimeline
import scala.annotation.tailrec
@ -29,7 +29,23 @@ object ValueVersions
private[value] val minGenMap = ValueVersion("7")
private[value] val minContractIdV1 = ValueVersion("7")
def assignVersion[Cid](v0: Value[Cid]): Either[String, ValueVersion] = {
abstract class VersionCid[-Cid] private[lf] {
private[lf] def assignVersion(cid: Cid): ValueVersion
}
object VersionCid extends VersionCid[ContractId] {
override private[lf] def assignVersion(cid: ContractId): ValueVersion =
cid match {
case AbsoluteContractId(cid) if Ref.ContractIdString.isB(cid) =>
minContractIdV1
case _ =>
minVersion
}
}
def assignVersion[Cid](
versionCid: VersionCid[Cid],
v0: Value[Cid]): Either[String, ValueVersion] = {
import VersionTimeline.{maxVersion => maxVV}
@tailrec
@ -48,9 +64,11 @@ object ValueVersions
case ValueRecord(_, fs) => go(currentVersion, fs.map(v => v._2) ++: values)
case ValueVariant(_, _, arg) => go(currentVersion, arg +: values)
case ValueList(vs) => go(currentVersion, vs.toImmArray ++: values)
case ValueContractId(_) | ValueInt64(_) | ValueText(_) | ValueTimestamp(_) |
ValueParty(_) | ValueBool(_) | ValueDate(_) | ValueUnit =>
case ValueInt64(_) | ValueText(_) | ValueTimestamp(_) | ValueParty(_) | ValueBool(_) |
ValueDate(_) | ValueUnit =>
go(currentVersion, values)
case ValueContractId(cid) =>
go(maxVV(versionCid.assignVersion(cid), currentVersion), values)
case ValueNumeric(x) if x.scale == Decimal.scale =>
go(currentVersion, values)
// for things added after version 1, we raise the minimum if present
@ -79,17 +97,24 @@ object ValueVersions
}
@throws[IllegalArgumentException]
def assertAssignVersion[Cid](v0: Value[Cid]): ValueVersion =
assignVersion(v0) match {
def assertAssignVersion[Cid](versionCid: VersionCid[Cid], v0: Value[Cid]): ValueVersion =
assignVersion(versionCid, v0) match {
case Left(err) => throw new IllegalArgumentException(err)
case Right(x) => x
}
def asVersionedValue[Cid](value: Value[Cid]): Either[String, VersionedValue[Cid]] =
assignVersion(value).map(version => VersionedValue(version = version, value = value))
def asVersionedValue[Cid](
versionCid: VersionCid[Cid],
value: Value[Cid],
): Either[String, VersionedValue[Cid]] =
assignVersion(versionCid, value).map(version =>
VersionedValue(version = version, value = value))
def asVersionedValue[Cid <: ContractId](value: Value[Cid]): Either[String, VersionedValue[Cid]] =
asVersionedValue(VersionCid, value)
@throws[IllegalArgumentException]
def assertAsVersionedValue[Cid](value: Value[Cid]): VersionedValue[Cid] =
def assertAsVersionedValue[Cid <: ContractId](value: Value[Cid]): VersionedValue[Cid] =
asVersionedValue(value) match {
case Left(err) => throw new IllegalArgumentException(err)
case Right(x) => x

View File

@ -114,20 +114,20 @@ class TransactionSpec extends FreeSpec with Matchers with GeneratorDrivenPropert
}
object TransactionSpec {
private[this] type Value[+Cid] = V[Cid]
type StringTransaction = GenTransaction[String, String, Value[String]]
private[this] type Value = V[V.AbsoluteContractId]
type StringTransaction = GenTransaction[String, V.AbsoluteContractId, Value]
def StringTransaction(
nodes: HashMap[String, GenNode[String, String, Value[String]]],
nodes: HashMap[String, GenNode[String, V.AbsoluteContractId, Value]],
roots: ImmArray[String],
): StringTransaction = GenTransaction(nodes, roots, None)
def dummyExerciseNode(
children: ImmArray[String],
hasExerciseResult: Boolean = true,
): NodeExercises[String, String, Value[String]] =
): NodeExercises[String, V.AbsoluteContractId, Value] =
NodeExercises(
nodeSeed = None,
targetCoid = "dummyCoid",
targetCoid = V.AbsoluteContractId(Ref.ContractIdString.assertFromString("dummyCoid")),
templateId = Ref.Identifier(
PackageId.assertFromString("-dummyPkg-"),
QualifiedName.assertFromString("DummyModule:dummyName"),
@ -145,10 +145,10 @@ object TransactionSpec {
key = None,
)
val dummyCreateNode: NodeCreate[String, Value[String]] =
val dummyCreateNode: NodeCreate[V.AbsoluteContractId, Value] =
NodeCreate(
nodeSeed = None,
coid = "dummyCoid",
coid = V.AbsoluteContractId(Ref.ContractIdString.assertFromString("dummyCoid")),
coinst = ContractInst(
Ref.Identifier(
PackageId.assertFromString("-dummyPkg-"),

View File

@ -6,7 +6,7 @@ package transaction
import data.ImmArray
import value.Value
import Value.{ValueOptional, VersionedValue}
import Value.{ContractId, ValueOptional, VersionedValue}
import value.ValueVersions.asVersionedValue
import TransactionVersions.assignVersion
import org.scalatest.{Matchers, WordSpec}
@ -23,27 +23,27 @@ class TransactionVersionSpec extends WordSpec with Matchers {
"pick version 2 when confronted with newer data" in {
val usingOptional = dummyCreateTransaction map3 (identity, identity, v =>
ValueOptional(Some(v)): Value[String])
ValueOptional(Some(v)): Value[Value.AbsoluteContractId])
assignVersion(assignValueVersions(usingOptional)) shouldBe TransactionVersion("2")
}
"pick version 7 when confronted with exercise result" in {
val hasExerciseResult = dummyExerciseWithResultTransaction map3 (identity, identity, v =>
ValueOptional(Some(v)): Value[String])
ValueOptional(Some(v)): Value[Value.AbsoluteContractId])
assignVersion(assignValueVersions(hasExerciseResult)) shouldBe TransactionVersion("7")
}
"pick version 2 when confronted with exercise result" in {
val hasExerciseResult = dummyExerciseTransaction map3 (identity, identity, v =>
ValueOptional(Some(v)): Value[String])
ValueOptional(Some(v)): Value[Value.AbsoluteContractId])
assignVersion(assignValueVersions(hasExerciseResult)) shouldBe TransactionVersion("2")
}
}
private[this] def assignValueVersions[Nid, Cid, Cid2](
t: GenTransaction[Nid, Cid, Value[Cid2]],
): GenTransaction[Nid, Cid, VersionedValue[Cid2]] =
private[this] def assignValueVersions[Nid, Cid <: ContractId](
t: GenTransaction[Nid, Cid, Value[Cid]],
): GenTransaction[Nid, Cid, VersionedValue[Cid]] =
t map3 (identity, identity, v =>
asVersionedValue(v) fold (e =>
fail(s"We didn't write traverse for GenTransaction: $e"), identity))

View File

@ -128,8 +128,14 @@ class ValueCoderSpec extends WordSpec with Matchers with EitherAssertions with P
}
}
"do ContractId in any ValueVersion" in forAll(coidValueGen, valueVersionGen())(
testRoundTripWithVersion,
"do ContractId V0 in any ValueVersion" in forAll(coidValueGenV0, valueVersionGen())(
testRoundTripWithVersion
)
"do ContractId in any ValueVersion > 1.7" in forAll(
coidValueGen,
valueVersionGen(ValueVersions.minContractIdV1))(
testRoundTripWithVersion
)
"do lists" in {
@ -185,7 +191,10 @@ class ValueCoderSpec extends WordSpec with Matchers with EitherAssertions with P
)
val fromToBytes = ValueCoder.valueFromBytes(
ValueCoder.CidDecoder,
ValueCoder.valueToBytes[ContractId](ValueCoder.CidEncoder, ValueUnit).toOption.get,
ValueCoder
.valueToBytes[ContractId](ValueVersions.VersionCid, ValueCoder.CidEncoder, ValueUnit)
.toOption
.get,
)
Right(ValueUnit) shouldEqual fromToBytes
recovered shouldEqual Right(ValueUnit)
@ -209,7 +218,7 @@ class ValueCoderSpec extends WordSpec with Matchers with EitherAssertions with P
}
"do versioned value with assigned version" in forAll(valueGen) { v: Value[ContractId] =>
testRoundTripWithVersion(v, ValueVersions.assertAssignVersion(v))
testRoundTripWithVersion(v, ValueVersions.assertAssignVersion(ValueVersions.VersionCid, v))
}
"versioned value should pass serialization if unsupported override version provided" in
@ -258,7 +267,9 @@ class ValueCoderSpec extends WordSpec with Matchers with EitherAssertions with P
)
val fromToBytes = ValueCoder.valueFromBytes(
ValueCoder.CidDecoder,
assertRight(ValueCoder.valueToBytes[ContractId](ValueCoder.CidEncoder, value)),
assertRight(
ValueCoder
.valueToBytes[ContractId](ValueVersions.VersionCid, ValueCoder.CidEncoder, value)),
)
Right(value) shouldEqual recovered
Right(value) shouldEqual fromToBytes

View File

@ -139,7 +139,8 @@ private[validation] object Serializability {
version: LanguageVersion,
world: World,
tyCon: TTyCon,
template: Template): Unit = {
template: Template,
): Unit = {
val context = ContextTemplate(tyCon.tycon)
Env(version, world, context, SRTemplateArg, tyCon).checkType()
template.choices.values.foreach { choice =>

View File

@ -45,10 +45,11 @@ object PlatformTypes {
f: Cid => Cid2): GenTransaction[Nid, Cid2] =
tx.mapContractIdAndValue(f, _.mapContractId(f))
def asVersionedValue[Cid](v: V[Cid]): scala.Either[String, V.VersionedValue[Cid]] =
def asVersionedValue[Cid <: V.ContractId](
v: V[Cid]): scala.Either[String, V.VersionedValue[Cid]] =
ValueVersions.asVersionedValue(v)
def asVersionedValueOrThrow[Cid](v: V[Cid]): V.VersionedValue[Cid] = {
def asVersionedValueOrThrow[Cid <: V.ContractId](v: V[Cid]): V.VersionedValue[Cid] = {
asVersionedValue(v).fold(
s => throw new IllegalArgumentException(s"Can't convert to versioned value: $s"),
identity)

View File

@ -12,6 +12,7 @@ import com.digitalasset.daml.lf.data.Ref.{LedgerString, Party}
import com.digitalasset.daml.lf.data.Time
import com.digitalasset.daml.lf.transaction.Node.GlobalKey
import com.digitalasset.daml.lf.transaction._
import com.digitalasset.daml.lf.transaction.VersionTimeline.Implicits._
import com.digitalasset.daml.lf.value.Value.{
AbsoluteContractId,
ContractId,
@ -263,15 +264,18 @@ private[state] object Conversions {
identity,
)
def contractIdStructOrStringToStateKey(
def contractIdStructOrStringToStateKey[A](
transactionVersion: TransactionVersion,
entryId: DamlLogEntryId,
coidString: String,
coidStruct: ValueOuterClass.ContractId): DamlStateKey = {
val result =
if (coidString.isEmpty)
ValueCoder.CidDecoder.fromStruct(coidStruct.getContractId, coidStruct.getRelative)
else
ValueCoder.CidDecoder.fromString(coidString)
coidStruct: ValueOuterClass.ContractId,
): DamlStateKey = {
val result = ValueCoder.CidDecoder.decode(
sv = transactionVersion,
stringForm = coidString,
structForm = coidStruct,
)
result match {
case Left(err) =>

View File

@ -9,13 +9,13 @@ import com.daml.ledger.participant.state.kvutils.DamlKvutils._
import com.daml.ledger.participant.state.kvutils.committer.{
ConfigCommitter,
PackageCommitter,
PartyAllocationCommitter,
PartyAllocationCommitter
}
import com.daml.ledger.participant.state.kvutils.committing._
import com.daml.ledger.participant.state.v1.{Configuration, ParticipantId}
import com.digitalasset.daml.lf.data.Time.Timestamp
import com.digitalasset.daml.lf.engine.Engine
import com.digitalasset.daml.lf.transaction.TransactionOuterClass
import com.digitalasset.daml.lf.transaction.{TransactionCoder, TransactionOuterClass}
import com.digitalasset.daml_lf_dev.DamlLf
import com.digitalasset.platform.common.metrics.VarGauge
import com.google.protobuf.ByteString
@ -219,74 +219,81 @@ object KeyValueCommitting {
private def transactionOutputs(
transactionEntry: DamlTransactionEntry,
entryId: DamlLogEntryId,
): Set[DamlStateKey] =
transactionEntry.getTransaction.getNodesList.asScala.flatMap {
node: TransactionOuterClass.Node =>
node.getNodeTypeCase match {
case TransactionOuterClass.Node.NodeTypeCase.CREATE =>
val create = node.getCreate
val ckeyOrEmpty =
if (create.hasKeyWithMaintainers)
List(
DamlStateKey.newBuilder
.setContractKey(
DamlContractKey.newBuilder
.setTemplateId(create.getContractInstance.getTemplateId)
.setKey(create.getKeyWithMaintainers.getKey),
)
.build,
)
else
List.empty
): Set[DamlStateKey] = {
val transaction = transactionEntry.getTransaction
val transactionVersion = TransactionCoder
.decodeVersion(transaction.getVersion)
.fold(err => throw Err.InvalidSubmission(err.errorMessage), identity)
transaction.getNodesList.asScala.flatMap { node: TransactionOuterClass.Node =>
node.getNodeTypeCase match {
case TransactionOuterClass.Node.NodeTypeCase.CREATE =>
val create = node.getCreate
val ckeyOrEmpty =
if (create.hasKeyWithMaintainers)
List(
DamlStateKey.newBuilder
.setContractKey(
DamlContractKey.newBuilder
.setTemplateId(create.getContractInstance.getTemplateId)
.setKey(create.getKeyWithMaintainers.getKey),
)
.build,
)
else
List.empty
Conversions
.contractIdStructOrStringToStateKey(
transactionVersion,
entryId,
create.getContractId,
create.getContractIdStruct,
) :: ckeyOrEmpty
case TransactionOuterClass.Node.NodeTypeCase.EXERCISE =>
val exe = node.getExercise
val ckeyOrEmpty =
if (exe.getConsuming && exe.hasKeyWithMaintainers)
List(
DamlStateKey.newBuilder
.setContractKey(
DamlContractKey.newBuilder
.setTemplateId(exe.getTemplateId)
.setKey(exe.getKeyWithMaintainers.getKey),
)
.build,
)
else
List.empty
Conversions
.contractIdStructOrStringToStateKey(
transactionVersion,
entryId,
exe.getContractId,
exe.getContractIdStruct,
) :: ckeyOrEmpty
case TransactionOuterClass.Node.NodeTypeCase.FETCH =>
// A fetch may cause a divulgence, which is why it is a potential output.
List(
Conversions
.contractIdStructOrStringToStateKey(
transactionVersion,
entryId,
create.getContractId,
create.getContractIdStruct,
) :: ckeyOrEmpty
case TransactionOuterClass.Node.NodeTypeCase.EXERCISE =>
val exe = node.getExercise
val ckeyOrEmpty =
if (exe.getConsuming && exe.hasKeyWithMaintainers)
List(
DamlStateKey.newBuilder
.setContractKey(
DamlContractKey.newBuilder
.setTemplateId(exe.getTemplateId)
.setKey(exe.getKeyWithMaintainers.getKey),
)
.build,
)
else
List.empty
Conversions
.contractIdStructOrStringToStateKey(
entryId,
exe.getContractId,
exe.getContractIdStruct,
) :: ckeyOrEmpty
case TransactionOuterClass.Node.NodeTypeCase.FETCH =>
// A fetch may cause a divulgence, which is why it is a potential output.
List(
Conversions
.contractIdStructOrStringToStateKey(
entryId,
node.getFetch.getContractId,
node.getFetch.getContractIdStruct,
),
)
case TransactionOuterClass.Node.NodeTypeCase.LOOKUP_BY_KEY =>
// Contract state only modified on divulgence, in which case we'll have a fetch node,
// so no outputs from lookup node.
List.empty
case TransactionOuterClass.Node.NodeTypeCase.NODETYPE_NOT_SET =>
throw Err.InvalidSubmission("submissionOutputs: NODETYPE_NOT_SET")
}
node.getFetch.getContractId,
node.getFetch.getContractIdStruct,
),
)
case TransactionOuterClass.Node.NodeTypeCase.LOOKUP_BY_KEY =>
// Contract state only modified on divulgence, in which case we'll have a fetch node,
// so no outputs from lookup node.
List.empty
case TransactionOuterClass.Node.NodeTypeCase.NODETYPE_NOT_SET =>
throw Err.InvalidSubmission("submissionOutputs: NODETYPE_NOT_SET")
}
}.toSet
}
private def verifyStateUpdatesAgainstPreDeclaredOutputs(
actualStateUpdates: Map[DamlStateKey, DamlStateValue],

View File

@ -126,7 +126,8 @@ object KeyHasher extends KeyHasher {
// Do not use directly. It is package visible for testing purpose.
private[serialization] def putValue(
digest: MessageDigest,
value: Value[AbsoluteContractId]): MessageDigest = {
value: Value[AbsoluteContractId],
): MessageDigest = {
// Then, write the value
foldLeft[MessageDigest](
value,