Revised ScalaDocs of DAML engine to make security relevant assumptions explicit (#16555)

CHANGE_LOG_BEGIN
CHANGE_LOG_END
This commit is contained in:
Matthias Schmalz 2023-03-21 17:01:20 +01:00 committed by GitHub
parent c423e8d151
commit b967ef3e6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 54 additions and 29 deletions

View File

@ -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:
* <ul>
* <li> `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.
* </li>
* <li> `ResultNeedContract(contractId, resume)` if the contract referenced by `contractId` is needed to execute
* `cmds`.
* </li>
* <li> `ResultNeedPackage(packageId, resume)` if the package referenced by `packageId` is needed to execute `cmds`.
* </li>
* <li> `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.
* </li>
* <li>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.</li>
* <li>The transaction paired with `submitters` is well-authorized according to the ledger model.</li>
* <li>The transaction is annotated with the packages used during interpretation.</li>
* </ul>
*
* [[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],

View File

@ -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:
* <ul>
* <li>`Some(contractInstance)`, if the caller can dereference `acoid` to `contractInstance`</li>
* <li>`None`, if the caller is unable to dereference `acoid`
* <li>`None`, if the caller is unable to dereference `acoid`</li>
* </ul>
*
* 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:
* <ul>
* <li>`Some(package)`, if the caller can dereference `packageId` to `package`</li>
* <li>`None`, if the caller is unable to dereference `packageId`
* <li>`None`, if the caller is unable to dereference `packageId`</li>
* </ul>
*
* 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:
* <ul>
* <li>`Some(contractId)`, if `key` is currently assigned to `contractId`</li>
* <li>`None`, if `key` is unassigned</li>
* </ul>
*
* 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],