From b967ef3e6d3e4ec2f8b1858e9c9018da2d9259da Mon Sep 17 00:00:00 2001
From: Matthias Schmalz <36510334+matthiasS-da@users.noreply.github.com>
Date: Tue, 21 Mar 2023 17:01:20 +0100
Subject: [PATCH] Revised ScalaDocs of DAML engine to make security relevant
assumptions explicit (#16555)
CHANGE_LOG_BEGIN
CHANGE_LOG_END
---
.../digitalasset/daml/lf/engine/Engine.scala | 51 +++++++++----------
.../digitalasset/daml/lf/engine/Result.scala | 32 +++++++++++-
2 files changed, 54 insertions(+), 29 deletions(-)
diff --git a/daml-lf/engine/src/main/scala/com/digitalasset/daml/lf/engine/Engine.scala b/daml-lf/engine/src/main/scala/com/digitalasset/daml/lf/engine/Engine.scala
index b3abac9f01..11e3e5666f 100644
--- a/daml-lf/engine/src/main/scala/com/digitalasset/daml/lf/engine/Engine.scala
+++ b/daml-lf/engine/src/main/scala/com/digitalasset/daml/lf/engine/Engine.scala
@@ -75,31 +75,26 @@ class Engine(val config: EngineConfig = Engine.StableConfig) {
def info = new EngineInfo(config)
- /** Executes commands `cmds` under the authority of `submitters`, with additional readers `readAs`,
- * and returns one of the following:
+ /** Interprets a sequence of commands `cmds` to the corresponding `SubmittedTransaction` and `Tx.Metadata`.
+ * Requests data required during the interpretation (such as the contract or package corresponding to a given id)
+ * through the `Result` subclasses.
+ *
+ * The resulting transaction (if any) meets the following properties:
*
- * - `ResultDone(tx)` if `cmds` could be successfully executed, where `tx` is the resulting transaction.
- * The transaction `tx` conforms to the Daml model consisting of the packages that have been supplied via
- * `ResultNeedPackage.resume` to this [[Engine]].
- * The transaction `tx` is internally consistent.
- *
- * - `ResultNeedContract(contractId, resume)` if the contract referenced by `contractId` is needed to execute
- * `cmds`.
- *
- * - `ResultNeedPackage(packageId, resume)` if the package referenced by `packageId` is needed to execute `cmds`.
- *
- * - `ResultError` if the execution of `cmds` fails.
- * The execution may fail due to an error during Daml evaluation (e.g. execution of "abort") or
- * because the caller has not provided a required contract instance or package.
- *
+ * - The transaction is well-typed and conforms to the DAML model described by the packages supplied via `ResultNeedPackage`.
+ * In particular, each contract created by the transaction meets the ensures clauses of the underlying template.
+ * - The transaction paired with `submitters` is well-authorized according to the ledger model.
+ * - The transaction is annotated with the packages used during interpretation.
*
*
- * [[transactionSeed]] is the master hash used to derive node and contractId discriminator.
- * If left undefined, no discriminator will be generated.
- *
- * This method does perform authorization checks
- *
- * The resulting transaction is annotated with packages required to validate it.
+ * @param submitters the parties authorizing the root actions (both read and write) of the resulting transaction
+ * ("committers" according to the ledger model)
+ * @param readAs the parties authorizing the root actions (only read, but no write) of the resulting transaction
+ * @param cmds the commands to be interpreted
+ * @param disclosures contracts to be used as input contracts of the transaction;
+ * contract data may come from an untrusted source and will therefore be validated during interpretation.
+ * @param participantId a unique identifier (of the underlying participant) used to derive node and contractId discriminators
+ * @param submissionSeed the master hash used to derive node and contractId discriminators
*/
def submit(
submitters: Set[Party],
@@ -130,19 +125,21 @@ class Engine(val config: EngineConfig = Engine.StableConfig) {
} yield tx -> meta.copy(submissionSeed = Some(submissionSeed))
}
- /** Behaves like `submit`, but it takes a single command argument.
+ /** Behaves like `submit`, but it takes a single `ReplayCommand` (instead of `ApiCommands`) as input.
* It can be used to reinterpret partially an already interpreted transaction.
*
* If the command would fail with an unhandled exception, we return a transaction containing a
* single rollback node. (This is achieving by compiling with `unsafeCompileForReinterpretation`
* which wraps the command with a catch-everything exception handler.)
*
- * [[nodeSeed]] is the seed of the Create and Exercise node as generated during submission.
- * If undefined the contract IDs are derive using V0 scheme.
- * The value of [[nodeSeed]] does not matter for other kind of nodes.
- *
* The reinterpretation does not recompute the package dependencies, so the field `usedPackages` in the
* `Tx.MetaData` component of the output is always set to `empty`.
+ *
+ * @param nodeSeed the seed of the root node as generated during submission.
+ * If undefined the contract IDs are derive using V0 scheme.
+ * The value does not matter for other kind of nodes.
+ * @param submissionTime the submission time used to compute contract IDs
+ * @param ledgerEffectiveTime the ledger effective time used as a result of `getTime` during reinterpretation
*/
def reinterpret(
submitters: Set[Party],
diff --git a/daml-lf/engine/src/main/scala/com/digitalasset/daml/lf/engine/Result.scala b/daml-lf/engine/src/main/scala/com/digitalasset/daml/lf/engine/Result.scala
index c14b62254b..55fb1ae39b 100644
--- a/daml-lf/engine/src/main/scala/com/digitalasset/daml/lf/engine/Result.scala
+++ b/daml-lf/engine/src/main/scala/com/digitalasset/daml/lf/engine/Result.scala
@@ -69,11 +69,15 @@ sealed trait Result[+A] extends Product with Serializable {
final case class ResultInterruption[A](continue: () => Result[A]) extends Result[A]
+/** Indicates that the command (re)interpretation was successful.
+ */
final case class ResultDone[A](result: A) extends Result[A]
object ResultDone {
val Unit: ResultDone[Unit] = new ResultDone(())
}
+/** Indicates that the command (re)interpretation has failed.
+ */
final case class ResultError(err: Error) extends Result[Nothing]
object ResultError {
def apply(packageError: Error.Package.Error): ResultError =
@@ -93,8 +97,12 @@ object ResultError {
* To resume the computation, the caller must invoke `resume` with the following argument:
*
* - `Some(contractInstance)`, if the caller can dereference `acoid` to `contractInstance`
- * - `None`, if the caller is unable to dereference `acoid`
+ *
- `None`, if the caller is unable to dereference `acoid`
*
+ *
+ * The caller of `resume` has to ensure that the contract instance passed to `resume` is a contract instance that
+ * has previously been associated with `acoid` by the engine.
+ * The engine does not validate the given contract instance.
*/
final case class ResultNeedContract[A](
acoid: ContractId,
@@ -105,17 +113,37 @@ final case class ResultNeedContract[A](
* To resume the computation, the caller must invoke `resume` with the following argument:
*
* - `Some(package)`, if the caller can dereference `packageId` to `package`
- * - `None`, if the caller is unable to dereference `packageId`
+ *
- `None`, if the caller is unable to dereference `packageId`
*
+ *
+ * It depends on the engine configuration whether the engine will validate the package provided to `resume`.
+ * If validation is switched off, it is the callers responsibility to provide a valid package corresponding to `packageId`.
*/
final case class ResultNeedPackage[A](packageId: PackageId, resume: Option[Package] => Result[A])
extends Result[A]
+/** Intermediate result indicating that the contract id corresponding to a key is required to complete the computation.
+ * To resume the computation, the caller must invoke `resume` with the following argument:
+ *
+ * - `Some(contractId)`, if `key` is currently assigned to `contractId`
+ * - `None`, if `key` is unassigned
+ *
+ *
+ * The caller of `resume` has to ensure that any contract id passed to `resume` has previously been associated with
+ * a contract with `key` as a key.
+ * Other than that, the caller does not need to validate the data passed to `resume`. In particular, it may pass
+ * the id of an archived contract to `resume`.
+ * It may also provide `None` to `resume` when the `key` is actually assigned.
+ */
final case class ResultNeedKey[A](
key: GlobalKeyWithMaintainers,
resume: Option[ContractId] => Result[A],
) extends Result[A]
+/** TODO: https://github.com/digital-asset/daml/issues/15882
+ * add ScalaDoc explaining the impact of the answers and the responsibilities of the caller.
+ * (Similarly as for the other subclasses of Result.)
+ */
final case class ResultNeedAuthority[A](
holding: Set[Party],
requesting: Set[Party],