diff --git a/daml-lf/data/src/main/scala/com/digitalasset/daml/lf/data/IdString.scala b/daml-lf/data/src/main/scala/com/digitalasset/daml/lf/data/IdString.scala index 6747fc645a8..e3b1ade3b13 100644 --- a/daml-lf/data/src/main/scala/com/digitalasset/daml/lf/data/IdString.scala +++ b/daml-lf/data/src/main/scala/com/digitalasset/daml/lf/data/IdString.scala @@ -52,10 +52,14 @@ sealed trait UnionStringModule[T <: String, TA <: T, TB <: T] extends StringModu def toEither(s: T): Either[TA, TB] + def isA(s: T): Boolean + def toA(s: T): Option[TA] def assertToVA(s: T): TA + def isB(s: T): Boolean + def toB(s: T): Option[TB] def assertToVB(s: T): TB @@ -228,7 +232,7 @@ private[data] final class IdStringImpl extends IdString { // Prefixed with "$0" which is not a valid substring of ContractIdV0. override type ContractIdStringV1 = String override val ContractIdStringV1: StringModule[ContractIdStringV1] = - new MatchingStringModule("""\$0[0-9a-f]{64}[A-Za-z0-9:\-_]{189}""") + new MatchingStringModule("""\$0[0-9a-f]{64}[A-Za-z0-9:\-_]{0,189}""") /** Identifier for a contractIs, union of `ContractIdStringV0` and `ContractIdStringV1` */ override type ContractIdString = String @@ -238,23 +242,28 @@ private[data] final class IdStringImpl extends IdString { with UnionStringModule[ContractIdString, ContractIdStringV0, ContractIdStringV1] { override def toEither(s: ContractIdString): Either[String, String] = - Either.cond(s.startsWith("$0"), s, s) + Either.cond(isA(s), s, s) override def fromString(s: String): Either[String, ContractIdString] = toEither(s).fold(ContractIdStringV0.fromString, ContractIdStringV1.fromString) - override def assertToVA(s: ContractIdString): ContractIdStringV0 = - toEither(s).left - .getOrElse(throw new IllegalArgumentException("expect V0 ContractId get V1")) - - override def assertToVB(s: ContractIdString): ContractIdStringV1 = - toEither(s).right - .getOrElse(throw new IllegalArgumentException("expect V1 ContractId get V0")) + override def isA(s: ContractIdString): Boolean = + s.startsWith("$0") override def toA(s: ContractIdString): Option[ContractIdStringV0] = - toEither(s).left.toOption + Some(s).filter(isA) + + override def assertToVA(s: ContractIdString): ContractIdStringV0 = + toA(s).getOrElse(throw new IllegalArgumentException("expect V0 ContractId get V1")) + + override def isB(s: ContractIdString): Boolean = + !isA(s) override def toB(s: ContractIdString): Option[ContractIdStringV1] = - toEither(s).right.toOption + Some(s).filter(isB) + + override def assertToVB(s: ContractIdString): ContractIdStringV1 = + toB(s).getOrElse(throw new IllegalArgumentException("expect V1 ContractId get V0")) + } } diff --git a/daml-lf/engine/src/main/scala/com/digitalasset/daml/lf/engine/Event.scala b/daml-lf/engine/src/main/scala/com/digitalasset/daml/lf/engine/Event.scala index b7f8d8fa116..c60e6d82eed 100644 --- a/daml-lf/engine/src/main/scala/com/digitalasset/daml/lf/engine/Event.scala +++ b/daml-lf/engine/src/main/scala/com/digitalasset/daml/lf/engine/Event.scala @@ -89,7 +89,7 @@ final case class ExerciseEvent[Nid, Cid, Val]( exerciseResult: Option[Val]) extends Event[Nid, Cid, Val] -object Event extends value.CidContainer3[Event] { +object Event extends value.CidContainer3WithDefaultCidResolver[Event] { override private[lf] def map3[Nid, Cid, Val, Nid2, Cid2, Val2]( f1: Nid => Nid2, @@ -253,7 +253,7 @@ object Event extends value.CidContainer3[Event] { Events(relevantRoots, Map() ++ evts) } - object Events extends value.CidContainer3[Events] { + object Events extends value.CidContainer3WithDefaultCidResolver[Events] { override private[lf] def map3[Nid, Cid, Val, Nid2, Cid2, Val2]( f1: Nid => Nid2, f2: Cid => Cid2, diff --git a/daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/InMemoryPrivateLedgerData.scala b/daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/InMemoryPrivateLedgerData.scala index 8860c5b6db7..2b744c61941 100644 --- a/daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/InMemoryPrivateLedgerData.scala +++ b/daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/InMemoryPrivateLedgerData.scala @@ -27,11 +27,11 @@ private[engine] class InMemoryPrivateLedgerData extends PrivateLedgerData { private val txCounter: AtomicInteger = new AtomicInteger(0) def update(tx: GenTransaction.WithTxValue[NodeId, ContractId]): Unit = - updateWithAbsoluteContractId(tx.resolveRelCid(toContractIdString(txCounter.get))) + updateWithAbsoluteContractId(tx.resolveRelCidV0(toContractIdString(txCounter.get))) - def toContractIdString(txCounter: Int)(r: RelativeContractId): Ref.ContractIdString = + def toContractIdString(txCounter: Int)(r: RelativeContractId): Ref.ContractIdStringV0 = // It is safe to concatenate numbers and "-" to form a valid ContractId - Ref.ContractIdString.assertFromString(s"$txCounter-${r.txnid.index}") + Ref.ContractIdStringV0.assertFromString(s"$txCounter-${r.txnid.index}") def updateWithAbsoluteContractId( tx: GenTransaction.WithTxValue[NodeId, AbsoluteContractId]): Unit = diff --git a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/types/Ledger.scala b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/types/Ledger.scala index 205837d2319..03052ae7488 100644 --- a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/types/Ledger.scala +++ b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/types/Ledger.scala @@ -191,7 +191,7 @@ object Ledger { nodes = enrichedTx.nodes.map { case (nodeId, node) => ScenarioNodeId(commitPrefix, nodeId) -> node - .resolveRelCid(makeAbs) + .resolveRelCidV0(makeAbs) .mapNodeId(ScenarioNodeId(commitPrefix, _)) }(breakOut), explicitDisclosure = enrichedTx.explicitDisclosure.map { diff --git a/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/crypto/Hash.scala b/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/crypto/Hash.scala index 8176722f7c8..a31af946fc1 100644 --- a/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/crypto/Hash.scala +++ b/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/crypto/Hash.scala @@ -5,7 +5,7 @@ package com.digitalasset.daml.lf package crypto import java.nio.ByteBuffer -import java.security.MessageDigest +import java.security.{MessageDigest, SecureRandom} import java.util import com.digitalasset.daml.lf.data.{ImmArray, Ref, Utf8} @@ -20,10 +20,10 @@ final class Hash private (private val bytes: Array[Byte]) { def toByteArray: Array[Byte] = bytes.clone() - def toLedgerString: Ref.LedgerString = - Hash.toLedgerString(this) + def toHexaString: String = + bytes.map("%02x" format _).mkString - override def toString: String = s"Hash($toLedgerString)" + override def toString: String = s"Hash($toHexaString)" override def equals(other: Any): Boolean = other match { @@ -48,6 +48,16 @@ object Hash { private val version = 0.toByte private val underlyingHashLength = 32 + val secureRandom: () => Hash = { + val random = new SecureRandom() + () => + { + val a = Array.ofDim[Byte](underlyingHashLength) + random.nextBytes(a) + new Hash(a) + } + } + implicit val HashOrdering: Ordering[Hash] = ((hash1, hash2) => implicitly[Ordering[Iterable[Byte]]].compare(hash1.bytes, hash2.bytes)) @@ -201,9 +211,6 @@ object Hash { } - def toLedgerString(hash: Hash): Ref.LedgerString = - Ref.LedgerString.assertFromString(hash.bytes.map("%02x" format _).mkString) - def fromString(s: String): Either[String, Hash] = { def error = s"Cannot parse hash $s" try { diff --git a/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/transaction/Node.scala b/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/transaction/Node.scala index 7a82d59a6aa..04414b4a02e 100644 --- a/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/transaction/Node.scala +++ b/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/transaction/Node.scala @@ -49,7 +49,9 @@ object Node { def requiredAuthorizers: Set[Party] } - object GenNode extends WithTxValue3[GenNode] with value.CidContainer3[GenNode] { + object GenNode + extends WithTxValue3[GenNode] + with value.CidContainer3WithDefaultCidResolver[GenNode] { override private[lf] def map3[A1, A2, A3, B1, B2, B3]( f1: A1 => B1, f2: A2 => B2, @@ -258,7 +260,7 @@ object Node { KeyWithMaintainers.map1(f)(this) } - object KeyWithMaintainers extends value.CidContainer1[KeyWithMaintainers] { + object KeyWithMaintainers extends value.CidContainer1WithDefaultCidResolver[KeyWithMaintainers] { implicit def equalInstance[Val: Equal]: Equal[KeyWithMaintainers[Val]] = ScalazEqual.withNatural(Equal[Val].equalIsNatural) { (a, b) => import a._ diff --git a/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/transaction/Transaction.scala b/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/transaction/Transaction.scala index ed8296b08b6..7e2e7752bf9 100644 --- a/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/transaction/Transaction.scala +++ b/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/transaction/Transaction.scala @@ -329,7 +329,7 @@ final case class GenTransaction[Nid, +Cid, +Val]( } } -object GenTransaction extends value.CidContainer3[GenTransaction] { +object GenTransaction extends value.CidContainer3WithDefaultCidResolver[GenTransaction] { type WithTxValue[Nid, +Cid] = GenTransaction[Nid, Cid, Transaction.Value[Cid]] case class NotWellFormedError[Nid](nid: Nid, reason: NotWellFormedErrorReason) diff --git a/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/value/CidContainer.scala b/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/value/CidContainer.scala index 3b8d5ac182e..97cb1225025 100644 --- a/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/value/CidContainer.scala +++ b/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/value/CidContainer.scala @@ -5,35 +5,86 @@ package com.digitalasset.daml.lf package value import com.digitalasset.daml.lf.data.Ref +import com.digitalasset.daml.lf.transaction.VersionTimeline import scala.language.higherKinds import scala.util.control.NoStackTrace -sealed trait CidMapper[-A1, +A2, Fun] { +sealed abstract class CidMapper[-A1, +A2, In, Out] { - def map(f: Fun): A1 => A2 + def map(f: In => Out): A1 => A2 + + // We cheat using exceptions, to get a cheap implementation of traverse using the `map` function above. + // In practice, we abort the traversal using an exception as soon as we find an input we cannot map. + def traverse[L](f: In => Either[L, Out]): A1 => Either[L, A2] = { + case class Ball(x: L) extends Throwable with NoStackTrace + a => + try { + Right(map(x => f(x).fold(y => throw Ball(y), identity))(a)) + } catch { + case Ball(x) => Left(x) + } + } } object CidMapper { - def trivialMapper[X, Fun]: CidMapper[X, X, Fun] = - new CidMapper[X, X, Fun] { - override def map(f: Fun): X => X = identity + type CidChecker[-A1, +A2, AllowCid] = CidMapper[A1, A2, Value.ContractId, AllowCid] + + type NoCidChecker[-A1, +A2] = CidChecker[A1, A2, Nothing] + + type NoRelCidChecker[-A1, +A2] = CidChecker[A1, A2, Value.AbsoluteContractId] + + type RelCidResolver[-A1, +A2, Id] = + CidMapper[A1, A2, Value.RelativeContractId, Id] + + type RelCidV0Resolver[-A1, +A2] = + CidMapper[A1, A2, Value.RelativeContractId, Ref.ContractIdStringV0] + + type RelCidV1Resolver[-A1, +A2] = + CidMapper[A1, A2, Value.RelativeContractId, Ref.ContractIdStringV1] + + def trivialMapper[X, In, Out]: CidMapper[X, X, In, Out] = + new CidMapper[X, X, In, Out] { + override def map(f: In => Out): X => X = identity } - private[value] def basicInstance[Cid1, Cid2]: CidMapper[Cid1, Cid2, Cid1 => Cid2] = - new CidMapper[Cid1, Cid2, Cid1 => Cid2] { + private[value] def basicMapperInstance[Cid1, Cid2]: CidMapper[Cid1, Cid2, Cid1, Cid2] = + new CidMapper[Cid1, Cid2, Cid1, Cid2] { override def map(f: Cid1 => Cid2): Cid1 => Cid2 = f } - type RelCidResolverMapper[-A1, +A2] = - CidMapper[A1, A2, Value.ContractId => Value.AbsoluteContractId] - - type NoCidMapper[-A1, +A2] = CidMapper[A1, A2, Value.ContractId => Nothing] - - type NoRelCidMapper[-A1, +A2] = CidMapper[A1, A2, Value.ContractId => Value.AbsoluteContractId] + private[value] def basicCidResolverInstance[Id <: Ref.ContractIdString] + : RelCidResolver[Value.ContractId, Value.AbsoluteContractId, Id] = + new CidMapper[Value.ContractId, Value.AbsoluteContractId, Value.RelativeContractId, Id] { + override def map( + f: Value.RelativeContractId => Id, + ): Value.ContractId => Value.AbsoluteContractId = { + case acoid: Value.AbsoluteContractId => acoid + case rcoid: Value.RelativeContractId => Value.AbsoluteContractId(f(rcoid)) + } + } + private[value] def valueVersionCidV1Resolver[A1, A2]( + implicit resolver: RelCidV1Resolver[A1, A2], + ): RelCidV1Resolver[Value.VersionedValue[A1], Value.VersionedValue[A2]] = + new CidMapper[ + Value.VersionedValue[A1], + Value.VersionedValue[A2], + Value.RelativeContractId, + Ref.ContractIdStringV1, + ] { + override def map( + f: Value.RelativeContractId => Ref.ContractIdStringV1, + ): Value.VersionedValue[A1] => Value.VersionedValue[A2] = { + case Value.VersionedValue(version, value) => + Value.VersionedValue( + version = VersionTimeline.maxVersion(version, ValueVersions.minContractIdV1), + value = value.map1(resolver.map(f)), + ) + } + } } trait CidContainer[+A] { @@ -42,46 +93,37 @@ trait CidContainer[+A] { protected val self: A - def resolveRelCid[B](f: Value.RelativeContractId => Ref.ContractIdString)( - implicit mapper: RelCidResolverMapper[A, B], + final def resolveRelCidV0[B](f: Value.RelativeContractId => Ref.ContractIdStringV0)( + implicit resolver: RelCidV0Resolver[A, B] ): B = - mapper.map({ - case acoid: Value.AbsoluteContractId => acoid - case rcoid: Value.RelativeContractId => Value.AbsoluteContractId(f(rcoid)) - })(self) + resolver.map(f)(self) - def ensureNoCid[B]( - implicit mapper: NoCidMapper[A, B] - ): Either[Value.ContractId, B] = { - case class Ball(x: Value.ContractId) extends Throwable with NoStackTrace - try { - Right(mapper.map(coid => throw Ball(coid))(self)) - } catch { - case Ball(coid) => Left(coid) - } - } + final def resolveRelCidV1[B]( + f: Value.RelativeContractId => Either[String, Ref.ContractIdStringV1])( + implicit resolver: RelCidV1Resolver[A, B], + ): Either[String, B] = + resolver.traverse[String](f)(self) - def assertNoCid[B](message: Value.ContractId => String)( - implicit mapper: NoCidMapper[A, B] + final def ensureNoCid[B]( + implicit checker: NoCidChecker[A, B] + ): Either[Value.ContractId, B] = + checker.traverse[Value.ContractId](Left(_))(self) + + final def assertNoCid[B](message: Value.ContractId => String)( + implicit checker: NoCidChecker[A, B] ): B = data.assertRight(ensureNoCid.left.map(message)) - def ensureNoRelCid[B]( - implicit mapper: NoRelCidMapper[A, B] - ): Either[Value.RelativeContractId, B] = { - case class Ball(x: Value.RelativeContractId) extends Throwable with NoStackTrace - try { - Right(mapper.map({ - case acoid: Value.AbsoluteContractId => acoid - case rcoid: Value.RelativeContractId => throw Ball(rcoid) - })(self)) - } catch { - case Ball(coid) => Left(coid) - } - } + final def ensureNoRelCid[B]( + implicit checker: NoRelCidChecker[A, B] + ): Either[Value.RelativeContractId, B] = + checker.traverse[Value.RelativeContractId] { + case acoid: Value.AbsoluteContractId => Right(acoid) + case rcoid: Value.RelativeContractId => Left(rcoid) + }(self) - def assertNoRelCid[B](message: Value.ContractId => String)( - implicit mapper: NoRelCidMapper[A, B] + final def assertNoRelCid[B](message: Value.ContractId => String)( + implicit checker: NoRelCidChecker[A, B] ): B = data.assertRight(ensureNoRelCid.left.map(message)) @@ -89,35 +131,80 @@ trait CidContainer[+A] { trait CidContainer1[F[_]] { + import CidMapper._ + private[lf] def map1[A, B](f: A => B): F[A] => F[B] - final implicit def cidMapperInstance[A1, A2, Fun]( - implicit mapper: CidMapper[A1, A2, Fun] - ): CidMapper[F[A1], F[A2], Fun] = - new CidMapper[F[A1], F[A2], Fun] { - override def map(f: Fun): F[A1] => F[A2] = + protected final def cidMapperInstance[A1, A2, In, Out]( + implicit mapper: CidMapper[A1, A2, In, Out] + ): CidMapper[F[A1], F[A2], In, Out] = + new CidMapper[F[A1], F[A2], In, Out] { + override def map(f: In => Out): F[A1] => F[A2] = map1[A1, A2](mapper.map(f)) } + final implicit def noCidCheckerInstance[A1, A2]( + implicit checker1: NoCidChecker[A1, A2], + ): NoCidChecker[F[A1], F[A2]] = + cidMapperInstance[A1, A2, Value.ContractId, Nothing] + + final implicit def noRelCidCheckerInstance[A1, A2]( + implicit checker1: NoRelCidChecker[A1, A2], + ): NoRelCidChecker[F[A1], F[A2]] = + cidMapperInstance[A1, A2, Value.ContractId, Value.AbsoluteContractId] + +} + +trait CidContainer1WithDefaultCidResolver[F[_]] extends CidContainer1[F] { + + import CidMapper._ + + final implicit def cidResolverInstance[A1, A2, OutputId]( + implicit resolver1: RelCidResolver[A1, A2, OutputId], + ): RelCidResolver[F[A1], F[A2], OutputId] = + cidMapperInstance(resolver1) + } trait CidContainer3[F[_, _, _]] { + import CidMapper._ + private[lf] def map3[A1, B1, C1, A2, B2, C2]( f1: A1 => A2, f2: B1 => B2, f3: C1 => C2, ): F[A1, B1, C1] => F[A2, B2, C2] - final implicit def cidMapperInstance[A1, B1, C1, A2, B2, C2, Fun]( - implicit mapper1: CidMapper[A1, A2, Fun], - mapper2: CidMapper[B1, B2, Fun], - mapper3: CidMapper[C1, C2, Fun], - ): CidMapper[F[A1, B1, C1], F[A2, B2, C2], Fun] = - new CidMapper[F[A1, B1, C1], F[A2, B2, C2], Fun] { - override def map(f: Fun): F[A1, B1, C1] => F[A2, B2, C2] = { + protected final def cidMapperInstance[A1, B1, C1, A2, B2, C2, In, Out]( + implicit mapper1: CidMapper[A1, A2, In, Out], + mapper2: CidMapper[B1, B2, In, Out], + mapper3: CidMapper[C1, C2, In, Out], + ): CidMapper[F[A1, B1, C1], F[A2, B2, C2], In, Out] = + new CidMapper[F[A1, B1, C1], F[A2, B2, C2], In, Out] { + override def map(f: In => Out): F[A1, B1, C1] => F[A2, B2, C2] = { map3[A1, B1, C1, A2, B2, C2](mapper1.map(f), mapper2.map(f), mapper3.map(f)) } } + final implicit def noRelCidCheckerInstance[A1, B1, C1, A2, B2, C2]( + implicit checker1: NoRelCidChecker[A1, A2], + checker2: NoRelCidChecker[B1, B2], + checker3: NoRelCidChecker[C1, C2], + ): NoRelCidChecker[F[A1, B1, C1], F[A2, B2, C2]] = + cidMapperInstance + +} + +trait CidContainer3WithDefaultCidResolver[F[_, _, _]] extends CidContainer3[F] { + + import CidMapper._ + + final implicit def cidResolverInstance[A1, B1, C1, A2, B2, C2, OutputId]( + implicit resolver1: RelCidResolver[A1, A2, OutputId], + resolver2: RelCidResolver[B1, B2, OutputId], + resolver3: RelCidResolver[C1, C2, OutputId], + ): RelCidResolver[F[A1, B1, C1], F[A2, B2, C2], OutputId] = + cidMapperInstance + } diff --git a/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/value/Value.scala b/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/value/Value.scala index ff41e6d568c..838c53a3135 100644 --- a/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/value/Value.scala +++ b/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/value/Value.scala @@ -131,7 +131,7 @@ sealed abstract class Value[+Cid] extends CidContainer[Value[Cid]] with Product } -object Value extends CidContainer1[Value] { +object Value extends CidContainer1WithDefaultCidResolver[Value] { // TODO (FM) make this tail recursive private[lf] override def map1[Cid, Cid2](f: Cid => Cid2): Value[Cid] => Value[Cid2] = { @@ -206,6 +206,16 @@ object Value extends CidContainer1[Value] { override private[lf] def map1[A, B](f: A => B): VersionedValue[A] => VersionedValue[B] = x => x.copy(value = Value.map1(f)(x.value)) + final implicit def cidResolverV0Instance[A1, A2]( + implicit mapper1: CidMapper.RelCidV0Resolver[A1, A2], + ): CidMapper.RelCidV0Resolver[VersionedValue[A1], VersionedValue[A2]] = + cidMapperInstance + + final implicit def cidResolverV1Instance[A1, A2]( + implicit mapper1: CidMapper.RelCidV1Resolver[A1, A2], + ): CidMapper.RelCidV1Resolver[VersionedValue[A1], VersionedValue[A2]] = + CidMapper.valueVersionCidV1Resolver + } /** The parent of all [[Value]] cases that cannot possibly have a Cid. @@ -312,7 +322,7 @@ object Value extends CidContainer1[Value] { } - object ContractInst extends CidContainer1[ContractInst] { + object ContractInst extends CidContainer1WithDefaultCidResolver[ContractInst] { implicit def equalInstance[Val: Equal]: Equal[ContractInst[Val]] = ScalazEqual.withNatural(Equal[Val].equalIsNatural) { (a, b) => import a._ @@ -350,10 +360,14 @@ object Value extends CidContainer1[Value] { object ContractId { implicit val equalInstance: Equal[ContractId] = Equal.equalA - implicit val noCidMapper: CidMapper.NoCidMapper[ContractId, Nothing] = - CidMapper.basicInstance[ContractId, Nothing] - implicit val noRelCidMapper: CidMapper.NoRelCidMapper[ContractId, AbsoluteContractId] = - CidMapper.basicInstance[ContractId, AbsoluteContractId] + implicit val noCidMapper: CidMapper.NoCidChecker[ContractId, Nothing] = + CidMapper.basicMapperInstance[ContractId, Nothing] + implicit val noRelCidMapper: CidMapper.NoRelCidChecker[ContractId, AbsoluteContractId] = + CidMapper.basicMapperInstance[ContractId, AbsoluteContractId] + implicit val relCidV0esolver: CidMapper.RelCidV0Resolver[ContractId, AbsoluteContractId] = + CidMapper.basicCidResolverInstance + implicit val relCidV1Resolver: CidMapper.RelCidV1Resolver[ContractId, AbsoluteContractId] = + CidMapper.basicCidResolverInstance } /** The constructor is private so that we make sure that only this object constructs @@ -362,7 +376,8 @@ object Value extends CidContainer1[Value] { final case class NodeId(index: Int) object NodeId { - implicit def cidMapperInstance[Fun]: CidMapper[NodeId, NodeId, Fun] = CidMapper.trivialMapper + implicit def cidMapperInstance[In, Out]: CidMapper[NodeId, NodeId, In, Out] = + CidMapper.trivialMapper } /*** Keys cannot contain contract ids */ diff --git a/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/value/ValueVersion.scala b/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/value/ValueVersion.scala index a09f50dd2d7..07b05d9a8ca 100644 --- a/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/value/ValueVersion.scala +++ b/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/value/ValueVersion.scala @@ -27,6 +27,7 @@ object ValueVersions private[value] val minEnum = ValueVersion("5") private[value] val minNumeric = ValueVersion("6") private[value] val minGenMap = ValueVersion("7") + private[value] val minContractIdV1 = ValueVersion("7") def assignVersion[Cid](v0: Value[Cid]): Either[String, ValueVersion] = { import VersionTimeline.{maxVersion => maxVV} diff --git a/daml-lf/transaction/src/test/scala/com/digitalasset/daml/lf/crypto/HashSpec.scala b/daml-lf/transaction/src/test/scala/com/digitalasset/daml/lf/crypto/HashSpec.scala index bedccbc3370..baa566a240f 100644 --- a/daml-lf/transaction/src/test/scala/com/digitalasset/daml/lf/crypto/HashSpec.scala +++ b/daml-lf/transaction/src/test/scala/com/digitalasset/daml/lf/crypto/HashSpec.scala @@ -90,7 +90,7 @@ class HashSpec extends WordSpec with Matchers { val hash = "ea24627f5b014af67dbedb13d950e60be7f96a1a5bd9fb1a3b9a85b7fa9db4bc" val value = complexRecordT.inj(complexRecordV) val name = defRef("module", "name") - Hash.hashContractKey(GlobalKey(name, value)).toLedgerString shouldBe hash + Hash.hashContractKey(GlobalKey(name, value)).toHexaString shouldBe hash } "be deterministic and thread safe" in { @@ -542,7 +542,7 @@ class HashSpec extends WordSpec with Matchers { .builder(Hash.Purpose.Testing) .addTypedValue(value) .build - .toLedgerString + .toHexaString s"${value.toString}$sep $hash" } .mkString("", sep, sep) @@ -554,7 +554,7 @@ class HashSpec extends WordSpec with Matchers { "Hash.fromString" should { "convert properly string" in { val s = "01cf85cfeb36d628ca2e6f583fa2331be029b6b28e877e1008fb3f862306c086" - Hash.assertFromString(s).toLedgerString shouldBe s + Hash.assertFromString(s).toHexaString shouldBe s } } diff --git a/daml-lf/transaction/src/test/scala/com/digitalasset/daml/lf/value/ValueSpec.scala b/daml-lf/transaction/src/test/scala/com/digitalasset/daml/lf/value/ValueSpec.scala index 72240b704ce..87e651ec614 100644 --- a/daml-lf/transaction/src/test/scala/com/digitalasset/daml/lf/value/ValueSpec.scala +++ b/daml-lf/transaction/src/test/scala/com/digitalasset/daml/lf/value/ValueSpec.scala @@ -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.{FrontStack, ImmArray, Ref, Unnatural} import com.digitalasset.daml.lf.value.Value._ @@ -44,6 +45,68 @@ class ValueSpec extends FreeSpec with Matchers with Checkers with GeneratorDrive } } + "VersionedValue" - { + + val pkgId = Ref.PackageId.assertFromString("pkgId") + val tmplId = Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Mod:Template")) + + "does not bump version when" - { + + "ensureNoCid is used " in { + val value = VersionedValue[ContractId](ValueVersions.minVersion, ValueUnit) + val contract = ContractInst(tmplId, value, "agreed") + value.ensureNoCid.map(_.version) shouldBe Right(ValueVersions.minVersion) + contract.ensureNoCid.map(_.arg.version) shouldBe Right(ValueVersions.minVersion) + + } + + "ensureNoRelCid is used " in { + val value = VersionedValue( + ValueVersions.minVersion, + ValueContractId(AbsoluteContractId(Ref.ContractIdStringV0.assertFromString("#0:0"))), + ) + val contract = ContractInst(tmplId, value, "agreed") + value.ensureNoRelCid.map(_.version) shouldBe Right(ValueVersions.minVersion) + contract.ensureNoRelCid.map(_.arg.version) shouldBe Right(ValueVersions.minVersion) + } + + "resolveRelCidV0 is used" in { + val value = VersionedValue( + ValueVersions.minVersion, + ValueContractId(ValueContractId(RelativeContractId(NodeId(0), Some(randomHash())))), + ) + val contract = ContractInst(tmplId, value, "agreed") + val resolver: RelativeContractId => Ref.ContractIdStringV0 = { + case RelativeContractId(NodeId(idx), _) => + Ref.ContractIdStringV0.assertFromString(s"#0:$idx") + } + value.resolveRelCidV0(resolver).version shouldBe ValueVersions.minVersion + contract.resolveRelCidV0(resolver).arg.version shouldBe ValueVersions.minVersion + } + + } + + "does bump version when" - { + "resolveRelCidV1 is used" in { + val value = VersionedValue( + ValueVersions.minVersion, + ValueContractId(RelativeContractId(NodeId(0), Some(randomHash()))), + ) + val contract = ContractInst(tmplId, value, "agreed") + val resolver: RelativeContractId => Either[String, Ref.ContractIdStringV1] = { + case RelativeContractId(_, Some(hash)) => + Right(Ref.ContractIdStringV1.assertFromString("$0" + hash.toHexaString)) + case RelativeContractId(_, _) => + Left("unexpected relative contractId without discriminator") + } + value.resolveRelCidV1(resolver).map(_.version) shouldBe Right(ValueVersions.minContractIdV1) + contract.resolveRelCidV1(resolver).map(_.arg.version) shouldBe Right( + ValueVersions.minContractIdV1) + } + } + + } + "Equal" - { import com.digitalasset.daml.lf.value.ValueGenerators._ import org.scalacheck.Arbitrary @@ -59,4 +122,6 @@ class ValueSpec extends FreeSpec with Matchers with Checkers with GeneratorDrive scalaz.Equal[T].equal(a, b) shouldBe (a == b) } } + + private val randomHash = crypto.Hash.secureRandom } diff --git a/ledger/participant-state/kvutils/src/main/scala/com/daml/ledger/participant/state/kvutils/Conversions.scala b/ledger/participant-state/kvutils/src/main/scala/com/daml/ledger/participant/state/kvutils/Conversions.scala index 07e1ee2d106..c42bfd80013 100644 --- a/ledger/participant-state/kvutils/src/main/scala/com/daml/ledger/participant/state/kvutils/Conversions.scala +++ b/ledger/participant-state/kvutils/src/main/scala/com/daml/ledger/participant/state/kvutils/Conversions.scala @@ -7,7 +7,8 @@ import java.time.{Duration, Instant} import com.daml.ledger.participant.state.kvutils.DamlKvutils._ import com.daml.ledger.participant.state.v1.{PackageId, SubmittedTransaction, SubmitterInfo} -import com.digitalasset.daml.lf.data.Ref.{ContractIdString, LedgerString, Party} +import com.digitalasset.daml.lf.data.Ref +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._ @@ -35,12 +36,12 @@ private[state] object Conversions { def packageStateKey(packageId: PackageId): DamlStateKey = DamlStateKey.newBuilder.setPackageId(packageId).build - def toAbsCoid(txId: DamlLogEntryId, coid: RelativeContractId): ContractIdString = { + def toAbsCoid(txId: DamlLogEntryId, coid: RelativeContractId): Ref.ContractIdStringV0 = { val hexTxId = BaseEncoding.base16.encode(txId.getEntryId.toByteArray) // NOTE(JM): Must be in sync with [[absoluteContractIdToLogEntryId]] and // [[absoluteContractIdToStateKey]]. - ContractIdString.assertFromString(s"$hexTxId:${coid.txnid.index}") + Ref.ContractIdStringV0.assertFromString(s"$hexTxId:${coid.txnid.index}") } def absoluteContractIdToLogEntryId(acoid: AbsoluteContractId): (DamlLogEntryId, Int) = @@ -87,7 +88,7 @@ private[state] object Conversions { def decodeContractId(coid: DamlContractId): AbsoluteContractId = { val hexTxId = BaseEncoding.base16.encode(coid.getEntryId.getEntryId.toByteArray) - AbsoluteContractId(ContractIdString.assertFromString(s"$hexTxId:${coid.getNodeId}")) + AbsoluteContractId(Ref.ContractIdString.assertFromString(s"$hexTxId:${coid.getNodeId}")) } def stateKeyToContractId(key: DamlStateKey): AbsoluteContractId = { diff --git a/ledger/participant-state/kvutils/src/main/scala/com/daml/ledger/participant/state/kvutils/KeyValueConsumption.scala b/ledger/participant-state/kvutils/src/main/scala/com/daml/ledger/participant/state/kvutils/KeyValueConsumption.scala index 3998116f2a7..a3b63d95ec5 100644 --- a/ledger/participant-state/kvutils/src/main/scala/com/daml/ledger/participant/state/kvutils/KeyValueConsumption.scala +++ b/ledger/participant-state/kvutils/src/main/scala/com/daml/ledger/participant/state/kvutils/KeyValueConsumption.scala @@ -240,7 +240,7 @@ object KeyValueConsumption { txId: DamlLogEntryId, tx: SubmittedTransaction): CommittedTransaction = /* Assign absolute contract ids */ - tx.resolveRelCid(toAbsCoid(txId, _)) + tx.resolveRelCidV0(toAbsCoid(txId, _)) @throws(classOf[Err]) private def parseLedgerString(what: String)(s: String): Ref.LedgerString = diff --git a/ledger/participant-state/kvutils/src/main/scala/com/daml/ledger/participant/state/kvutils/committing/ProcessTransactionSubmission.scala b/ledger/participant-state/kvutils/src/main/scala/com/daml/ledger/participant/state/kvutils/committing/ProcessTransactionSubmission.scala index a39466ae60a..10d961a838d 100644 --- a/ledger/participant-state/kvutils/src/main/scala/com/daml/ledger/participant/state/kvutils/committing/ProcessTransactionSubmission.scala +++ b/ledger/participant-state/kvutils/src/main/scala/com/daml/ledger/participant/state/kvutils/committing/ProcessTransactionSubmission.scala @@ -231,7 +231,7 @@ private[kvutils] case class ProcessTransactionSubmission( blindingInfo.localDisclosure(NodeId(key.getContractId.getNodeId.toInt)) cs.addAllLocallyDisclosedTo((localDisclosure: Iterable[String]).asJava) val absCoInst = - createNode.coinst.resolveRelCid(Conversions.toAbsCoid(entryId, _)) + createNode.coinst.resolveRelCidV0(Conversions.toAbsCoid(entryId, _)) cs.setContractInstance( Conversions.encodeContractInstance(absCoInst) ) diff --git a/ledger/sandbox/src/main/scala/com/digitalasset/platform/sandbox/stores/ledger/Ledger.scala b/ledger/sandbox/src/main/scala/com/digitalasset/platform/sandbox/stores/ledger/Ledger.scala index 0cac95d795a..a6e374362a7 100644 --- a/ledger/sandbox/src/main/scala/com/digitalasset/platform/sandbox/stores/ledger/Ledger.scala +++ b/ledger/sandbox/src/main/scala/com/digitalasset/platform/sandbox/stores/ledger/Ledger.scala @@ -67,7 +67,7 @@ object Ledger { // First we "commit" the transaction by converting all relative contractIds to absolute ones val committedTransaction: GenTransaction.WithTxValue[NodeId, AbsoluteContractId] = - transaction.resolveRelCid(EventIdFormatter.makeAbs(transactionId)) + transaction.resolveRelCidV0(EventIdFormatter.makeAbs(transactionId)) // here we just need to align the type for blinding val blindingInfo = Blinding.blind(committedTransaction) diff --git a/ledger/sandbox/src/main/scala/com/digitalasset/platform/store/ActiveLedgerStateManager.scala b/ledger/sandbox/src/main/scala/com/digitalasset/platform/store/ActiveLedgerStateManager.scala index ee43ca14800..69ee713ba2b 100644 --- a/ledger/sandbox/src/main/scala/com/digitalasset/platform/store/ActiveLedgerStateManager.scala +++ b/ledger/sandbox/src/main/scala/com/digitalasset/platform/store/ActiveLedgerStateManager.scala @@ -139,7 +139,7 @@ class ActiveLedgerStateManager[ALS <: ActiveLedgerState[ALS]](initialState: => A transactionId = transactionId, eventId = nodeId, workflowId = workflowId, - contract = nc.coinst.resolveRelCid(EventIdFormatter.makeAbs(transactionId)), + contract = nc.coinst.resolveRelCidV0(EventIdFormatter.makeAbs(transactionId)), witnesses = disclosure(nodeId), // The divulgences field used to be filled with data coming from the `localDivulgence` field of the blinding info. // But this field is always empty in transactions with only absolute contract ids.