Skip to content

Commit

Permalink
LF: Update specification with Contract ID Comparability check (#10703)
Browse files Browse the repository at this point in the history
This is part of #10504.

CHANGELOG_BEGIN
CHANGELOG_END
  • Loading branch information
remyhaemmerle-da authored Aug 31, 2021
1 parent e5c4734 commit 9ef3377
Show file tree
Hide file tree
Showing 9 changed files with 84 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,10 @@ final class Conversions(
builder.setContractIdInContractKey(
proto.ScenarioError.ContractIdInContractKey.newBuilder.setKey(convertValue(key))
)
case ContractIdFreshness(_) =>
case ContractIdComparability(_) =>
// We crash here because you cannot construct a cid yourself in scenarios
// or daml script.
builder.setCrash(s"Contract Id freshness Error")
builder.setCrash(s"Contract Id comparability Error")
case NonComparableValues =>
builder.setComparableValueError(proto.Empty.newBuilder)
case ValueExceedsMaxNesting =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ private[lf] object Pretty {
text("Update failed due to a unknown contract") & prettyContractId(cid)
case NonComparableValues =>
text("functions are not comparable")
case ContractIdFreshness(globalCid) =>
case ContractIdComparability(globalCid) =>
text(s"The global contract ID $globalCid conflicts with a local contract ID")
case ContractIdInContractKey(key) =>
text(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,13 @@ private[lf] object Equality {
SContractId(cid2 @ ContractId.V1(discriminator2, suffix2)),
) if discriminator1 == discriminator2 =>
if (suffix1.isEmpty)
throw SError.SErrorDamlException(interpretation.Error.ContractIdFreshness(cid2))
throw SError.SErrorDamlException(
interpretation.Error.ContractIdComparability(cid2)
)
else if (suffix2.isEmpty)
throw SError.SErrorDamlException(interpretation.Error.ContractIdFreshness(cid1))
throw SError.SErrorDamlException(
interpretation.Error.ContractIdComparability(cid1)
)
else
false
case _ =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ object Ordering extends scala.math.Ordering[SValue] {
}
@inline
private[this] def compareCid(cid1: ContractId, cid2: ContractId): Int = {
// Note that the engine never produce V0 contract IDs directly
// (V0 contract ID scheme can only be "emulated" at commit using
// `com.daml.lf.transaction.LegacyTransactionCommitter`).
// So we can assume here that all V0 Contract IDs are global.
(cid1, cid2) match {
case (ContractId.V0(s1), ContractId.V0(s2)) =>
s1 compareTo s2
Expand All @@ -125,11 +129,11 @@ object Ordering extends scala.math.Ordering[SValue] {
if (suffix2.isEmpty) {
0
} else {
throw SError.SErrorDamlException(interpretation.Error.ContractIdFreshness(cid2))
throw SError.SErrorDamlException(interpretation.Error.ContractIdComparability(cid2))
}
} else {
if (suffix2.isEmpty) {
throw SError.SErrorDamlException(interpretation.Error.ContractIdFreshness(cid1))
throw SError.SErrorDamlException(interpretation.Error.ContractIdComparability(cid1))
} else {
Bytes.ordering.compare(suffix1, suffix2)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package svalue

import com.daml.lf.crypto
import com.daml.lf.data.Bytes
import com.daml.lf.interpretation.Error.ContractIdFreshness
import com.daml.lf.interpretation.Error.ContractIdComparability
import com.daml.lf.speedy.SValue._
import com.daml.lf.value.Value
import org.scalatest.Inside
Expand Down Expand Up @@ -56,9 +56,9 @@ class EqualitySpec extends AnyWordSpec with Inside with Matchers with ScalaCheck

forEvery(positiveTestCases) { globalCid =>
Try(Equality.areEqual(vCid10, SContractId(globalCid))) shouldBe
Failure(SError.SErrorDamlException(ContractIdFreshness(globalCid)))
Failure(SError.SErrorDamlException(ContractIdComparability(globalCid)))
Try(Equality.areEqual(SContractId(globalCid), vCid10)) shouldBe
Failure(SError.SErrorDamlException(ContractIdFreshness(globalCid)))
Failure(SError.SErrorDamlException(ContractIdComparability(globalCid)))
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import com.daml.lf.value.test.TypedValueGenerators.genAddend
import com.daml.lf.value.test.ValueGenerators.{cidV0Gen, comparableCoidsGen}
import com.daml.lf.PureCompiledPackages
import com.daml.lf.iface
import com.daml.lf.interpretation.Error.ContractIdFreshness
import com.daml.lf.interpretation.Error.ContractIdComparability
import com.daml.lf.language.{Ast, Util => AstUtil}
import org.scalacheck.{Arbitrary, Gen}
import org.scalatest.Inside
Expand Down Expand Up @@ -173,9 +173,9 @@ class OrderingSpec

forEvery(positiveTestCases) { globalCid =>
Try(Ordering.compare(vCid10, SContractId(globalCid))) shouldBe
Failure(SError.SErrorDamlException(ContractIdFreshness(globalCid)))
Failure(SError.SErrorDamlException(ContractIdComparability(globalCid)))
Try(Ordering.compare(SContractId(globalCid), vCid10)) shouldBe
Failure(SError.SErrorDamlException(ContractIdFreshness(globalCid)))
Failure(SError.SErrorDamlException(ContractIdComparability(globalCid)))
}

}
Expand Down
64 changes: 38 additions & 26 deletions daml-lf/spec/contract-id.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,18 @@ projection. That is, the contract IDs for contracts created in the
projection of a transaction to a set of parties can be computed
solely from the projection and input seeds.

**Unlinkability**: It is computationally infeasible to link the contract
contents to the contract ID unless the create node is witnessed or the input seeds are known. The
contract contents include the contract instance, the template ID, the
stakeholders / signatories / maintainers, and the contract key.
**Unlinkability**: It is computationally infeasible to link the
contract contents to the contract ID unless the create node is
witnessed or the input seeds are known. The contract contents include
the contract instance, the template ID, the stakeholders / signatories
/ maintainers, and the contract key.

**Freshness**: It is computationally infeasible to find contracts C1 and
C2 with the same contract ID such that one of the following holds:
**Freshness**: It is computationally infeasible to find contracts C1
and C2 with the same contract ID such that one of the following holds:

* The contracts have different stakeholders.
* The contracts are created by different transactions.
* The contracts are created by different subactions of the same
* The contracts are created by different sub-actions of the same
transaction forest where the seeds are pairwise different.

**Distinctness**: The ledger can enforce that the seeds are pairwise
Expand Down Expand Up @@ -64,16 +65,16 @@ where
uniqueness of the contract ID in a distributed ledger.


Discriminator freshness
-----------------------
Local/Global Contract IDs
-------------------------

In a transaction we distinguish two kinds of contract IDs:

* The *local* contract IDs are the IDs of the contracts created by the
transaction.

* The *global* contract IDs are the contract IDs that:

* appear in the commands that produced the transaction. This
includes the IDs of the exercised contract, together with all the
IDs referenced in the arguments of the create and exercise
Expand All @@ -82,19 +83,11 @@ In a transaction we distinguish two kinds of contract IDs:
* are referenced in the input contracts.

Note that local contract IDs correspond to the ID of output contracts
together with those contracts that have been created and archived in
the transaction. On the other hand, global contract IDs do not
only reference IDs of some contracts that have been previously
persisted on the ledger, but also any arbitrary contract IDs that the
submitter has referenced in its submission.

The so-called *discriminator freshness condition* holds if the
discriminators from local contract IDs are distinct from the
discriminators from global contract IDs.

This ensures that only the discriminators, not the suffix are needed
to order the contract IDs of created contracts w.r.t. other contract
IDs.
together with those contracts that have been both created and archived
in the transaction. On the other hand, global contract IDs do not only
reference IDs of some contracts that have been previously persisted on
the ledger, but also any arbitrary contract IDs that the submitter has
referenced in its submission.


Contract ID uniqueness
Expand All @@ -116,6 +109,23 @@ discriminator allocation scheme ensures in this case the uniqueness of
allocated discriminators.


Contract ID Comparability
-------------------------

The so-called *contract ID comparability restriction*, states that the
comparison of a local contract ID with a global contract ID with the
same discriminator is forbidden. Any attempt to compare such IDs
during interpretation, either explicitly (using Generic equality and
order builtins) or implicitly (though ordering of key in generic
maps), must be rejected. Ledger implementations that suffix contract
IDs should furthermore enforce that all global Contract IDs are
suffixed.

This ensures that only the discriminators, not the suffix are needed
to compare the contract IDs of local contract IDs w.r.t. global
contract IDs.


Submission time
---------------

Expand Down Expand Up @@ -232,9 +242,11 @@ The submission performs the following steps:

Depending on the ledger implementation, the local contract IDs are
suffixed with a suffix in a later step. This yields the *committed
transaction*. For ledgers that do not require suffixing, committed and submitted
transactions coincide. Committed transactions are the source of truth to
derive the state of the ledger.
transaction*.

For ledgers that do not require suffixing, committed and
submitted transactions coincide. Committed transactions are the source
of truth to derive the state of the ledger.


Validation
Expand Down
26 changes: 20 additions & 6 deletions daml-lf/spec/daml-lf-1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -470,11 +470,11 @@ V0 contract identifiers, a Daml-LF compliant engine must refuse to
load any Daml-LF >= 1.11 archives. On the contrary, when configured
to produce V1 contract IDs, a Daml-LF compliant engine must accept to
load any non-deprecated Daml-LF version. V1 Contract IDs allocation
scheme is described in the `V1 Contract ID allocation
scheme specification <./contract-id.rst>`_.

Also note that package identifiers are typically `cryptographic hash
<Package hash_>`_ of the content of the package itself.
scheme is described in the `V1 Contract ID allocation scheme
specification <./contract-id.rst>`_. In the following we will say that
a V1 contract identifier is *non-suffixed* if it is built from exactly
66 charters. Otherwise (meaning it has between 68 and 254 charters) we
will say it is *suffixed*.

Literals
~~~~~~~~
Expand Down Expand Up @@ -3554,6 +3554,15 @@ The following builtin functions defines an order on the so-called
abstractions, functions, partially applied builtin functions, and
updates.

Note that as described in the `V1 Contract ID allocation scheme
specification <./contract-id.rst>`_ the comparison of two V1 contract
identifiers may fail at run time. For the purpose of this
specification, we will say that two contract identifiers are *not
comparable* if (i) both of them are V1 contract identifiers, (ii) one
of them is ``non-suffixed``, and (iii) is a strict prefixed of the
other one.


* ``LESS_EQ : ∀ (α:*). α → α → 'Bool'``

The builtin function ``LESS_EQ`` returns ``'True'`` if the first
Expand Down Expand Up @@ -3602,7 +3611,12 @@ updates.
𝕆('LESS_EQ' @σ LitRoundingMode₁ LitRoundingMode₂) =
Ok (LitRoundingMode₁ ≤ₗ LitRoundingMode₂)

—————————————————————————————————————————————————————————————————————— EvLessEqContractId
cid₁ and cid₂ are not comparable
—————————————————————————————————————————————————————————————————————— EvLessEqNonComparableContractId
𝕆('LESS_EQ' @σ cid₁ cid₂) = Err 'ContractIdComparability'

cid₁ and cid₂ are comparable
—————————————————————————————————————————————————————————————————————— EvLessEqComparableContractId
𝕆('LESS_EQ' @σ cid₁ cid₂) = Ok (cid₁ ≤ₗ cid₂)

—————————————————————————————————————————————————————————————————————— EvLessEqStructEmpty
Expand Down
6 changes: 5 additions & 1 deletion daml-lf/transaction/src/main/scala/com/daml/lf/Error.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,11 @@ object Error {
// as are not serializable.
final case object NonComparableValues extends Error

final case class ContractIdFreshness(globalCid: ContractId.V1) extends Error
// Attempt to compare the global contract ID `globalCid` and a local
// contract ID with same discriminator. See the "Contract ID
// Comparability" section in the contract ID specification
// (//daml-lf/spec/contract-id.rst) for more details.
final case class ContractIdComparability(globalCid: ContractId.V1) extends Error

final case class ContractIdInContractKey(key: Value[ContractId]) extends Error

Expand Down

0 comments on commit 9ef3377

Please sign in to comment.