mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-19 08:48:21 +03:00
DAML-LF: remove submitter is in maintainer check (#5611)
CHANGELOG_BEGIN CHANGELOG_END
This commit is contained in:
parent
68b7dcfa1a
commit
15354c3256
@ -151,7 +151,6 @@ class Context(val contextId: Context.ContextId) {
|
||||
} yield
|
||||
Speedy.Machine
|
||||
.build(
|
||||
checkSubmitterInMaintainers = false,
|
||||
sexpr = defn,
|
||||
compiledPackages = PureCompiledPackages(allPackages, defns),
|
||||
submissionTime,
|
||||
|
@ -150,9 +150,6 @@ final class Conversions(
|
||||
sys.error(
|
||||
s"Got unexpected DamlEWronglyTypedContract error in scenario service: $wtc. Note that in the scenario service this error should never surface since contract fetches are all type checked.",
|
||||
)
|
||||
|
||||
case e @ SError.DamlESubmitterNotInMaintainers(_, _, _) =>
|
||||
sys.error(s"Unexpected error $e")
|
||||
}
|
||||
builder.build
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ final class ConcurrentCompiledPackages extends MutableCompiledPackages {
|
||||
}
|
||||
}
|
||||
|
||||
ResultDone(())
|
||||
ResultDone.Unit
|
||||
}
|
||||
|
||||
def clear(): Unit = this.synchronized[Unit] {
|
||||
|
@ -8,7 +8,7 @@ import com.daml.lf.command._
|
||||
import com.daml.lf.data._
|
||||
import com.daml.lf.data.Ref.{PackageId, ParticipantId, Party}
|
||||
import com.daml.lf.language.Ast._
|
||||
import com.daml.lf.speedy.{Compiler, InitialSeeding, Pretty, Command => SpeedyCommand}
|
||||
import com.daml.lf.speedy.{InitialSeeding, Pretty}
|
||||
import com.daml.lf.speedy.Speedy.Machine
|
||||
import com.daml.lf.speedy.SResult._
|
||||
import com.daml.lf.transaction.{Transaction => Tx}
|
||||
@ -85,33 +85,28 @@ final class Engine {
|
||||
preprocessor
|
||||
.preprocessCommands(cmds.commands)
|
||||
.flatMap { processedCmds =>
|
||||
ShouldCheckSubmitterInMaintainers(compiledPackages, cmds).flatMap {
|
||||
checkSubmitterInMaintainers =>
|
||||
interpretCommands(
|
||||
validating = false,
|
||||
checkSubmitterInMaintainers = checkSubmitterInMaintainers,
|
||||
submitters = Set(cmds.submitter),
|
||||
commands = processedCmds,
|
||||
ledgerTime = cmds.ledgerEffectiveTime,
|
||||
submissionTime = submissionTime,
|
||||
seeding = Engine.initialSeeding(submissionSeed, participantId, submissionTime),
|
||||
) map {
|
||||
case (tx, meta) =>
|
||||
// Annotate the transaction with the package dependencies. Since
|
||||
// all commands are actions on a contract template, with a fully typed
|
||||
// argument, we only need to consider the templates mentioned in the command
|
||||
// to compute the full dependencies.
|
||||
val deps = processedCmds.foldLeft(Set.empty[PackageId]) { (pkgIds, cmd) =>
|
||||
val pkgId = cmd.templateId.packageId
|
||||
val transitiveDeps =
|
||||
compiledPackages
|
||||
.getPackageDependencies(pkgId)
|
||||
.getOrElse(
|
||||
sys.error(s"INTERNAL ERROR: Missing dependencies of package $pkgId"))
|
||||
(pkgIds + pkgId) union transitiveDeps
|
||||
}
|
||||
tx -> meta.copy(submissionSeed = submissionSeed, usedPackages = deps)
|
||||
interpretCommands(
|
||||
validating = false,
|
||||
submitters = Set(cmds.submitter),
|
||||
commands = processedCmds,
|
||||
ledgerTime = cmds.ledgerEffectiveTime,
|
||||
submissionTime = submissionTime,
|
||||
seeding = Engine.initialSeeding(submissionSeed, participantId, submissionTime),
|
||||
) map {
|
||||
case (tx, meta) =>
|
||||
// Annotate the transaction with the package dependencies. Since
|
||||
// all commands are actions on a contract template, with a fully typed
|
||||
// argument, we only need to consider the templates mentioned in the command
|
||||
// to compute the full dependencies.
|
||||
val deps = processedCmds.foldLeft(Set.empty[PackageId]) { (pkgIds, cmd) =>
|
||||
val pkgId = cmd.templateId.packageId
|
||||
val transitiveDeps =
|
||||
compiledPackages
|
||||
.getPackageDependencies(pkgId)
|
||||
.getOrElse(sys.error(s"INTERNAL ERROR: Missing dependencies of package $pkgId"))
|
||||
(pkgIds + pkgId) union transitiveDeps
|
||||
}
|
||||
tx -> meta.copy(submissionSeed = submissionSeed, usedPackages = deps)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -137,13 +132,9 @@ final class Engine {
|
||||
): Result[(Tx.Transaction, Tx.Metadata)] =
|
||||
for {
|
||||
command <- preprocessor.translateNode(node)
|
||||
checkSubmitterInMaintainers <- ShouldCheckSubmitterInMaintainers(
|
||||
compiledPackages,
|
||||
ImmArray(command.templateId))
|
||||
// reinterpret is never used for submission, only for validation.
|
||||
result <- interpretCommands(
|
||||
validating = true,
|
||||
checkSubmitterInMaintainers = checkSubmitterInMaintainers,
|
||||
submitters = submitters,
|
||||
commands = ImmArray(command),
|
||||
ledgerTime = ledgerEffectiveTime,
|
||||
@ -196,18 +187,14 @@ final class Engine {
|
||||
|
||||
_ <- if (submittersOpt.exists(_.size != 1))
|
||||
ResultError(ValidationError(s"Transaction's roots do not have exactly one authorizer: $tx"))
|
||||
else ResultDone(())
|
||||
else ResultDone.Unit
|
||||
|
||||
// For empty transactions, use an empty set of submitters
|
||||
submitters = submittersOpt.getOrElse(Set.empty)
|
||||
|
||||
commands <- preprocessor.translateTransactionRoots(tx)
|
||||
checkSubmitterInMaintainers <- ShouldCheckSubmitterInMaintainers(
|
||||
compiledPackages,
|
||||
commands.map(_._2.templateId))
|
||||
result <- interpretCommands(
|
||||
validating = true,
|
||||
checkSubmitterInMaintainers = checkSubmitterInMaintainers,
|
||||
submitters = submitters,
|
||||
commands = commands.map(_._2),
|
||||
ledgerTime = ledgerEffectiveTime,
|
||||
@ -216,7 +203,7 @@ final class Engine {
|
||||
)
|
||||
(rtx, _) = result
|
||||
validationResult <- if (tx isReplayedBy rtx) {
|
||||
ResultDone(())
|
||||
ResultDone.Unit
|
||||
} else {
|
||||
ResultError(
|
||||
ValidationError(
|
||||
@ -225,34 +212,64 @@ final class Engine {
|
||||
} yield validationResult
|
||||
}
|
||||
|
||||
private def loadPackages(pkgIds: List[PackageId]): Result[Unit] =
|
||||
pkgIds.dropWhile(compiledPackages.packages.isDefinedAt) match {
|
||||
case pkgId :: rest =>
|
||||
ResultNeedPackage(pkgId, {
|
||||
case Some(pkg) =>
|
||||
compiledPackages.addPackage(pkgId, pkg).flatMap(_ => loadPackages(rest))
|
||||
case None =>
|
||||
ResultError(Error(s"package $pkgId not found"))
|
||||
})
|
||||
case Nil =>
|
||||
ResultDone.Unit
|
||||
}
|
||||
|
||||
@inline
|
||||
private[lf] def runSafely[X](handleMissingDependencies: => Result[Unit])(
|
||||
run: => Result[X]): Result[X] = {
|
||||
def start: Result[X] =
|
||||
try {
|
||||
run
|
||||
} catch {
|
||||
case speedy.Compiler.PackageNotFound(_) =>
|
||||
handleMissingDependencies.flatMap(_ => start)
|
||||
case speedy.Compiler.CompilationError(error) =>
|
||||
ResultError(Error(s"CompilationError: $error"))
|
||||
}
|
||||
start
|
||||
}
|
||||
|
||||
/** Interprets the given commands under the authority of @submitters
|
||||
*
|
||||
* Submitters are a set, in order to support interpreting subtransactions
|
||||
* (a subtransaction can be authorized by multiple parties).
|
||||
*
|
||||
* [[seeding]] is seeding used to derive node seed and contractId discriminator.
|
||||
*
|
||||
*/
|
||||
private[engine] def interpretCommands(
|
||||
validating: Boolean,
|
||||
/* See documentation for `Speedy.Machine` for the meaning of this field */
|
||||
checkSubmitterInMaintainers: Boolean,
|
||||
submitters: Set[Party],
|
||||
commands: ImmArray[SpeedyCommand],
|
||||
commands: ImmArray[speedy.Command],
|
||||
ledgerTime: Time.Timestamp,
|
||||
submissionTime: Time.Timestamp,
|
||||
seeding: speedy.InitialSeeding,
|
||||
): Result[(Tx.Transaction, Tx.Metadata)] = {
|
||||
val machine = Machine
|
||||
.build(
|
||||
checkSubmitterInMaintainers = checkSubmitterInMaintainers,
|
||||
sexpr = Compiler(compiledPackages.packages).unsafeCompile(commands),
|
||||
compiledPackages = compiledPackages,
|
||||
submissionTime = submissionTime,
|
||||
seeds = seeding,
|
||||
)
|
||||
.copy(validating = validating, committers = submitters)
|
||||
interpretLoop(machine, ledgerTime)
|
||||
}
|
||||
): Result[(Tx.Transaction, Tx.Metadata)] =
|
||||
runSafely(
|
||||
loadPackages(commands.foldLeft(Set.empty[PackageId])(_ + _.templateId.packageId).toList)
|
||||
) {
|
||||
val machine = Machine
|
||||
.build(
|
||||
sexpr = speedy.Compiler(compiledPackages.packages).unsafeCompile(commands),
|
||||
compiledPackages = compiledPackages,
|
||||
submissionTime = submissionTime,
|
||||
seeds = seeding,
|
||||
)
|
||||
.copy(validating = validating, committers = submitters)
|
||||
interpretLoop(machine, ledgerTime)
|
||||
}
|
||||
|
||||
// TODO SC remove 'return', notwithstanding a love of unhandled exceptions
|
||||
@SuppressWarnings(Array("org.wartremover.warts.Any", "org.wartremover.warts.Return"))
|
||||
|
@ -58,6 +58,9 @@ sealed trait Result[+A] extends Product with Serializable {
|
||||
}
|
||||
|
||||
final case class ResultDone[A](result: A) extends Result[A]
|
||||
object ResultDone {
|
||||
val Unit: ResultDone[Unit] = new ResultDone(())
|
||||
}
|
||||
final case class ResultError(err: Error) extends Result[Nothing]
|
||||
|
||||
/**
|
||||
@ -204,9 +207,10 @@ object Result {
|
||||
}
|
||||
|
||||
def assert(assertion: Boolean)(err: Error): Result[Unit] =
|
||||
if (assertion) {
|
||||
ResultDone(())
|
||||
} else ResultError(err)
|
||||
if (assertion)
|
||||
ResultDone.Unit
|
||||
else
|
||||
ResultError(err)
|
||||
|
||||
implicit val resultInstance: Monad[Result] = new Monad[Result] {
|
||||
override def point[A](a: => A): Result[A] = ResultDone(a)
|
||||
|
@ -1,94 +0,0 @@
|
||||
// Copyright (c) 2020 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.lf.engine
|
||||
|
||||
import com.daml.lf.transaction.VersionTimeline
|
||||
import com.daml.lf.data.Ref._
|
||||
import com.daml.lf.command._
|
||||
import com.daml.lf.data.{ImmArray, ImmArrayCons}
|
||||
import com.daml.lf.language.Ast.Package
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
/** After #1866, DAML-LF execution is parametrized by whether we should check
|
||||
* if the "transaction submitter" is in the key maintainers for key lookup
|
||||
* and fetch.
|
||||
*
|
||||
* For scenarios we just use the version of the module where the scenario
|
||||
* definition comes from. For Ledger API commands, we use the latest version
|
||||
* amongst the versions of the modules from where the templates of the commands
|
||||
* come from. This file implements the latter scenario.
|
||||
*
|
||||
* We only return [[ResultError]], [[ResultDone]], and [[ResultNeedPackage]].
|
||||
*/
|
||||
object ShouldCheckSubmitterInMaintainers {
|
||||
|
||||
private def templateShouldCheckSubmitterInMaintainers(
|
||||
compiledPackages: Option[MutableCompiledPackages],
|
||||
templateId: Identifier): Result[Boolean] = {
|
||||
def withPkg(pkg: Package): Result[Boolean] =
|
||||
pkg.modules.get(templateId.qualifiedName.module) match {
|
||||
case None => ResultError(Error(s"Could not find module ${templateId.qualifiedName.module}"))
|
||||
case Some(module) =>
|
||||
ResultDone(VersionTimeline.checkSubmitterInMaintainers(module.languageVersion))
|
||||
}
|
||||
|
||||
compiledPackages match {
|
||||
case None => Result.needPackage(templateId.packageId, withPkg)
|
||||
case Some(pkgs) => Result.needPackage(pkgs, templateId.packageId, withPkg)
|
||||
}
|
||||
}
|
||||
|
||||
private def apply(
|
||||
compiledPackages: Option[MutableCompiledPackages],
|
||||
templates0: ImmArray[Identifier]): Result[Boolean] = {
|
||||
// not using [[Result#sequence]] on purpose, see
|
||||
// <https://github.com/digital-asset/daml/blob/995ee82fd0655231d7034d0a66c9fe2c6a419536/daml-lf/engine/src/main/scala/com/digitalasset/daml/lf/engine/CommandPreprocessor.scala#L463>
|
||||
@tailrec
|
||||
def go(
|
||||
checkSubmitterInMaintainers: Boolean,
|
||||
templates: ImmArray[Identifier]): Result[Boolean] = {
|
||||
if (checkSubmitterInMaintainers) {
|
||||
ResultDone(true)
|
||||
} else {
|
||||
templates match {
|
||||
case ImmArray() => ResultDone(checkSubmitterInMaintainers)
|
||||
case ImmArrayCons(template, rest) =>
|
||||
templateShouldCheckSubmitterInMaintainers(compiledPackages, template) match {
|
||||
case ResultError(err) => ResultError(err)
|
||||
case ResultDone(b) => go(checkSubmitterInMaintainers || b, rest)
|
||||
case ResultNeedPackage(pkgId, resume) =>
|
||||
ResultNeedPackage(pkgId, { pkg =>
|
||||
resume(pkg).flatMap { b =>
|
||||
goResume(checkSubmitterInMaintainers || b, rest)
|
||||
}
|
||||
})
|
||||
case result => sys.error(s"Unexpected result: $result")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
def goResume(
|
||||
checkSubmitterInMaintainers: Boolean,
|
||||
templates: ImmArray[Identifier]): Result[Boolean] =
|
||||
go(checkSubmitterInMaintainers, templates)
|
||||
// for no commands the version is irrelevant -- we just return
|
||||
// the earliest one.
|
||||
go(false, templates0)
|
||||
}
|
||||
|
||||
def apply(commands: Commands): Result[Boolean] =
|
||||
apply(None, commands.commands.map(_.templateId))
|
||||
|
||||
def apply(compiledPackages: MutableCompiledPackages, commands: Commands): Result[Boolean] =
|
||||
apply(Some(compiledPackages), commands.commands.map(_.templateId))
|
||||
|
||||
def apply(templates: ImmArray[Identifier]): Result[Boolean] =
|
||||
apply(None, templates)
|
||||
|
||||
def apply(
|
||||
compiledPackages: MutableCompiledPackages,
|
||||
templates: ImmArray[Identifier]): Result[Boolean] =
|
||||
apply(Some(compiledPackages), templates)
|
||||
}
|
@ -492,7 +492,6 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
|
||||
engine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
checkSubmitterInMaintainers = true,
|
||||
submitters = Set(party),
|
||||
commands = r,
|
||||
ledgerTime = let,
|
||||
@ -595,7 +594,6 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
|
||||
engine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
checkSubmitterInMaintainers = true,
|
||||
submitters = Set(alice),
|
||||
commands = r,
|
||||
ledgerTime = let,
|
||||
@ -678,7 +676,6 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
|
||||
engine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
checkSubmitterInMaintainers = true,
|
||||
submitters = Set(party),
|
||||
commands = r,
|
||||
ledgerTime = let,
|
||||
@ -909,7 +906,6 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
|
||||
val Right((rtx, _)) = engine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
checkSubmitterInMaintainers = true,
|
||||
submitters = Set(bob),
|
||||
commands = cmds,
|
||||
ledgerTime = let,
|
||||
@ -989,7 +985,6 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
|
||||
engine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
checkSubmitterInMaintainers = true,
|
||||
submitters = Set(bob),
|
||||
commands = cmds,
|
||||
ledgerTime = let,
|
||||
@ -1126,7 +1121,6 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
|
||||
engine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
checkSubmitterInMaintainers = true,
|
||||
submitters = Set(exerciseActor),
|
||||
commands = r,
|
||||
ledgerTime = let,
|
||||
@ -1384,7 +1378,14 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
|
||||
val cmd = speedy.Command.Fetch(BasicTests_WithKey, SValue.SContractId(fetchedCid))
|
||||
|
||||
val Right((tx, _)) = engine
|
||||
.interpretCommands(false, false, Set(alice), ImmArray(cmd), now, now, InitialSeeding.NoSeed)
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = Set(alice),
|
||||
commands = ImmArray(cmd),
|
||||
ledgerTime = now,
|
||||
submissionTime = now,
|
||||
seeding = InitialSeeding.NoSeed,
|
||||
)
|
||||
.consume(lookupContractMap.get, lookupPackage, lookupKey)
|
||||
|
||||
tx.nodes.values.headOption match {
|
||||
@ -1438,7 +1439,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
|
||||
.consume(lookupContractMap.get, lookupPackage, lookupKey)
|
||||
|
||||
val Right((tx, txMeta)) = engine
|
||||
.interpretCommands(false, false, Set(alice), cmds, now, now, InitialSeeding.NoSeed)
|
||||
.interpretCommands(false, Set(alice), cmds, now, now, InitialSeeding.NoSeed)
|
||||
.consume(lookupContractMap.get, lookupPackage, lookupKey)
|
||||
|
||||
tx.nodes
|
||||
|
@ -77,7 +77,7 @@ class PreprocessorSpec extends WordSpec with Matchers with TableDrivenPropertyCh
|
||||
)
|
||||
|
||||
val compiledPackage = ConcurrentCompiledPackages()
|
||||
assert(compiledPackage.addPackage(pkgId, pkg) == ResultDone(()))
|
||||
assert(compiledPackage.addPackage(pkgId, pkg) == ResultDone.Unit)
|
||||
val preprocessor = new Preprocessor(compiledPackage)
|
||||
import preprocessor.translateValue
|
||||
|
||||
|
@ -70,11 +70,6 @@ object Pretty {
|
||||
text("Expected contract of type") & prettyTypeConName(expected) & text("but got") & prettyTypeConName(
|
||||
actual,
|
||||
)
|
||||
|
||||
case DamlESubmitterNotInMaintainers(templateId, submitter, maintainers) =>
|
||||
text("Expected the submitter") & prettyParty(submitter) &
|
||||
text("to be in maintainers") & intercalate(comma + space, maintainers.map(prettyParty)) &
|
||||
text("when looking up template of maintainer") & prettyTypeConName(templateId)
|
||||
}
|
||||
|
||||
// A minimal pretty-print of an update transaction node, without recursing into child nodes..
|
||||
|
@ -1116,7 +1116,6 @@ object SBuiltin {
|
||||
def execute(args: util.ArrayList[SValue], machine: Machine): Unit = {
|
||||
checkToken(args.get(1))
|
||||
val keyWithMaintainers = extractKeyWithMaintainers(args.get(0))
|
||||
checkLookupMaintainers(templateId, machine, keyWithMaintainers.maintainers)
|
||||
val gkey = GlobalKey(templateId, keyWithMaintainers.key.value)
|
||||
// check if we find it locally
|
||||
machine.ptx.keys.get(gkey) match {
|
||||
@ -1187,7 +1186,6 @@ object SBuiltin {
|
||||
def execute(args: util.ArrayList[SValue], machine: Machine): Unit = {
|
||||
checkToken(args.get(1))
|
||||
val keyWithMaintainers = extractKeyWithMaintainers(args.get(0))
|
||||
checkLookupMaintainers(templateId, machine, keyWithMaintainers.maintainers)
|
||||
val gkey = GlobalKey(templateId, keyWithMaintainers.key.value)
|
||||
// check if we find it locally
|
||||
machine.ptx.keys.get(gkey) match {
|
||||
@ -1626,36 +1624,6 @@ object SBuiltin {
|
||||
case v => crash(s"Expected optional key with maintainers, got: $v")
|
||||
}
|
||||
|
||||
private def checkLookupMaintainers(
|
||||
templateId: Identifier,
|
||||
machine: Machine,
|
||||
maintainers: Set[Party],
|
||||
): Unit = {
|
||||
// This check is dependent on whether we are submitting or validating the transaction.
|
||||
// See <https://github.com/digital-asset/daml/issues/1866#issuecomment-506315152>,
|
||||
// specifically "Consequently it suffices to implement this check
|
||||
// only for the submission. There is no intention to enforce "submitter
|
||||
// must be a maintainer" during validation; if we find in the future a
|
||||
// way to disclose key information or support interactive submission,
|
||||
// then we can lift this restriction without changing the validation
|
||||
// parts. In particular, this should not affect whether we have to ship
|
||||
// the submitter along with the transaction."
|
||||
if (!machine.validating) {
|
||||
val submitter = if (machine.committers.size != 1) {
|
||||
crash(
|
||||
s"expecting exactly one committer since we're not validating, but got ${machine.committers}",
|
||||
)
|
||||
} else {
|
||||
machine.committers.toSeq.head
|
||||
}
|
||||
if (machine.checkSubmitterInMaintainers) {
|
||||
if (!(maintainers.contains(submitter))) {
|
||||
throw DamlESubmitterNotInMaintainers(templateId, submitter, maintainers)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def rightOrArithmeticError[A](message: String, mb: Either[String, A]): A =
|
||||
mb.fold(_ => throw DamlEArithmeticError(s"$message"), identity)
|
||||
|
||||
|
@ -63,15 +63,6 @@ object SError {
|
||||
reason: String,
|
||||
) extends SErrorDamlException
|
||||
|
||||
/** The submitter was not in the key maintainers on lookup.
|
||||
* See <https://github.com/digital-asset/daml/issues/1866>.
|
||||
*/
|
||||
final case class DamlESubmitterNotInMaintainers(
|
||||
templateId: TypeConName,
|
||||
submitter: Party,
|
||||
maintainers: Set[Party],
|
||||
) extends SErrorDamlException
|
||||
|
||||
/** Errors from scenario interpretation. */
|
||||
sealed trait SErrorScenario extends SError
|
||||
|
||||
|
@ -40,17 +40,6 @@ object Speedy {
|
||||
var committers: Set[Party],
|
||||
/* Commit location, if a scenario commit is in progress. */
|
||||
var commitLocation: Option[Location],
|
||||
/* Whether we check if the submitter is in contract key maintainers
|
||||
* when looking up / fetching keys. This was introduced in #1866.
|
||||
* We derive this from the "submission version", which is the scenario
|
||||
* definition DAML-LF version for scenarios, and the command version for
|
||||
* a Ledger API submission.
|
||||
*
|
||||
* We store a specific flag rather than the DAML-LF version mostly here because
|
||||
* we want to avoid the risk of future implementors misusing the DAML-LF
|
||||
* version to influence the operational semantics of DAML-LF.
|
||||
*/
|
||||
var checkSubmitterInMaintainers: Boolean,
|
||||
/* Whether the current submission is validating the transaction, or interpreting
|
||||
* it. If this is false, the committers must be a singleton set.
|
||||
*/
|
||||
@ -254,7 +243,6 @@ object Speedy {
|
||||
private val damlTraceLog = LoggerFactory.getLogger("daml.tracelog")
|
||||
|
||||
private def initial(
|
||||
checkSubmitterInMaintainers: Boolean,
|
||||
compiledPackages: CompiledPackages,
|
||||
submissionTime: Time.Timestamp,
|
||||
initialSeeding: InitialSeeding,
|
||||
@ -269,7 +257,6 @@ object Speedy {
|
||||
commitLocation = None,
|
||||
traceLog = TraceLog(damlTraceLog, 100),
|
||||
compiledPackages = compiledPackages,
|
||||
checkSubmitterInMaintainers = checkSubmitterInMaintainers,
|
||||
validating = false,
|
||||
dependsOnTime = false,
|
||||
)
|
||||
@ -278,21 +265,19 @@ object Speedy {
|
||||
compiledPackages: CompiledPackages,
|
||||
submissionTime: Time.Timestamp,
|
||||
transactionSeed: Option[crypto.Hash],
|
||||
): Either[SError, (Boolean, Expr) => Machine] = {
|
||||
): Either[SError, Expr => Machine] = {
|
||||
val compiler = Compiler(compiledPackages.packages)
|
||||
Right({ (checkSubmitterInMaintainers: Boolean, expr: Expr) =>
|
||||
fromSExpr(
|
||||
SEApp(compiler.unsafeCompile(expr), Array(SEValue.Token)),
|
||||
checkSubmitterInMaintainers,
|
||||
compiledPackages,
|
||||
submissionTime,
|
||||
InitialSeeding(transactionSeed)
|
||||
)
|
||||
})
|
||||
Right(
|
||||
(expr: Expr) =>
|
||||
fromSExpr(
|
||||
SEApp(compiler.unsafeCompile(expr), Array(SEValue.Token)),
|
||||
compiledPackages,
|
||||
submissionTime,
|
||||
InitialSeeding(transactionSeed)
|
||||
))
|
||||
}
|
||||
|
||||
def build(
|
||||
checkSubmitterInMaintainers: Boolean,
|
||||
sexpr: SExpr,
|
||||
compiledPackages: CompiledPackages,
|
||||
submissionTime: Time.Timestamp,
|
||||
@ -300,7 +285,6 @@ object Speedy {
|
||||
): Machine =
|
||||
fromSExpr(
|
||||
SEApp(sexpr, Array(SEValue.Token)),
|
||||
checkSubmitterInMaintainers,
|
||||
compiledPackages,
|
||||
submissionTime,
|
||||
seeds,
|
||||
@ -309,7 +293,6 @@ object Speedy {
|
||||
// Used from repl.
|
||||
def fromExpr(
|
||||
expr: Expr,
|
||||
checkSubmitterInMaintainers: Boolean,
|
||||
compiledPackages: CompiledPackages,
|
||||
scenario: Boolean,
|
||||
submissionTime: Time.Timestamp,
|
||||
@ -324,7 +307,6 @@ object Speedy {
|
||||
|
||||
fromSExpr(
|
||||
sexpr,
|
||||
checkSubmitterInMaintainers,
|
||||
compiledPackages,
|
||||
submissionTime,
|
||||
InitialSeeding(transactionSeed),
|
||||
@ -336,13 +318,11 @@ object Speedy {
|
||||
// a token is not appropriate.
|
||||
def fromSExpr(
|
||||
sexpr: SExpr,
|
||||
checkSubmitterInMaintainers: Boolean,
|
||||
compiledPackages: CompiledPackages,
|
||||
submissionTime: Time.Timestamp,
|
||||
seeds: InitialSeeding,
|
||||
seeding: InitialSeeding,
|
||||
): Machine =
|
||||
initial(checkSubmitterInMaintainers, compiledPackages, submissionTime, seeds).copy(
|
||||
ctrl = CtrlExpr(sexpr))
|
||||
initial(compiledPackages, submissionTime, seeding).copy(ctrl = CtrlExpr(sexpr))
|
||||
}
|
||||
|
||||
/** Control specifies the thing that the machine should be reducing.
|
||||
|
@ -25,7 +25,6 @@ class InterpreterTest extends WordSpec with Matchers with TableDrivenPropertyChe
|
||||
private def runExpr(e: Expr): SValue = {
|
||||
val machine = Speedy.Machine.fromExpr(
|
||||
expr = e,
|
||||
checkSubmitterInMaintainers = true,
|
||||
compiledPackages = PureCompiledPackages(Map.empty).right.get,
|
||||
scenario = false,
|
||||
submissionTime = Time.Timestamp.now(),
|
||||
@ -142,7 +141,6 @@ class InterpreterTest extends WordSpec with Matchers with TableDrivenPropertyChe
|
||||
"compile" in {
|
||||
machine = Speedy.Machine.fromExpr(
|
||||
expr = list,
|
||||
checkSubmitterInMaintainers = true,
|
||||
compiledPackages = PureCompiledPackages(Map.empty).right.get,
|
||||
scenario = false,
|
||||
submissionTime = Time.Timestamp.now(),
|
||||
@ -244,7 +242,6 @@ class InterpreterTest extends WordSpec with Matchers with TableDrivenPropertyChe
|
||||
"succeeds" in {
|
||||
val machine = Speedy.Machine.fromExpr(
|
||||
expr = EVal(ref),
|
||||
checkSubmitterInMaintainers = true,
|
||||
compiledPackages = pkgs1,
|
||||
scenario = false,
|
||||
submissionTime = Time.Timestamp.now(),
|
||||
@ -272,7 +269,6 @@ class InterpreterTest extends WordSpec with Matchers with TableDrivenPropertyChe
|
||||
"crashes without definition" in {
|
||||
val machine = Speedy.Machine.fromExpr(
|
||||
expr = EVal(ref),
|
||||
checkSubmitterInMaintainers = true,
|
||||
compiledPackages = pkgs1,
|
||||
scenario = false,
|
||||
submissionTime = Time.Timestamp.now(),
|
||||
|
@ -1452,7 +1452,6 @@ object SBuiltinTest {
|
||||
private def eval(e: Expr): Either[SError, SValue] = {
|
||||
val machine = Speedy.Machine.fromExpr(
|
||||
expr = e,
|
||||
checkSubmitterInMaintainers = true,
|
||||
compiledPackages = compiledPackages,
|
||||
scenario = false,
|
||||
Time.Timestamp.now(),
|
||||
|
@ -253,7 +253,6 @@ object SpeedyTest {
|
||||
private def eval(e: Expr, packages: PureCompiledPackages): Either[SError, SValue] = {
|
||||
val machine = Speedy.Machine.fromExpr(
|
||||
expr = e,
|
||||
checkSubmitterInMaintainers = true,
|
||||
compiledPackages = packages,
|
||||
scenario = false,
|
||||
submissionTime = Time.Timestamp.now(),
|
||||
|
@ -73,15 +73,5 @@ object LanguageVersion {
|
||||
*/
|
||||
val unstable = v1_dev
|
||||
|
||||
/** See <https://github.com/digital-asset/daml/issues/1866>. To not break backwards
|
||||
* compatibility, we introduce a new DAML-LF version where this restriction is in
|
||||
* place, and then:
|
||||
* * When committing a scenario, we check that the scenario code is at least of that
|
||||
* version;
|
||||
* * When executing a Ledger API command, we check that the template underpinning
|
||||
* said command is at least of that version.
|
||||
*/
|
||||
val checkSubmitterInMaintainersVersion = v1_dev
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ import com.daml.lf.speedy.SExpr.LfDefRef
|
||||
import com.daml.lf.validation.Validation
|
||||
import com.daml.lf.testing.parser
|
||||
import com.daml.lf.language.LanguageVersion
|
||||
import com.daml.lf.transaction.VersionTimeline
|
||||
import java.io.{File, PrintWriter, StringWriter}
|
||||
import java.nio.file.{Path, Paths}
|
||||
import java.io.PrintStream
|
||||
@ -169,11 +168,9 @@ object Repl {
|
||||
private val build = Speedy.Machine
|
||||
.newBuilder(PureCompiledPackages(packages).right.get, Time.Timestamp.MinValue, nextSeed())
|
||||
.fold(err => sys.error(err.toString), identity)
|
||||
def run(submissionVersion: LanguageVersion, expr: Expr)
|
||||
: (Speedy.Machine, Either[(SError, Ledger.Ledger), (Double, Int, Ledger.Ledger)]) = {
|
||||
val mach = build(VersionTimeline.checkSubmitterInMaintainers(submissionVersion), expr)
|
||||
(mach, ScenarioRunner(mach).run())
|
||||
}
|
||||
def run(expr: Expr)
|
||||
: (Speedy.Machine, Either[(SError, Ledger.Ledger), (Double, Int, Ledger.Ledger)]) =
|
||||
(build(expr), ScenarioRunner(build(expr)).run())
|
||||
}
|
||||
|
||||
case class Command(help: String, action: (State, Seq[String]) => State)
|
||||
@ -402,13 +399,12 @@ object Repl {
|
||||
case None =>
|
||||
println("Error: definition '" + id + "' not found. Try :list.")
|
||||
usage
|
||||
case Some((lfVer, DValue(_, _, body, _))) =>
|
||||
case Some(DValue(_, _, body, _)) =>
|
||||
val expr = argExprs.foldLeft(body)((e, arg) => EApp(e, arg))
|
||||
|
||||
val machine =
|
||||
Speedy.Machine.fromExpr(
|
||||
expr = expr,
|
||||
checkSubmitterInMaintainers = VersionTimeline.checkSubmitterInMaintainers(lfVer),
|
||||
compiledPackages = PureCompiledPackages(state.packages).right.get,
|
||||
scenario = false,
|
||||
submissionTime = Time.Timestamp.now(),
|
||||
@ -449,40 +445,40 @@ object Repl {
|
||||
}
|
||||
}
|
||||
|
||||
def buildExpr(state: State, idAndArgs: Seq[String]): Option[(LanguageVersion, Expr)] =
|
||||
def buildExpr(state: State, idAndArgs: Seq[String]): Option[Expr] =
|
||||
idAndArgs match {
|
||||
case id :: args =>
|
||||
lookup(state, id) match {
|
||||
case None =>
|
||||
println("Error: " + id + " not found.")
|
||||
None
|
||||
case Some((lfVer, DValue(_, _, body, _))) =>
|
||||
case Some(DValue(_, _, body, _)) =>
|
||||
val argExprs = args.map(s => assertRight(parser.parseExpr(s)))
|
||||
Some((lfVer, argExprs.foldLeft(body)((e, arg) => EApp(e, arg))))
|
||||
Some(argExprs.foldLeft(body)((e, arg) => EApp(e, arg)))
|
||||
case Some(_) =>
|
||||
println("Error: " + id + " is not a value.")
|
||||
None
|
||||
}
|
||||
case _ =>
|
||||
usage(); None
|
||||
usage()
|
||||
None
|
||||
}
|
||||
|
||||
def invokeScenario(state: State, idAndArgs: Seq[String]): (Boolean, State) = {
|
||||
buildExpr(state, idAndArgs)
|
||||
.map {
|
||||
case (lfVer, expr) =>
|
||||
val (machine, errOrLedger) =
|
||||
state.scenarioRunner.run(lfVer, expr)
|
||||
errOrLedger match {
|
||||
case Left((err, ledger @ _)) =>
|
||||
println(prettyError(err, machine.ptx).render(128))
|
||||
(false, state)
|
||||
case Right((diff @ _, steps @ _, ledger)) =>
|
||||
// NOTE(JM): cannot print this, output used in tests.
|
||||
//println(s"done in ${diff.formatted("%.2f")}ms, ${steps} steps")
|
||||
println(prettyLedger(ledger).render(128))
|
||||
(true, state)
|
||||
}
|
||||
.map { expr =>
|
||||
val (machine, errOrLedger) =
|
||||
state.scenarioRunner.run(expr)
|
||||
errOrLedger match {
|
||||
case Left((err, ledger @ _)) =>
|
||||
println(prettyError(err, machine.ptx).render(128))
|
||||
(false, state)
|
||||
case Right((diff @ _, steps @ _, ledger)) =>
|
||||
// NOTE(JM): cannot print this, output used in tests.
|
||||
//println(s"done in ${diff.formatted("%.2f")}ms, ${steps} steps")
|
||||
println(prettyLedger(ledger).render(128))
|
||||
(true, state)
|
||||
}
|
||||
}
|
||||
.getOrElse((false, state))
|
||||
}
|
||||
@ -496,16 +492,16 @@ object Repl {
|
||||
definition <- mod.definitions
|
||||
(dfnName, dfn) = definition
|
||||
bodyScenario <- List(dfn).collect { case DValue(TScenario(_), _, body, _) => body }
|
||||
} yield QualifiedName(modName, dfnName).toString -> ((mod.languageVersion, bodyScenario))
|
||||
} yield QualifiedName(modName, dfnName).toString -> bodyScenario
|
||||
var failures = 0
|
||||
var successes = 0
|
||||
val state = state0
|
||||
var totalTime = 0.0
|
||||
var totalSteps = 0
|
||||
allScenarios.foreach {
|
||||
case (name, (lfVer, body)) =>
|
||||
case (name, body) =>
|
||||
print(name + ": ")
|
||||
val (machine, errOrLedger) = state.scenarioRunner.run(lfVer, body)
|
||||
val (machine, errOrLedger) = state.scenarioRunner.run(body)
|
||||
errOrLedger match {
|
||||
case Left((err, ledger @ _)) =>
|
||||
println(
|
||||
@ -545,7 +541,7 @@ object Repl {
|
||||
LfDefRef(DefinitionRef(packageId, qualName))
|
||||
}
|
||||
|
||||
def lookup(state: State, id: String): Option[(LanguageVersion, Definition)] = {
|
||||
def lookup(state: State, id: String): Option[Definition] = {
|
||||
val (defRef, optPackageId): (String, Option[PackageId]) =
|
||||
id.split("@").toList match {
|
||||
case defRef :: packageId :: Nil =>
|
||||
@ -558,20 +554,18 @@ object Repl {
|
||||
}
|
||||
optPackageId match {
|
||||
case Some(packageId) =>
|
||||
state.packages
|
||||
.get(packageId)
|
||||
.flatMap(pkg => pkg.modules.get(qualName.module))
|
||||
.flatMap(module =>
|
||||
module.definitions.get(qualName.name).map(defn => (module.languageVersion, defn)))
|
||||
for {
|
||||
pkg <- state.packages.get(packageId)
|
||||
module <- pkg.modules.get(qualName.module)
|
||||
defn <- module.definitions.get(qualName.name)
|
||||
} yield defn
|
||||
case None =>
|
||||
state.packages.view
|
||||
.flatMap { case (pkgId @ _, pkg) => pkg.modules.get(qualName.module).toList }
|
||||
.flatMap(
|
||||
module =>
|
||||
module.definitions
|
||||
.get(qualName.name)
|
||||
.toList
|
||||
.map(defn => (module.languageVersion, defn)))
|
||||
state.packages.values.view
|
||||
.flatMap(pkg =>
|
||||
for {
|
||||
module <- pkg.modules.get(qualName.module).toList
|
||||
defn <- module.definitions.get(qualName.name).toList
|
||||
} yield defn)
|
||||
.headOption
|
||||
|
||||
}
|
||||
|
@ -17,7 +17,6 @@ class ScenarioRunnerTest extends AsyncWordSpec with Matchers with ScalaFutures {
|
||||
val e = Ast.EScenario(ScenarioGetParty(Ast.EPrimLit(Ast.PLText(("foo-bar")))))
|
||||
val m = Speedy.Machine.fromExpr(
|
||||
expr = e,
|
||||
checkSubmitterInMaintainers = true,
|
||||
compiledPackages = PureCompiledPackages(Map.empty).right.get,
|
||||
scenario = true,
|
||||
submissionTime = Time.Timestamp.now(),
|
||||
|
@ -180,9 +180,4 @@ object VersionTimeline {
|
||||
.getOrElse(minimum)
|
||||
}
|
||||
|
||||
def checkSubmitterInMaintainers(lfVers: LanguageVersion): Boolean = {
|
||||
import Implicits._
|
||||
!(lfVers precedes LanguageVersion.Features.checkSubmitterInMaintainersVersion)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -212,11 +212,10 @@ object Converter {
|
||||
Array(SEVar(2), SEVar(1))))
|
||||
val machine =
|
||||
Speedy.Machine.fromSExpr(
|
||||
SEApp(SEValue(fun), Array(extractStruct)),
|
||||
false,
|
||||
compiledPackages,
|
||||
Time.Timestamp.now(),
|
||||
InitialSeeding.NoSeed
|
||||
sexpr = SEApp(SEValue(fun), Array(extractStruct)),
|
||||
compiledPackages = compiledPackages,
|
||||
submissionTime = Time.Timestamp.now(),
|
||||
seeding = InitialSeeding.NoSeed
|
||||
)
|
||||
@tailrec
|
||||
def iter(): Either[String, (SValue, SValue)] = {
|
||||
|
@ -288,10 +288,9 @@ class Runner(
|
||||
val machine =
|
||||
Speedy.Machine.fromSExpr(
|
||||
sexpr = script.expr,
|
||||
checkSubmitterInMaintainers = false,
|
||||
compiledPackages = extendedCompiledPackages,
|
||||
submissionTime = Timestamp.now(),
|
||||
seeds = InitialSeeding.NoSeed,
|
||||
seeding = InitialSeeding.NoSeed,
|
||||
)
|
||||
|
||||
// Removing the early return only makes this harder to read.
|
||||
|
@ -61,8 +61,6 @@ the submitter.
|
||||
|
||||
This means that if it fails, it doesn't guarantee that a contract with that key doesn't exist, just that you can't see one.
|
||||
|
||||
Moreover, future versions of DAML will enforce that when using ``fetchByKey`` the submitter of the transaction is one of the maintainers. It's therefore advised to write your contract key workflows with this future limitation in mind.
|
||||
|
||||
Because different templates can use the same key type, you need to specify the type of the contract you are trying to fetch using the ``@ContractType`` syntax.
|
||||
|
||||
.. _lookupbykey:
|
||||
@ -84,8 +82,6 @@ Unlike ``fetchByKey``, the transaction **does not fail** if a contract with the
|
||||
|
||||
To get the data from the contract once you've confirmed it exists, you'll still need to use ``fetch``.
|
||||
|
||||
Moreover, like ``fetchByKey``, future versions of DAML will enforce the submitter of the transaction is one of the maintainers. It's therefore advised to write your contract key workflows with this future limitation in mind.
|
||||
|
||||
Because different templates can use the same key type, you need to specify the type of the contract you are trying to fetch using the ``@ContractType`` syntax.
|
||||
|
||||
``exerciseByKey``
|
||||
|
@ -27,7 +27,6 @@ object Tests {
|
||||
"CommandSubmissionCompletionIT" -> (new CommandSubmissionCompletion(_)),
|
||||
"CommandDeduplicationIT" -> (new CommandDeduplication(_)),
|
||||
"ContractKeysIT" -> (new ContractKeys(_)),
|
||||
"ContractKeysSubmitterIsMaintainerIT" -> (new ContractKeysSubmitterIsMaintainer(_)),
|
||||
"DivulgenceIT" -> (new Divulgence(_)),
|
||||
"HealthServiceIT" -> (new HealthService(_)),
|
||||
"IdentityIT" -> (new Identity(_)),
|
||||
|
@ -1,230 +0,0 @@
|
||||
// Copyright (c) 2020 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.ledger.api.testtool.tests
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import com.daml.ledger.api.testtool.infrastructure.Allocation._
|
||||
import com.daml.ledger.api.testtool.infrastructure.Assertions._
|
||||
import com.daml.ledger.api.testtool.infrastructure.Eventually.eventually
|
||||
import com.daml.ledger.api.testtool.infrastructure.Synchronize.synchronize
|
||||
import com.daml.ledger.api.testtool.infrastructure.{LedgerSession, LedgerTestSuite}
|
||||
import com.daml.ledger.test_dev.DA.Types.{Tuple2 => DamlTuple2}
|
||||
import com.daml.ledger.test_dev.Test.Delegation._
|
||||
import com.daml.ledger.test_dev.Test.ShowDelegated._
|
||||
import com.daml.ledger.test_dev.Test.TextKey._
|
||||
import com.daml.ledger.test_dev.Test.TextKeyOperations._
|
||||
import com.daml.ledger.test_dev.Test._
|
||||
import io.grpc.Status
|
||||
|
||||
final class ContractKeysSubmitterIsMaintainer(session: LedgerSession)
|
||||
extends LedgerTestSuite(session) {
|
||||
test(
|
||||
"CKNoFetchOrLookup",
|
||||
"Divulged contracts cannot be fetched or looked up by key",
|
||||
allocate(SingleParty, SingleParty),
|
||||
) {
|
||||
case Participants(Participant(alpha, owner), Participant(beta, delegate)) =>
|
||||
val key = s"${UUID.randomUUID.toString}-key"
|
||||
for {
|
||||
// create contracts to work with
|
||||
delegated <- alpha.create(owner, Delegated(owner, key))
|
||||
delegation <- alpha.create(owner, Delegation(owner, delegate))
|
||||
showDelegated <- alpha.create(owner, ShowDelegated(owner, delegate))
|
||||
|
||||
// divulge the contract
|
||||
_ <- alpha.exercise(owner, showDelegated.exerciseShowIt(_, delegated))
|
||||
// fetch delegated
|
||||
_ <- eventually {
|
||||
beta.exercise(delegate, delegation.exerciseFetchDelegated(_, delegated))
|
||||
}
|
||||
|
||||
// fetch by key delegation is not allowed
|
||||
fetchByKeyFailure <- beta
|
||||
.exercise(
|
||||
delegate,
|
||||
delegation
|
||||
.exerciseFetchByKeyDelegated(_, owner, key),
|
||||
)
|
||||
.failed
|
||||
|
||||
// lookup by key delegation is not allowed
|
||||
lookupByKeyFailure <- beta
|
||||
.exercise(
|
||||
delegate,
|
||||
delegation
|
||||
.exerciseLookupByKeyDelegated(_, owner, key),
|
||||
)
|
||||
.failed
|
||||
} yield {
|
||||
assertGrpcError(
|
||||
fetchByKeyFailure,
|
||||
Status.Code.INVALID_ARGUMENT,
|
||||
s"Expected the submitter '$delegate' to be in maintainers '$owner'",
|
||||
)
|
||||
assertGrpcError(
|
||||
lookupByKeyFailure,
|
||||
Status.Code.INVALID_ARGUMENT,
|
||||
s"Expected the submitter '$delegate' to be in maintainers '$owner'",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
test(
|
||||
"CKSubmitterIsMaintainerNoFetchUndisclosed",
|
||||
"Contract Keys should reject fetching an undisclosed contract",
|
||||
allocate(SingleParty, SingleParty),
|
||||
) {
|
||||
case Participants(Participant(alpha, owner), Participant(beta, delegate)) =>
|
||||
val key = s"${UUID.randomUUID.toString}-key"
|
||||
for {
|
||||
// create contracts to work with
|
||||
delegated <- alpha.create(owner, Delegated(owner, key))
|
||||
delegation <- alpha.create(owner, Delegation(owner, delegate))
|
||||
|
||||
_ <- synchronize(alpha, beta)
|
||||
|
||||
// fetch should fail
|
||||
fetchFailure <- beta
|
||||
.exercise(
|
||||
delegate,
|
||||
delegation
|
||||
.exerciseFetchDelegated(_, delegated),
|
||||
)
|
||||
.failed
|
||||
|
||||
// fetch by key should fail
|
||||
fetchByKeyFailure <- beta
|
||||
.exercise(
|
||||
delegate,
|
||||
delegation
|
||||
.exerciseFetchByKeyDelegated(_, owner, key),
|
||||
)
|
||||
.failed
|
||||
|
||||
// lookup by key should fail
|
||||
lookupByKeyFailure <- beta
|
||||
.exercise(
|
||||
delegate,
|
||||
delegation
|
||||
.exerciseLookupByKeyDelegated(_, owner, key),
|
||||
)
|
||||
.failed
|
||||
} yield {
|
||||
assertGrpcError(
|
||||
fetchFailure,
|
||||
Status.Code.INVALID_ARGUMENT,
|
||||
"dependency error: couldn't find contract",
|
||||
)
|
||||
assertGrpcError(
|
||||
fetchByKeyFailure,
|
||||
Status.Code.INVALID_ARGUMENT,
|
||||
s"Expected the submitter '$delegate' to be in maintainers '$owner'",
|
||||
)
|
||||
assertGrpcError(
|
||||
lookupByKeyFailure,
|
||||
Status.Code.INVALID_ARGUMENT,
|
||||
s"Expected the submitter '$delegate' to be in maintainers '$owner'",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
test(
|
||||
"CKSubmitterIsMaintainerMaintainerScoped",
|
||||
"Contract keys should be scoped by maintainer",
|
||||
allocate(SingleParty, SingleParty),
|
||||
) {
|
||||
case Participants(Participant(alpha, alice), Participant(beta, bob)) =>
|
||||
val keyPrefix = UUID.randomUUID.toString
|
||||
val key1 = s"$keyPrefix-some-key"
|
||||
val key2 = s"$keyPrefix-some-other-key"
|
||||
val unknownKey = s"$keyPrefix-unknown-key"
|
||||
|
||||
for {
|
||||
//create contracts to work with
|
||||
tk1 <- alpha.create(alice, TextKey(alice, key1, List(bob)))
|
||||
tk2 <- alpha.create(alice, TextKey(alice, key2, List(bob)))
|
||||
aliceTKO <- alpha.create(alice, TextKeyOperations(alice))
|
||||
bobTKO <- beta.create(bob, TextKeyOperations(bob))
|
||||
|
||||
// creating a contract with a duplicate key should fail
|
||||
duplicateKeyFailure <- alpha.create(alice, TextKey(alice, key1, List(bob))).failed
|
||||
|
||||
_ <- synchronize(alpha, beta)
|
||||
|
||||
// trying to lookup an unauthorized key should fail
|
||||
bobLooksUpTextKeyFailure <- beta
|
||||
.exercise(
|
||||
bob,
|
||||
bobTKO
|
||||
.exerciseTKOLookup(_, DamlTuple2(alice, key1), Some(tk1)),
|
||||
)
|
||||
.failed
|
||||
|
||||
// trying to lookup an unauthorized non-existing key should fail
|
||||
bobLooksUpBogusTextKeyFailure <- beta
|
||||
.exercise(bob, bobTKO.exerciseTKOLookup(_, DamlTuple2(alice, unknownKey), None))
|
||||
.failed
|
||||
|
||||
// successful, authorized lookup
|
||||
_ <- alpha.exercise(
|
||||
alice,
|
||||
aliceTKO
|
||||
.exerciseTKOLookup(_, DamlTuple2(alice, key1), Some(tk1)),
|
||||
)
|
||||
|
||||
// successful fetch
|
||||
_ <- alpha.exercise(alice, aliceTKO.exerciseTKOFetch(_, DamlTuple2(alice, key1), tk1))
|
||||
|
||||
// successful, authorized lookup of non-existing key
|
||||
_ <- alpha.exercise(
|
||||
alice,
|
||||
aliceTKO.exerciseTKOLookup(_, DamlTuple2(alice, unknownKey), None),
|
||||
)
|
||||
|
||||
// failing fetch
|
||||
aliceFailedFetch <- alpha
|
||||
.exercise(
|
||||
alice,
|
||||
aliceTKO
|
||||
.exerciseTKOFetch(_, DamlTuple2(alice, unknownKey), tk1),
|
||||
)
|
||||
.failed
|
||||
|
||||
// now we exercise the contract, thus archiving it, and then verify
|
||||
// that we cannot look it up anymore
|
||||
_ <- alpha.exercise(alice, tk1.exerciseTextKeyChoice)
|
||||
_ <- alpha.exercise(alice, aliceTKO.exerciseTKOLookup(_, DamlTuple2(alice, key1), None))
|
||||
|
||||
// lookup the key, consume it, then verify we cannot look it up anymore
|
||||
_ <- alpha.exercise(
|
||||
alice,
|
||||
aliceTKO.exerciseTKOConsumeAndLookup(_, tk2, DamlTuple2(alice, key2)),
|
||||
)
|
||||
|
||||
// failing create when a maintainer is not a signatory
|
||||
maintainerNotSignatoryFailed <- alpha
|
||||
.create(alice, MaintainerNotSignatory(alice, bob))
|
||||
.failed
|
||||
} yield {
|
||||
assertGrpcError(duplicateKeyFailure, Status.Code.INVALID_ARGUMENT, "DuplicateKey")
|
||||
assertGrpcError(
|
||||
bobLooksUpTextKeyFailure,
|
||||
Status.Code.INVALID_ARGUMENT,
|
||||
s"Expected the submitter '$bob' to be in maintainers '$alice'",
|
||||
)
|
||||
assertGrpcError(
|
||||
bobLooksUpBogusTextKeyFailure,
|
||||
Status.Code.INVALID_ARGUMENT,
|
||||
s"Expected the submitter '$bob' to be in maintainers '$alice'",
|
||||
)
|
||||
assertGrpcError(aliceFailedFetch, Status.Code.INVALID_ARGUMENT, "couldn't find key")
|
||||
assertGrpcError(
|
||||
maintainerNotSignatoryFailed,
|
||||
Status.Code.INVALID_ARGUMENT,
|
||||
"are not a subset of the signatories",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -8,9 +8,9 @@ import java.time.Instant
|
||||
import com.daml.lf.CompiledPackages
|
||||
import com.daml.lf.data._
|
||||
import com.daml.lf.language.Ast.{DDataType, DTypeSyn, DValue, Definition}
|
||||
import com.daml.lf.language.{Ast, LanguageVersion}
|
||||
import com.daml.lf.language.Ast
|
||||
import com.daml.lf.speedy.{ScenarioRunner, Speedy}
|
||||
import com.daml.lf.transaction.{GenTransaction, VersionTimeline}
|
||||
import com.daml.lf.transaction.GenTransaction
|
||||
import com.daml.lf.types.Ledger.ScenarioTransactionId
|
||||
import com.daml.lf.types.{Ledger => L}
|
||||
import com.daml.platform.packages.InMemoryPackageStore
|
||||
@ -112,12 +112,11 @@ object ScenarioLoader {
|
||||
compiledPackages: CompiledPackages,
|
||||
scenario: String): (L.Ledger, Ref.DefinitionRef) = {
|
||||
val scenarioQualName = getScenarioQualifiedName(packages, scenario)
|
||||
val candidateScenarios: List[(Ref.DefinitionRef, LanguageVersion, Definition)] =
|
||||
val candidateScenarios: List[(Ref.DefinitionRef, Ast.Definition)] =
|
||||
getCandidateScenarios(packages, scenarioQualName)
|
||||
val (scenarioRef, scenarioLfVers, scenarioDef) =
|
||||
identifyScenario(packages, scenario, candidateScenarios)
|
||||
val (scenarioRef, scenarioDef) = identifyScenario(packages, scenario, candidateScenarios)
|
||||
val scenarioExpr = getScenarioExpr(scenarioRef, scenarioDef)
|
||||
val speedyMachine = getSpeedyMachine(scenarioLfVers, scenarioExpr, compiledPackages)
|
||||
val speedyMachine = getSpeedyMachine(scenarioExpr, compiledPackages)
|
||||
val scenarioLedger = getScenarioLedger(scenarioRef, speedyMachine)
|
||||
(scenarioLedger, scenarioRef)
|
||||
}
|
||||
@ -133,13 +132,11 @@ object ScenarioLoader {
|
||||
}
|
||||
|
||||
private def getSpeedyMachine(
|
||||
submissionVersion: LanguageVersion,
|
||||
scenarioExpr: Ast.Expr,
|
||||
compiledPackages: CompiledPackages): Speedy.Machine =
|
||||
Speedy.Machine.newBuilder(compiledPackages, Time.Timestamp.now(), None) match {
|
||||
case Left(err) => throw new RuntimeException(s"Could not build speedy machine: $err")
|
||||
case Right(build) =>
|
||||
build(VersionTimeline.checkSubmitterInMaintainers(submissionVersion), scenarioExpr)
|
||||
case Right(build) => build(scenarioExpr)
|
||||
}
|
||||
|
||||
private def getScenarioExpr(scenarioRef: Ref.DefinitionRef, scenarioDef: Definition): Ast.Expr = {
|
||||
@ -157,8 +154,8 @@ object ScenarioLoader {
|
||||
private def identifyScenario(
|
||||
packages: InMemoryPackageStore,
|
||||
scenario: String,
|
||||
candidateScenarios: List[(Ref.DefinitionRef, LanguageVersion, Definition)])
|
||||
: (Ref.DefinitionRef, LanguageVersion, Definition) = {
|
||||
candidateScenarios: List[(Ref.DefinitionRef, Definition)])
|
||||
: (Ref.DefinitionRef, Definition) = {
|
||||
candidateScenarios match {
|
||||
case Nil =>
|
||||
throw new RuntimeException(
|
||||
@ -173,7 +170,7 @@ object ScenarioLoader {
|
||||
private def getCandidateScenarios(
|
||||
packages: InMemoryPackageStore,
|
||||
scenarioQualName: Ref.QualifiedName
|
||||
): List[(Ref.Identifier, LanguageVersion, Definition)] = {
|
||||
): List[(Ref.Identifier, Ast.Definition)] = {
|
||||
packages
|
||||
.listLfPackagesSync()
|
||||
.flatMap {
|
||||
@ -183,11 +180,7 @@ object ScenarioLoader {
|
||||
.getOrElse(sys.error(s"Listed package $packageId not found"))
|
||||
pkg.lookupIdentifier(scenarioQualName) match {
|
||||
case Right(x) =>
|
||||
List(
|
||||
(
|
||||
Ref.Identifier(packageId, scenarioQualName),
|
||||
pkg.modules(scenarioQualName.module).languageVersion,
|
||||
x))
|
||||
List((Ref.Identifier(packageId, scenarioQualName) -> x))
|
||||
case Left(_) => List()
|
||||
}
|
||||
}(breakOut)
|
||||
|
@ -138,10 +138,9 @@ object Trigger extends StrictLogging {
|
||||
)
|
||||
val machine = Speedy.Machine.fromSExpr(
|
||||
sexpr = heartbeat,
|
||||
checkSubmitterInMaintainers = false,
|
||||
compiledPackages = compiledPackages,
|
||||
submissionTime = Timestamp.now(),
|
||||
seeds = InitialSeeding.NoSeed,
|
||||
seeding = InitialSeeding.NoSeed,
|
||||
)
|
||||
Machine.stepToValue(machine)
|
||||
machine.toSValue match {
|
||||
@ -163,10 +162,9 @@ object Trigger extends StrictLogging {
|
||||
val machine =
|
||||
Speedy.Machine.fromSExpr(
|
||||
sexpr = registeredTemplates,
|
||||
checkSubmitterInMaintainers = false,
|
||||
compiledPackages = compiledPackages,
|
||||
submissionTime = Timestamp.now(),
|
||||
seeds = InitialSeeding.NoSeed,
|
||||
seeding = InitialSeeding.NoSeed,
|
||||
)
|
||||
Machine.stepToValue(machine)
|
||||
machine.toSValue match {
|
||||
@ -318,10 +316,9 @@ class Runner(
|
||||
|
||||
var machine = Speedy.Machine.fromSExpr(
|
||||
sexpr = null,
|
||||
checkSubmitterInMaintainers = false,
|
||||
compiledPackages = compiledPackages,
|
||||
submissionTime = Timestamp.now(),
|
||||
seeds = InitialSeeding.NoSeed
|
||||
seeding = InitialSeeding.NoSeed
|
||||
)
|
||||
val createdExpr: SExpr = SEValue(converter.fromACS(acs) match {
|
||||
case Left(err) => throw new ConverterException(err)
|
||||
|
Loading…
Reference in New Issue
Block a user