Skip to content

Commit

Permalink
LF: Drop contract ID Freshness check
Browse files Browse the repository at this point in the history
As stated in #10504 the contract ID freshness check cannot be
implemented correctly in general.

This PR drops the support for this (buggy) check.

This corresponds to the fist task of #10504.

CHANGELOG_BEGIN
CHANGELOG_END
  • Loading branch information
remyhaemmerle-da committed Aug 19, 2021
1 parent 121047e commit 9f23bf3
Show file tree
Hide file tree
Showing 12 changed files with 70 additions and 576 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class Engine(val config: EngineConfig = new EngineConfig(LanguageVersion.StableV
val submissionTime = cmds.ledgerEffectiveTime
preprocessor
.preprocessCommands(cmds.commands)
.flatMap { case (processedCmds, globalCids) =>
.flatMap { processedCmds =>
interpretCommands(
validating = false,
submitters = submitters,
Expand All @@ -105,7 +105,6 @@ class Engine(val config: EngineConfig = new EngineConfig(LanguageVersion.StableV
ledgerTime = cmds.ledgerEffectiveTime,
submissionTime = submissionTime,
seeding = Engine.initialSeeding(submissionSeed, participantId, submissionTime),
globalCids,
) map { case (tx, meta) =>
// Annotate the transaction with the package dependencies. Since
// all commands are actions on a contract template, with a fully typed
Expand Down Expand Up @@ -146,8 +145,7 @@ class Engine(val config: EngineConfig = new EngineConfig(LanguageVersion.StableV
ledgerEffectiveTime: Time.Timestamp,
): Result[(SubmittedTransaction, Tx.Metadata)] =
for {
commandWithCids <- preprocessor.preprocessCommand(command)
(speedyCommand, globalCids) = commandWithCids
speedyCommand <- preprocessor.preprocessCommand(command)
sexpr = compiledPackages.compiler.unsafeCompileForReinterpretation(speedyCommand)
// reinterpret is never used for submission, only for validation.
result <- interpretExpression(
Expand All @@ -158,7 +156,6 @@ class Engine(val config: EngineConfig = new EngineConfig(LanguageVersion.StableV
ledgerTime = ledgerEffectiveTime,
submissionTime = submissionTime,
seeding = InitialSeeding.RootNodeSeeds(ImmArray(nodeSeed)),
globalCids = globalCids,
)
(tx, meta) = result
} yield (tx, meta)
Expand All @@ -172,8 +169,7 @@ class Engine(val config: EngineConfig = new EngineConfig(LanguageVersion.StableV
submissionSeed: crypto.Hash,
): Result[(SubmittedTransaction, Tx.Metadata)] =
for {
commandsWithCids <- preprocessor.translateTransactionRoots(tx.transaction)
(commands, globalCids) = commandsWithCids
commands <- preprocessor.translateTransactionRoots(tx.transaction)
result <- interpretCommands(
validating = true,
submitters = submitters,
Expand All @@ -182,7 +178,6 @@ class Engine(val config: EngineConfig = new EngineConfig(LanguageVersion.StableV
ledgerTime = ledgerEffectiveTime,
submissionTime = submissionTime,
seeding = Engine.initialSeeding(submissionSeed, participantId, submissionTime),
globalCids,
)

} yield result
Expand Down Expand Up @@ -273,7 +268,6 @@ class Engine(val config: EngineConfig = new EngineConfig(LanguageVersion.StableV
ledgerTime: Time.Timestamp,
submissionTime: Time.Timestamp,
seeding: speedy.InitialSeeding,
globalCids: Set[Value.ContractId],
): Result[(SubmittedTransaction, Tx.Metadata)] = {
val sexpr = compiledPackages.compiler.unsafeCompile(commands)
interpretExpression(
Expand All @@ -284,7 +278,6 @@ class Engine(val config: EngineConfig = new EngineConfig(LanguageVersion.StableV
ledgerTime,
submissionTime,
seeding,
globalCids,
)
}

Expand All @@ -304,15 +297,13 @@ class Engine(val config: EngineConfig = new EngineConfig(LanguageVersion.StableV
ledgerTime: Time.Timestamp,
submissionTime: Time.Timestamp,
seeding: speedy.InitialSeeding,
globalCids: Set[Value.ContractId],
): Result[(SubmittedTransaction, Tx.Metadata)] =
runSafely(NameOf.qualifiedNameOfCurrentFunc) {
val machine = Machine(
compiledPackages = compiledPackages,
submissionTime = submissionTime,
initialSeeding = seeding,
expr = SExpr.SEApp(sexpr, Array(SExpr.SEValue.Token)),
globalCids = globalCids,
committers = submitters,
readAs = readAs,
validating = validating,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,14 +107,6 @@ object Error {

final case class RootNode(nodeId: NodeId, override val message: String) extends Error

final case class ContractIdFreshness(
localContractIds: Set[Value.ContractId],
globalContractIds: Set[Value.ContractId],
) extends Error {
assert(localContractIds exists globalContractIds)
def message: String = "Conflicting discriminators between a global and local contract ID."
}

}

// Error happening during interpretation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import com.daml.lf.data._
import com.daml.lf.language.Ast
import com.daml.lf.speedy.SValue
import com.daml.lf.value.Value
import com.daml.nameof.NameOf

import scala.annotation.tailrec

Expand All @@ -24,9 +23,9 @@ private[lf] final class CommandPreprocessor(compiledPackages: CompiledPackages)
def unsafePreprocessCreate(
templateId: Ref.Identifier,
argument: Value[Value.ContractId],
): (speedy.Command.Create, Set[Value.ContractId]) = {
val (arg, argCids) = valueTranslator.unsafeTranslateValue(Ast.TTyCon(templateId), argument)
speedy.Command.Create(templateId, arg) -> argCids
): speedy.Command.Create = {
val arg = valueTranslator.unsafeTranslateValue(Ast.TTyCon(templateId), argument)
speedy.Command.Create(templateId, arg)
}

@throws[Error.Preprocessing.Error]
Expand All @@ -35,11 +34,10 @@ private[lf] final class CommandPreprocessor(compiledPackages: CompiledPackages)
contractId: Value.ContractId,
choiceId: Ref.ChoiceName,
argument: Value[Value.ContractId],
): (speedy.Command.Exercise, Set[Value.ContractId]) = {
): speedy.Command.Exercise = {
val choice = handleLookup(interface.lookupChoice(templateId, choiceId)).argBinder._2
val (arg, argCids) = valueTranslator.unsafeTranslateValue(choice, argument)
val cids = argCids + contractId
speedy.Command.Exercise(templateId, SValue.SContractId(contractId), choiceId, arg) -> cids
val arg = valueTranslator.unsafeTranslateValue(choice, argument)
speedy.Command.Exercise(templateId, SValue.SContractId(contractId), choiceId, arg)
}

@throws[Error.Preprocessing.Error]
Expand All @@ -48,20 +46,12 @@ private[lf] final class CommandPreprocessor(compiledPackages: CompiledPackages)
contractKey: Value[Value.ContractId],
choiceId: Ref.ChoiceName,
argument: Value[Value.ContractId],
): (speedy.Command.ExerciseByKey, Set[Value.ContractId]) = {
): speedy.Command.ExerciseByKey = {
val choiceArgType = handleLookup(interface.lookupChoice(templateId, choiceId)).argBinder._2
val ckTtype = handleLookup(interface.lookupTemplateKey(templateId)).typ
val (arg, argCids) = valueTranslator.unsafeTranslateValue(choiceArgType, argument)
val (key, keyCids) = valueTranslator.unsafeTranslateValue(ckTtype, contractKey)
keyCids.foreach { coid =>
// The type checking of contractKey done by unsafeTranslateValue should ensure
// keyCids is empty
throw Error.Preprocessing.Internal(
NameOf.qualifiedNameOfCurrentFunc,
s"Unexpected contract IDs in contract key of $templateId: $coid",
)
}
speedy.Command.ExerciseByKey(templateId, key, choiceId, arg) -> argCids
val arg = valueTranslator.unsafeTranslateValue(choiceArgType, argument)
val key = valueTranslator.unsafeTranslateValue(ckTtype, contractKey)
speedy.Command.ExerciseByKey(templateId, key, choiceId, arg)
}

@throws[Error.Preprocessing.Error]
Expand All @@ -70,19 +60,19 @@ private[lf] final class CommandPreprocessor(compiledPackages: CompiledPackages)
createArgument: Value[Value.ContractId],
choiceId: Ref.ChoiceName,
choiceArgument: Value[Value.ContractId],
): (speedy.Command.CreateAndExercise, Set[Value.ContractId]) = {
val (createArg, createArgCids) =
): speedy.Command.CreateAndExercise = {
val createArg =
valueTranslator.unsafeTranslateValue(Ast.TTyCon(templateId), createArgument)
val choiceArgType = handleLookup(interface.lookupChoice(templateId, choiceId)).argBinder._2
val (choiceArg, choiceArgCids) =
val choiceArg =
valueTranslator.unsafeTranslateValue(choiceArgType, choiceArgument)
speedy.Command
.CreateAndExercise(
templateId,
createArg,
choiceId,
choiceArg,
) -> (createArgCids | choiceArgCids)
)
}

@throws[Error.Preprocessing.Error]
Expand All @@ -91,22 +81,14 @@ private[lf] final class CommandPreprocessor(compiledPackages: CompiledPackages)
contractKey: Value[Nothing],
): speedy.Command.LookupByKey = {
val ckTtype = handleLookup(interface.lookupTemplateKey(templateId)).typ
val (key, keyCids) = valueTranslator.unsafeTranslateValue(ckTtype, contractKey)
keyCids.foreach { coid =>
// The type checking of contractKey done by unsafeTranslateValue should ensure
// keyCids is empty
throw Error.Preprocessing.Internal(
NameOf.qualifiedNameOfCurrentFunc,
s"Unexpected contract IDs in contract key of $templateId: $coid",
)
}
val key = valueTranslator.unsafeTranslateValue(ckTtype, contractKey)
speedy.Command.LookupByKey(templateId, key)
}

// returns the speedy translation of an LF command together with all the contract IDs contains inside.
private[preprocessing] def unsafePreprocessCommand(
cmd: command.Command
): (speedy.Command, Set[Value.ContractId]) = {
): speedy.Command = {
cmd match {
case command.CreateCommand(templateId, argument) =>
unsafePreprocessCreate(templateId, argument)
Expand All @@ -127,41 +109,36 @@ private[lf] final class CommandPreprocessor(compiledPackages: CompiledPackages)
choiceArgument,
)
case command.FetchCommand(templateId, coid) =>
(speedy.Command.Fetch(templateId, SValue.SContractId(coid)), Set(coid))
speedy.Command.Fetch(templateId, SValue.SContractId(coid))
case command.FetchByKeyCommand(templateId, key) =>
val ckTtype = handleLookup(interface.lookupTemplateKey(templateId)).typ
val (sKey, cids) = valueTranslator.unsafeTranslateValue(ckTtype, key)
assert(cids.isEmpty)
(speedy.Command.FetchByKey(templateId, sKey), Set.empty)
val sKey = valueTranslator.unsafeTranslateValue(ckTtype, key)
speedy.Command.FetchByKey(templateId, sKey)
case command.LookupByKeyCommand(templateId, key) =>
val ckTtype = handleLookup(interface.lookupTemplateKey(templateId)).typ
val (sKey, cids) = valueTranslator.unsafeTranslateValue(ckTtype, key)
assert(cids.isEmpty)
(speedy.Command.LookupByKey(templateId, sKey), Set.empty)
val sKey = valueTranslator.unsafeTranslateValue(ckTtype, key)
speedy.Command.LookupByKey(templateId, sKey)
}
}

@throws[Error.Preprocessing.Error]
def unsafePreprocessCommands(
cmds: ImmArray[command.ApiCommand]
): (ImmArray[speedy.Command], Set[Value.ContractId]) = {
def unsafePreprocessCommands(cmds: ImmArray[command.ApiCommand]): ImmArray[speedy.Command] = {

@tailrec
def go(
toProcess: FrontStack[command.ApiCommand],
processed: BackStack[speedy.Command],
acc: Set[Value.ContractId],
): (ImmArray[speedy.Command], Set[Value.ContractId]) = {
): ImmArray[speedy.Command] = {
toProcess match {
case FrontStackCons(cmd, rest) =>
val (speedyCmd, newCids) = unsafePreprocessCommand(cmd)
go(rest, processed :+ speedyCmd, acc | newCids)
val speedyCmd = unsafePreprocessCommand(cmd)
go(rest, processed :+ speedyCmd)
case FrontStack() =>
(processed.toImmArray, acc)
processed.toImmArray
}
}

go(FrontStack(cmds), BackStack.empty, Set.empty)
go(FrontStack(cmds), BackStack.empty)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,11 @@ private[engine] final class Preprocessor(compiledPackages: MutableCompiledPackag
def translateValue(ty0: Ast.Type, v0: Value[Value.ContractId]): Result[SValue] =
safelyRun(getDependencies(List(ty0), List.empty)) {
unsafeTranslateValue(ty0, v0)
}.map(_._1)
}

private[engine] def preprocessCommand(
cmd: command.Command
): Result[(speedy.Command, Set[Value.ContractId])] =
): Result[speedy.Command] =
safelyRun(getDependencies(List.empty, List(cmd.templateId))) {
unsafePreprocessCommand(cmd)
}
Expand All @@ -141,14 +141,14 @@ private[engine] final class Preprocessor(compiledPackages: MutableCompiledPackag
*/
def preprocessCommands(
cmds: data.ImmArray[command.ApiCommand]
): Result[(ImmArray[speedy.Command], Set[Value.ContractId])] =
): Result[ImmArray[speedy.Command]] =
safelyRun(getDependencies(List.empty, cmds.map(_.templateId).toList)) {
unsafePreprocessCommands(cmds)
}

def translateTransactionRoots[Cid <: Value.ContractId](
tx: GenTransaction[NodeId, Cid]
): Result[(ImmArray[speedy.Command], Set[Value.ContractId])] =
): Result[ImmArray[speedy.Command]] =
safelyRun(
getDependencies(List.empty, tx.rootNodes.toList.map(_.templateId))
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,13 @@ package preprocessing
import com.daml.lf.data.{BackStack, ImmArray}
import com.daml.lf.transaction.{GenTransaction, Node, NodeId}
import com.daml.lf.value.Value
import com.daml.lf.value.Value.ContractId

private[preprocessing] final class TransactionPreprocessor(
compiledPackages: MutableCompiledPackages
) {

val commandPreprocessor = new CommandPreprocessor(compiledPackages)

// Accumulator used by unsafeTranslateTransactionRoots method.
private[this] case class Acc(
globalCids: Set[ContractId],
localCids: Set[ContractId],
commands: BackStack[speedy.Command],
) {
def update(
newInputCids: Iterable[ContractId],
newLocalCids: Iterable[ContractId],
cmd: speedy.Command,
) = Acc(
globalCids ++ newInputCids.filterNot(localCids),
localCids ++ newLocalCids,
commands :+ cmd,
)
}

private[this] def invalidRootNode(nodeId: NodeId, message: String) =
throw Error.Preprocessing.RootNode(nodeId, message)

Expand Down Expand Up @@ -85,18 +67,17 @@ private[preprocessing] final class TransactionPreprocessor(
@throws[Error.Preprocessing.Error]
def unsafeTranslateTransactionRoots[Cid <: Value.ContractId](
tx: GenTransaction[NodeId, Cid]
): (ImmArray[speedy.Command], Set[ContractId]) = {
): ImmArray[speedy.Command] = {

val result = tx.roots.foldLeft(Acc(Set.empty, Set.empty, BackStack.empty)) { (acc, id) =>
val result = tx.roots.foldLeft(BackStack.empty[speedy.Command]) { (acc, id) =>
tx.nodes.get(id) match {
case Some(node: Node.GenActionNode[_, Cid]) =>
node match {
case create: Node.NodeCreate[Cid] =>
val (cmd, newCids) =
commandPreprocessor.unsafePreprocessCreate(create.templateId, create.arg)
acc.update(newCids, List(create.coid), cmd)
val cmd = commandPreprocessor.unsafePreprocessCreate(create.templateId, create.arg)
acc :+ cmd
case exe: Node.NodeExercises[_, Cid] =>
val (cmd, newCids) = exe.key match {
val cmd = exe.key match {
case Some(key) if exe.byKey =>
commandPreprocessor.unsafePreprocessExerciseByKey(
exe.templateId,
Expand All @@ -112,8 +93,7 @@ private[preprocessing] final class TransactionPreprocessor(
exe.chosenValue,
)
}
val newLocalCids = GenTransaction(tx.nodes, ImmArray(id)).localContracts.keys
acc.update(newCids, newLocalCids, cmd)
acc :+ cmd
case _: Node.NodeFetch[_] =>
invalidRootNode(id, s"Transaction contains a fetch root node $id")
case _: Node.NodeLookupByKey[_] =>
Expand All @@ -126,16 +106,7 @@ private[preprocessing] final class TransactionPreprocessor(
}
}

// The following check ensures that `localCids ∩ globalCids = ∅`.
// It is probably not 100% necessary, as the reinterpretation should catch the cases where it is not true.
// We still prefer to perform the check here as:
// - it is cheap,
// - it catches obviously buggy transaction,
// - it is easier to reason about "soundness" of preprocessing under the disjointness assumption.
if (result.localCids exists result.globalCids)
throw Error.Preprocessing.ContractIdFreshness(result.localCids, result.globalCids)

result.commands.toImmArray -> result.globalCids
result.toImmArray
}

}
Loading

0 comments on commit 9f23bf3

Please sign in to comment.