diff --git a/compiler/scenario-service/server/src/main/scala/com/digitalasset/daml/lf/scenario/Conversions.scala b/compiler/scenario-service/server/src/main/scala/com/digitalasset/daml/lf/scenario/Conversions.scala index 0e6bb9ce8f1b..1600b75cb4f3 100644 --- a/compiler/scenario-service/server/src/main/scala/com/digitalasset/daml/lf/scenario/Conversions.scala +++ b/compiler/scenario-service/server/src/main/scala/com/digitalasset/daml/lf/scenario/Conversions.scala @@ -310,7 +310,7 @@ case class Conversions(homePackageId: Ref.PackageId) { .setContractId(coid) .setTemplateId(convertIdentifier(templateId)) .build - case V.RelativeContractId(txnid, _) => + case V.RelativeContractId(txnid) => ContractRef.newBuilder .setRelative(true) .setContractId(txnid.index.toString) @@ -321,7 +321,7 @@ case class Conversions(homePackageId: Ref.PackageId) { def convertContractId(coid: V.ContractId): String = coid match { case V.AbsoluteContractId(coid) => coid - case V.RelativeContractId(txnid, _) => txnid.index.toString + case V.RelativeContractId(txnid) => txnid.index.toString } def convertScenarioStep( @@ -653,7 +653,7 @@ case class Conversions(homePackageId: Ref.PackageId) { coid match { case V.AbsoluteContractId(acoid) => builder.setContractId(acoid) - case V.RelativeContractId(txnid, _) => + case V.RelativeContractId(txnid) => builder.setContractId(txnid.index.toString) } case V.ValueList(values) => diff --git a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Pretty.scala b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Pretty.scala index 50493185afd2..cad14e920028 100644 --- a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Pretty.scala +++ b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Pretty.scala @@ -339,7 +339,7 @@ object Pretty { def prettyContractId(coid: ContractId): Doc = coid match { case AbsoluteContractId(acoid) => text(acoid) - case RelativeContractId(rcoid, _) => str(rcoid) + case RelativeContractId(rcoid) => str(rcoid) } def prettyActiveContracts(c: L.LedgerData): Doc = { @@ -416,7 +416,7 @@ object Pretty { text(constructor) case ValueText(t) => char('"') + text(t) + char('"') case ValueContractId(AbsoluteContractId(acoid)) => text(acoid) - case ValueContractId(RelativeContractId(rcoid, _)) => + case ValueContractId(RelativeContractId(rcoid)) => char('~') + text(rcoid.toString) case ValueUnit => text("") case ValueBool(b) => str(b) diff --git a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltin.scala b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltin.scala index 3c35440de713..9c24e0ceee20 100644 --- a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltin.scala +++ b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltin.scala @@ -949,48 +949,50 @@ object SBuiltin { case SContractId(coid) => coid case v => crash(s"expected contract id, got: $v") } - val arg = coid match { - case rcoid: V.RelativeContractId => - machine.ptx.lookupLocalContract(rcoid) match { - case None => - crash(s"Relative contract $rcoid ($templateId) not found from partial transaction") - case Some((_, Some(consumedBy))) => - throw DamlELocalContractNotActive(coid, templateId, consumedBy) - case Some((coinst, None)) => - // Here we crash hard rather than throwing a "nice" error - // ([[DamlEWronglyTypedContract]]) since if _relative_ contract - // id to be of the wrong template it means that the DAML-LF - // program that generated it is ill-typed. - // - // On the other hand absolute contract ids can come from outside - // (e.g. Ledger API) and thus we need to fail more gracefully - // (see below). - if (coinst.template != templateId) { + val coinst = + machine.ptx + .lookupLocalContract(coid) + .getOrElse( + coid match { + case acoid: V.AbsoluteContractId => + throw SpeedyHungry( + SResultNeedContract( + acoid, + templateId, + machine.committers, + cbMissing = _ => machine.tryHandleException(), + cbPresent = { + coinst => + // Note that we cannot throw in this continuation -- instead + // set the control appropriately which will crash the machine + // correctly later. + if (coinst.template != templateId) { + machine.ctrl = + CtrlWronglyTypeContractId(acoid, templateId, coinst.template) + } else { + machine.ctrl = CtrlValue(SValue.fromValue(coinst.arg.value)) + } + }, + ), + ) + case rcoid: V.RelativeContractId => crash(s"Relative contract $rcoid ($templateId) not found from partial transaction") - } - coinst.arg - } - case acoid: V.AbsoluteContractId => - throw SpeedyHungry( - SResultNeedContract( - acoid, - templateId, - machine.committers, - cbMissing = _ => machine.tryHandleException(), - cbPresent = { coinst => - // Note that we cannot throw in this continuation -- instead - // set the control appropriately which will crash the machine - // correctly later. - if (coinst.template != templateId) { - machine.ctrl = CtrlWronglyTypeContractId(acoid, templateId, coinst.template) - } else { - machine.ctrl = CtrlValue(SValue.fromValue(coinst.arg.value)) - } - }, - ), + } ) + + if (coinst.template != templateId) { + // Here we crash hard rather than throwing a "nice" error + // ([[DamlEWronglyTypedContract]]) since if _relative_ contract + // id to be of the wrong template it means that the DAML-LF + // program that generated it is ill-typed. + // + // On the other hand absolute contract ids can come from outside + // (e.g. Ledger API) and thus we need to fail more gracefully + // (see below). + crash(s"Relative contract $coid ($templateId) not found from partial transaction") + } else { + machine.ctrl = CtrlValue(SValue.fromValue(coinst.arg.value)) } - machine.ctrl = CtrlValue(SValue.fromValue(arg.value)) } } diff --git a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/transaction/PartialTransaction.scala b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/transaction/PartialTransaction.scala index 42295c9a531e..5719c3858b9c 100644 --- a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/transaction/PartialTransaction.scala +++ b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/transaction/PartialTransaction.scala @@ -95,6 +95,7 @@ object PartialTransaction { context = ContextRoot(seedWithTime.map(_._1)), aborted = None, keys = Map.empty, + localContracts = Map.empty, ) } @@ -131,6 +132,7 @@ case class PartialTransaction( context: PartialTransaction.Context, aborted: Option[Tx.TransactionError], keys: Map[Node.GlobalKey, Option[Value.ContractId]], + localContracts: Map[Value.ContractId, Value.NodeId] ) { import PartialTransaction._ @@ -179,10 +181,6 @@ case class PartialTransaction( sb.toString } - def resolveCidDiscriminator(node: Tx.Node) = - node.resolveRelCid(cid => - Ref.ContractIdString.assertFromString("0" + cid.discriminator.get.toHexaString)) - /** Finish building a transaction; i.e., try to extract a complete * transaction from the given 'PartialTransaction'. This fails if * the 'PartialTransaction' is not yet complete or has been @@ -193,9 +191,7 @@ case class PartialTransaction( case ContextRoot(transactionSeed, children) if aborted.isEmpty => Right( GenTransaction( - nodes = - if (transactionSeed.isEmpty) nodes - else nodes.transform((_, v) => resolveCidDiscriminator(v)), + nodes = nodes, roots = children.toImmArray, optUsedPackages = None, transactionSeed = transactionSeed, @@ -210,13 +206,14 @@ case class PartialTransaction( * consumed if any. */ def lookupLocalContract( - lcoid: Value.RelativeContractId, - ): Option[(Value.ContractInst[Tx.Value[Value.ContractId]], Option[Value.NodeId])] = + lcoid: Value.ContractId, + ): Option[Value.ContractInst[Tx.Value[Value.ContractId]]] = for { - node <- nodes.get(lcoid.txnid) + nid <- localContracts.get(lcoid) + node <- nodes.get(nid) coinst <- node match { case create: Node.NodeCreate.WithTxValue[Value.ContractId] => - Some((create.coinst, consumedBy.get(lcoid))) + Some(create.coinst) case _: Node.NodeExercises[_, _, _] | _: Node.NodeFetch[_] | _: Node.NodeLookupByKey[_, _] => None @@ -241,12 +238,17 @@ case class PartialTransaction( ) } else { val nodeSeed = deriveChildSeed - val contractDiscriminator = + val discriminator = for { seed <- nodeSeed time <- submissionTime } yield crypto.Hash.deriveContractDiscriminator(seed, time, stakeholders) - val cid = Value.RelativeContractId(Value.NodeId(nextNodeIdx), contractDiscriminator) + val cid = discriminator.fold[Value.ContractId]( + Value.RelativeContractId(Value.NodeId(nextNodeIdx)) + )( + hash => + Value.AbsoluteContractId(Ref.ContractIdString.assertFromString("0" + hash.toHexaString)) + ) val createNode = Node.NodeCreate( nodeSeed, cid, @@ -261,6 +263,7 @@ case class PartialTransaction( nextNodeIdx = nextNodeIdx + 1, context = context.addChild(nid), nodes = nodes.updated(nid, createNode), + localContracts = localContracts.updated(cid, nid) ) // if we have a contract key being added, include it in the list of 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 bf7019a72a67..c6729189ee25 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 @@ -349,8 +349,7 @@ object Value extends CidContainer1WithDefaultCidResolver[Value] { */ sealed trait ContractId extends Product with Serializable final case class AbsoluteContractId(coid: ContractIdString) extends ContractId - final case class RelativeContractId(txnid: NodeId, discriminator: Option[crypto.Hash] = None) - extends ContractId + final case class RelativeContractId(txnid: NodeId) extends ContractId object ContractId { implicit val equalInstance: Equal[ContractId] = Equal.equalA diff --git a/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/value/ValueCoder.scala b/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/value/ValueCoder.scala index c6b9222d4428..af2341d1e239 100644 --- a/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/value/ValueCoder.scala +++ b/daml-lf/transaction/src/main/scala/com/digitalasset/daml/lf/value/ValueCoder.scala @@ -60,13 +60,13 @@ object ValueCoder { ): Either[EncodeError, Either[String, (proto.ContractId)]] = if (useOldStringField(sv)) cid match { - case RelativeContractId(nid, _) => + case RelativeContractId(nid) => Right(Left("~" + nid.index.toString)) case AbsoluteContractId(s) => Right(Left(s)) } else cid match { - case RelativeContractId(nid, _) => + case RelativeContractId(nid) => Right( Right( proto.ContractId.newBuilder @@ -105,7 +105,7 @@ object ValueCoder { .bimap( _ => // DecodeError(s"""cannot parse relative contractId "$s""""), - idx => Some(RelativeContractId(NodeId(idx), None)) + idx => Some(RelativeContractId(NodeId(idx))) ) private def stringToCidString(s: String): Either[DecodeError, Ref.ContractIdString] = @@ -151,7 +151,7 @@ object ValueCoder { structForm: ValueOuterClass.ContractId, ): Either[DecodeError, Option[AbsoluteContractId]] = CidDecoder.decodeOptional(sv, stringForm, structForm).flatMap { - case Some(RelativeContractId(_, _)) => + case Some(RelativeContractId(_)) => Left(DecodeError("Unexpected relative contractId")) case Some(AbsoluteContractId(coid)) => Right(Some(AbsoluteContractId(coid))) 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 9f4b3627fdc9..60e3c01b2d8e 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 @@ -73,11 +73,11 @@ class ValueSpec extends FreeSpec with Matchers with Checkers with GeneratorDrive "resolveRelCidV0 is used" in { val value = VersionedValue( ValueVersions.minVersion, - ValueContractId(ValueContractId(RelativeContractId(NodeId(0), Some(randomHash())))), + ValueContractId(ValueContractId(RelativeContractId(NodeId(0)))), ) val contract = ContractInst(tmplId, value, "agreed") val resolver: RelativeContractId => Ref.ContractIdString = { - case RelativeContractId(NodeId(idx), _) => + case RelativeContractId(NodeId(idx)) => Ref.ContractIdString.assertFromString(s"#0:$idx") } value.resolveRelCid(resolver).version shouldBe ValueVersions.minVersion @@ -104,5 +104,4 @@ class ValueSpec extends FreeSpec with Matchers with Checkers with GeneratorDrive } } - 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 cd00b0bed0b0..d5c72d8063bc 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 @@ -75,7 +75,8 @@ private[state] object Conversions { def relativeContractIdToStateKey( entryId: DamlLogEntryId, - rcoid: RelativeContractId): DamlStateKey = + rcoid: RelativeContractId, + ): DamlStateKey = DamlStateKey.newBuilder .setContractId(encodeRelativeContractId(entryId, rcoid)) .build diff --git a/ledger/sandbox/src/main/scala/com/digitalasset/platform/events/EventIdFormatter.scala b/ledger/sandbox/src/main/scala/com/digitalasset/platform/events/EventIdFormatter.scala index 5f108fd8054d..a0126afebad8 100644 --- a/ledger/sandbox/src/main/scala/com/digitalasset/platform/events/EventIdFormatter.scala +++ b/ledger/sandbox/src/main/scala/com/digitalasset/platform/events/EventIdFormatter.scala @@ -22,7 +22,7 @@ object EventIdFormatter { def makeAbsCoid(transactionId: LedgerString)(coid: Lf.ContractId): Lf.AbsoluteContractId = coid match { case a @ Lf.AbsoluteContractId(_) => a - case Lf.RelativeContractId(txnid, _) => + case Lf.RelativeContractId(txnid) => Lf.AbsoluteContractId(fromTransactionId(transactionId, txnid)) }