DAML-LF: remove submitter is in maintainer check (#5611)

CHANGELOG_BEGIN
CHANGELOG_END
This commit is contained in:
Remy 2020-04-23 16:10:39 +02:00 committed by GitHub
parent 68b7dcfa1a
commit 15354c3256
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 152 additions and 569 deletions

View File

@ -151,7 +151,6 @@ class Context(val contextId: Context.ContextId) {
} yield } yield
Speedy.Machine Speedy.Machine
.build( .build(
checkSubmitterInMaintainers = false,
sexpr = defn, sexpr = defn,
compiledPackages = PureCompiledPackages(allPackages, defns), compiledPackages = PureCompiledPackages(allPackages, defns),
submissionTime, submissionTime,

View File

@ -150,9 +150,6 @@ final class Conversions(
sys.error( 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.", 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 builder.build
} }

View File

@ -98,7 +98,7 @@ final class ConcurrentCompiledPackages extends MutableCompiledPackages {
} }
} }
ResultDone(()) ResultDone.Unit
} }
def clear(): Unit = this.synchronized[Unit] { def clear(): Unit = this.synchronized[Unit] {

View File

@ -8,7 +8,7 @@ import com.daml.lf.command._
import com.daml.lf.data._ import com.daml.lf.data._
import com.daml.lf.data.Ref.{PackageId, ParticipantId, Party} import com.daml.lf.data.Ref.{PackageId, ParticipantId, Party}
import com.daml.lf.language.Ast._ 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.Speedy.Machine
import com.daml.lf.speedy.SResult._ import com.daml.lf.speedy.SResult._
import com.daml.lf.transaction.{Transaction => Tx} import com.daml.lf.transaction.{Transaction => Tx}
@ -85,33 +85,28 @@ final class Engine {
preprocessor preprocessor
.preprocessCommands(cmds.commands) .preprocessCommands(cmds.commands)
.flatMap { processedCmds => .flatMap { processedCmds =>
ShouldCheckSubmitterInMaintainers(compiledPackages, cmds).flatMap { interpretCommands(
checkSubmitterInMaintainers => validating = false,
interpretCommands( submitters = Set(cmds.submitter),
validating = false, commands = processedCmds,
checkSubmitterInMaintainers = checkSubmitterInMaintainers, ledgerTime = cmds.ledgerEffectiveTime,
submitters = Set(cmds.submitter), submissionTime = submissionTime,
commands = processedCmds, seeding = Engine.initialSeeding(submissionSeed, participantId, submissionTime),
ledgerTime = cmds.ledgerEffectiveTime, ) map {
submissionTime = submissionTime, case (tx, meta) =>
seeding = Engine.initialSeeding(submissionSeed, participantId, submissionTime), // Annotate the transaction with the package dependencies. Since
) map { // all commands are actions on a contract template, with a fully typed
case (tx, meta) => // argument, we only need to consider the templates mentioned in the command
// Annotate the transaction with the package dependencies. Since // to compute the full dependencies.
// all commands are actions on a contract template, with a fully typed val deps = processedCmds.foldLeft(Set.empty[PackageId]) { (pkgIds, cmd) =>
// argument, we only need to consider the templates mentioned in the command val pkgId = cmd.templateId.packageId
// to compute the full dependencies. val transitiveDeps =
val deps = processedCmds.foldLeft(Set.empty[PackageId]) { (pkgIds, cmd) => compiledPackages
val pkgId = cmd.templateId.packageId .getPackageDependencies(pkgId)
val transitiveDeps = .getOrElse(sys.error(s"INTERNAL ERROR: Missing dependencies of package $pkgId"))
compiledPackages (pkgIds + pkgId) union transitiveDeps
.getPackageDependencies(pkgId)
.getOrElse(
sys.error(s"INTERNAL ERROR: Missing dependencies of package $pkgId"))
(pkgIds + pkgId) union transitiveDeps
}
tx -> meta.copy(submissionSeed = submissionSeed, usedPackages = deps)
} }
tx -> meta.copy(submissionSeed = submissionSeed, usedPackages = deps)
} }
} }
} }
@ -137,13 +132,9 @@ final class Engine {
): Result[(Tx.Transaction, Tx.Metadata)] = ): Result[(Tx.Transaction, Tx.Metadata)] =
for { for {
command <- preprocessor.translateNode(node) command <- preprocessor.translateNode(node)
checkSubmitterInMaintainers <- ShouldCheckSubmitterInMaintainers(
compiledPackages,
ImmArray(command.templateId))
// reinterpret is never used for submission, only for validation. // reinterpret is never used for submission, only for validation.
result <- interpretCommands( result <- interpretCommands(
validating = true, validating = true,
checkSubmitterInMaintainers = checkSubmitterInMaintainers,
submitters = submitters, submitters = submitters,
commands = ImmArray(command), commands = ImmArray(command),
ledgerTime = ledgerEffectiveTime, ledgerTime = ledgerEffectiveTime,
@ -196,18 +187,14 @@ final class Engine {
_ <- if (submittersOpt.exists(_.size != 1)) _ <- if (submittersOpt.exists(_.size != 1))
ResultError(ValidationError(s"Transaction's roots do not have exactly one authorizer: $tx")) 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 // For empty transactions, use an empty set of submitters
submitters = submittersOpt.getOrElse(Set.empty) submitters = submittersOpt.getOrElse(Set.empty)
commands <- preprocessor.translateTransactionRoots(tx) commands <- preprocessor.translateTransactionRoots(tx)
checkSubmitterInMaintainers <- ShouldCheckSubmitterInMaintainers(
compiledPackages,
commands.map(_._2.templateId))
result <- interpretCommands( result <- interpretCommands(
validating = true, validating = true,
checkSubmitterInMaintainers = checkSubmitterInMaintainers,
submitters = submitters, submitters = submitters,
commands = commands.map(_._2), commands = commands.map(_._2),
ledgerTime = ledgerEffectiveTime, ledgerTime = ledgerEffectiveTime,
@ -216,7 +203,7 @@ final class Engine {
) )
(rtx, _) = result (rtx, _) = result
validationResult <- if (tx isReplayedBy rtx) { validationResult <- if (tx isReplayedBy rtx) {
ResultDone(()) ResultDone.Unit
} else { } else {
ResultError( ResultError(
ValidationError( ValidationError(
@ -225,34 +212,64 @@ final class Engine {
} yield validationResult } 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 /** Interprets the given commands under the authority of @submitters
* *
* Submitters are a set, in order to support interpreting subtransactions * Submitters are a set, in order to support interpreting subtransactions
* (a subtransaction can be authorized by multiple parties). * (a subtransaction can be authorized by multiple parties).
* *
* [[seeding]] is seeding used to derive node seed and contractId discriminator. * [[seeding]] is seeding used to derive node seed and contractId discriminator.
*
*/ */
private[engine] def interpretCommands( private[engine] def interpretCommands(
validating: Boolean, validating: Boolean,
/* See documentation for `Speedy.Machine` for the meaning of this field */ /* See documentation for `Speedy.Machine` for the meaning of this field */
checkSubmitterInMaintainers: Boolean,
submitters: Set[Party], submitters: Set[Party],
commands: ImmArray[SpeedyCommand], commands: ImmArray[speedy.Command],
ledgerTime: Time.Timestamp, ledgerTime: Time.Timestamp,
submissionTime: Time.Timestamp, submissionTime: Time.Timestamp,
seeding: speedy.InitialSeeding, seeding: speedy.InitialSeeding,
): Result[(Tx.Transaction, Tx.Metadata)] = { ): Result[(Tx.Transaction, Tx.Metadata)] =
val machine = Machine runSafely(
.build( loadPackages(commands.foldLeft(Set.empty[PackageId])(_ + _.templateId.packageId).toList)
checkSubmitterInMaintainers = checkSubmitterInMaintainers, ) {
sexpr = Compiler(compiledPackages.packages).unsafeCompile(commands), val machine = Machine
compiledPackages = compiledPackages, .build(
submissionTime = submissionTime, sexpr = speedy.Compiler(compiledPackages.packages).unsafeCompile(commands),
seeds = seeding, compiledPackages = compiledPackages,
) submissionTime = submissionTime,
.copy(validating = validating, committers = submitters) seeds = seeding,
interpretLoop(machine, ledgerTime) )
} .copy(validating = validating, committers = submitters)
interpretLoop(machine, ledgerTime)
}
// TODO SC remove 'return', notwithstanding a love of unhandled exceptions // TODO SC remove 'return', notwithstanding a love of unhandled exceptions
@SuppressWarnings(Array("org.wartremover.warts.Any", "org.wartremover.warts.Return")) @SuppressWarnings(Array("org.wartremover.warts.Any", "org.wartremover.warts.Return"))

View File

@ -58,6 +58,9 @@ sealed trait Result[+A] extends Product with Serializable {
} }
final case class ResultDone[A](result: A) extends Result[A] 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] final case class ResultError(err: Error) extends Result[Nothing]
/** /**
@ -204,9 +207,10 @@ object Result {
} }
def assert(assertion: Boolean)(err: Error): Result[Unit] = def assert(assertion: Boolean)(err: Error): Result[Unit] =
if (assertion) { if (assertion)
ResultDone(()) ResultDone.Unit
} else ResultError(err) else
ResultError(err)
implicit val resultInstance: Monad[Result] = new Monad[Result] { implicit val resultInstance: Monad[Result] = new Monad[Result] {
override def point[A](a: => A): Result[A] = ResultDone(a) override def point[A](a: => A): Result[A] = ResultDone(a)

View File

@ -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)
}

View File

@ -492,7 +492,6 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
engine engine
.interpretCommands( .interpretCommands(
validating = false, validating = false,
checkSubmitterInMaintainers = true,
submitters = Set(party), submitters = Set(party),
commands = r, commands = r,
ledgerTime = let, ledgerTime = let,
@ -595,7 +594,6 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
engine engine
.interpretCommands( .interpretCommands(
validating = false, validating = false,
checkSubmitterInMaintainers = true,
submitters = Set(alice), submitters = Set(alice),
commands = r, commands = r,
ledgerTime = let, ledgerTime = let,
@ -678,7 +676,6 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
engine engine
.interpretCommands( .interpretCommands(
validating = false, validating = false,
checkSubmitterInMaintainers = true,
submitters = Set(party), submitters = Set(party),
commands = r, commands = r,
ledgerTime = let, ledgerTime = let,
@ -909,7 +906,6 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val Right((rtx, _)) = engine val Right((rtx, _)) = engine
.interpretCommands( .interpretCommands(
validating = false, validating = false,
checkSubmitterInMaintainers = true,
submitters = Set(bob), submitters = Set(bob),
commands = cmds, commands = cmds,
ledgerTime = let, ledgerTime = let,
@ -989,7 +985,6 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
engine engine
.interpretCommands( .interpretCommands(
validating = false, validating = false,
checkSubmitterInMaintainers = true,
submitters = Set(bob), submitters = Set(bob),
commands = cmds, commands = cmds,
ledgerTime = let, ledgerTime = let,
@ -1126,7 +1121,6 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
engine engine
.interpretCommands( .interpretCommands(
validating = false, validating = false,
checkSubmitterInMaintainers = true,
submitters = Set(exerciseActor), submitters = Set(exerciseActor),
commands = r, commands = r,
ledgerTime = let, 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 cmd = speedy.Command.Fetch(BasicTests_WithKey, SValue.SContractId(fetchedCid))
val Right((tx, _)) = engine 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) .consume(lookupContractMap.get, lookupPackage, lookupKey)
tx.nodes.values.headOption match { tx.nodes.values.headOption match {
@ -1438,7 +1439,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
.consume(lookupContractMap.get, lookupPackage, lookupKey) .consume(lookupContractMap.get, lookupPackage, lookupKey)
val Right((tx, txMeta)) = engine 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) .consume(lookupContractMap.get, lookupPackage, lookupKey)
tx.nodes tx.nodes

View File

@ -77,7 +77,7 @@ class PreprocessorSpec extends WordSpec with Matchers with TableDrivenPropertyCh
) )
val compiledPackage = ConcurrentCompiledPackages() val compiledPackage = ConcurrentCompiledPackages()
assert(compiledPackage.addPackage(pkgId, pkg) == ResultDone(())) assert(compiledPackage.addPackage(pkgId, pkg) == ResultDone.Unit)
val preprocessor = new Preprocessor(compiledPackage) val preprocessor = new Preprocessor(compiledPackage)
import preprocessor.translateValue import preprocessor.translateValue

View File

@ -70,11 +70,6 @@ object Pretty {
text("Expected contract of type") & prettyTypeConName(expected) & text("but got") & prettyTypeConName( text("Expected contract of type") & prettyTypeConName(expected) & text("but got") & prettyTypeConName(
actual, 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.. // A minimal pretty-print of an update transaction node, without recursing into child nodes..

View File

@ -1116,7 +1116,6 @@ object SBuiltin {
def execute(args: util.ArrayList[SValue], machine: Machine): Unit = { def execute(args: util.ArrayList[SValue], machine: Machine): Unit = {
checkToken(args.get(1)) checkToken(args.get(1))
val keyWithMaintainers = extractKeyWithMaintainers(args.get(0)) val keyWithMaintainers = extractKeyWithMaintainers(args.get(0))
checkLookupMaintainers(templateId, machine, keyWithMaintainers.maintainers)
val gkey = GlobalKey(templateId, keyWithMaintainers.key.value) val gkey = GlobalKey(templateId, keyWithMaintainers.key.value)
// check if we find it locally // check if we find it locally
machine.ptx.keys.get(gkey) match { machine.ptx.keys.get(gkey) match {
@ -1187,7 +1186,6 @@ object SBuiltin {
def execute(args: util.ArrayList[SValue], machine: Machine): Unit = { def execute(args: util.ArrayList[SValue], machine: Machine): Unit = {
checkToken(args.get(1)) checkToken(args.get(1))
val keyWithMaintainers = extractKeyWithMaintainers(args.get(0)) val keyWithMaintainers = extractKeyWithMaintainers(args.get(0))
checkLookupMaintainers(templateId, machine, keyWithMaintainers.maintainers)
val gkey = GlobalKey(templateId, keyWithMaintainers.key.value) val gkey = GlobalKey(templateId, keyWithMaintainers.key.value)
// check if we find it locally // check if we find it locally
machine.ptx.keys.get(gkey) match { machine.ptx.keys.get(gkey) match {
@ -1626,36 +1624,6 @@ object SBuiltin {
case v => crash(s"Expected optional key with maintainers, got: $v") 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 = private def rightOrArithmeticError[A](message: String, mb: Either[String, A]): A =
mb.fold(_ => throw DamlEArithmeticError(s"$message"), identity) mb.fold(_ => throw DamlEArithmeticError(s"$message"), identity)

View File

@ -63,15 +63,6 @@ object SError {
reason: String, reason: String,
) extends SErrorDamlException ) 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. */ /** Errors from scenario interpretation. */
sealed trait SErrorScenario extends SError sealed trait SErrorScenario extends SError

View File

@ -40,17 +40,6 @@ object Speedy {
var committers: Set[Party], var committers: Set[Party],
/* Commit location, if a scenario commit is in progress. */ /* Commit location, if a scenario commit is in progress. */
var commitLocation: Option[Location], 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 /* Whether the current submission is validating the transaction, or interpreting
* it. If this is false, the committers must be a singleton set. * 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 val damlTraceLog = LoggerFactory.getLogger("daml.tracelog")
private def initial( private def initial(
checkSubmitterInMaintainers: Boolean,
compiledPackages: CompiledPackages, compiledPackages: CompiledPackages,
submissionTime: Time.Timestamp, submissionTime: Time.Timestamp,
initialSeeding: InitialSeeding, initialSeeding: InitialSeeding,
@ -269,7 +257,6 @@ object Speedy {
commitLocation = None, commitLocation = None,
traceLog = TraceLog(damlTraceLog, 100), traceLog = TraceLog(damlTraceLog, 100),
compiledPackages = compiledPackages, compiledPackages = compiledPackages,
checkSubmitterInMaintainers = checkSubmitterInMaintainers,
validating = false, validating = false,
dependsOnTime = false, dependsOnTime = false,
) )
@ -278,21 +265,19 @@ object Speedy {
compiledPackages: CompiledPackages, compiledPackages: CompiledPackages,
submissionTime: Time.Timestamp, submissionTime: Time.Timestamp,
transactionSeed: Option[crypto.Hash], transactionSeed: Option[crypto.Hash],
): Either[SError, (Boolean, Expr) => Machine] = { ): Either[SError, Expr => Machine] = {
val compiler = Compiler(compiledPackages.packages) val compiler = Compiler(compiledPackages.packages)
Right({ (checkSubmitterInMaintainers: Boolean, expr: Expr) => Right(
fromSExpr( (expr: Expr) =>
SEApp(compiler.unsafeCompile(expr), Array(SEValue.Token)), fromSExpr(
checkSubmitterInMaintainers, SEApp(compiler.unsafeCompile(expr), Array(SEValue.Token)),
compiledPackages, compiledPackages,
submissionTime, submissionTime,
InitialSeeding(transactionSeed) InitialSeeding(transactionSeed)
) ))
})
} }
def build( def build(
checkSubmitterInMaintainers: Boolean,
sexpr: SExpr, sexpr: SExpr,
compiledPackages: CompiledPackages, compiledPackages: CompiledPackages,
submissionTime: Time.Timestamp, submissionTime: Time.Timestamp,
@ -300,7 +285,6 @@ object Speedy {
): Machine = ): Machine =
fromSExpr( fromSExpr(
SEApp(sexpr, Array(SEValue.Token)), SEApp(sexpr, Array(SEValue.Token)),
checkSubmitterInMaintainers,
compiledPackages, compiledPackages,
submissionTime, submissionTime,
seeds, seeds,
@ -309,7 +293,6 @@ object Speedy {
// Used from repl. // Used from repl.
def fromExpr( def fromExpr(
expr: Expr, expr: Expr,
checkSubmitterInMaintainers: Boolean,
compiledPackages: CompiledPackages, compiledPackages: CompiledPackages,
scenario: Boolean, scenario: Boolean,
submissionTime: Time.Timestamp, submissionTime: Time.Timestamp,
@ -324,7 +307,6 @@ object Speedy {
fromSExpr( fromSExpr(
sexpr, sexpr,
checkSubmitterInMaintainers,
compiledPackages, compiledPackages,
submissionTime, submissionTime,
InitialSeeding(transactionSeed), InitialSeeding(transactionSeed),
@ -336,13 +318,11 @@ object Speedy {
// a token is not appropriate. // a token is not appropriate.
def fromSExpr( def fromSExpr(
sexpr: SExpr, sexpr: SExpr,
checkSubmitterInMaintainers: Boolean,
compiledPackages: CompiledPackages, compiledPackages: CompiledPackages,
submissionTime: Time.Timestamp, submissionTime: Time.Timestamp,
seeds: InitialSeeding, seeding: InitialSeeding,
): Machine = ): Machine =
initial(checkSubmitterInMaintainers, compiledPackages, submissionTime, seeds).copy( initial(compiledPackages, submissionTime, seeding).copy(ctrl = CtrlExpr(sexpr))
ctrl = CtrlExpr(sexpr))
} }
/** Control specifies the thing that the machine should be reducing. /** Control specifies the thing that the machine should be reducing.

View File

@ -25,7 +25,6 @@ class InterpreterTest extends WordSpec with Matchers with TableDrivenPropertyChe
private def runExpr(e: Expr): SValue = { private def runExpr(e: Expr): SValue = {
val machine = Speedy.Machine.fromExpr( val machine = Speedy.Machine.fromExpr(
expr = e, expr = e,
checkSubmitterInMaintainers = true,
compiledPackages = PureCompiledPackages(Map.empty).right.get, compiledPackages = PureCompiledPackages(Map.empty).right.get,
scenario = false, scenario = false,
submissionTime = Time.Timestamp.now(), submissionTime = Time.Timestamp.now(),
@ -142,7 +141,6 @@ class InterpreterTest extends WordSpec with Matchers with TableDrivenPropertyChe
"compile" in { "compile" in {
machine = Speedy.Machine.fromExpr( machine = Speedy.Machine.fromExpr(
expr = list, expr = list,
checkSubmitterInMaintainers = true,
compiledPackages = PureCompiledPackages(Map.empty).right.get, compiledPackages = PureCompiledPackages(Map.empty).right.get,
scenario = false, scenario = false,
submissionTime = Time.Timestamp.now(), submissionTime = Time.Timestamp.now(),
@ -244,7 +242,6 @@ class InterpreterTest extends WordSpec with Matchers with TableDrivenPropertyChe
"succeeds" in { "succeeds" in {
val machine = Speedy.Machine.fromExpr( val machine = Speedy.Machine.fromExpr(
expr = EVal(ref), expr = EVal(ref),
checkSubmitterInMaintainers = true,
compiledPackages = pkgs1, compiledPackages = pkgs1,
scenario = false, scenario = false,
submissionTime = Time.Timestamp.now(), submissionTime = Time.Timestamp.now(),
@ -272,7 +269,6 @@ class InterpreterTest extends WordSpec with Matchers with TableDrivenPropertyChe
"crashes without definition" in { "crashes without definition" in {
val machine = Speedy.Machine.fromExpr( val machine = Speedy.Machine.fromExpr(
expr = EVal(ref), expr = EVal(ref),
checkSubmitterInMaintainers = true,
compiledPackages = pkgs1, compiledPackages = pkgs1,
scenario = false, scenario = false,
submissionTime = Time.Timestamp.now(), submissionTime = Time.Timestamp.now(),

View File

@ -1452,7 +1452,6 @@ object SBuiltinTest {
private def eval(e: Expr): Either[SError, SValue] = { private def eval(e: Expr): Either[SError, SValue] = {
val machine = Speedy.Machine.fromExpr( val machine = Speedy.Machine.fromExpr(
expr = e, expr = e,
checkSubmitterInMaintainers = true,
compiledPackages = compiledPackages, compiledPackages = compiledPackages,
scenario = false, scenario = false,
Time.Timestamp.now(), Time.Timestamp.now(),

View File

@ -253,7 +253,6 @@ object SpeedyTest {
private def eval(e: Expr, packages: PureCompiledPackages): Either[SError, SValue] = { private def eval(e: Expr, packages: PureCompiledPackages): Either[SError, SValue] = {
val machine = Speedy.Machine.fromExpr( val machine = Speedy.Machine.fromExpr(
expr = e, expr = e,
checkSubmitterInMaintainers = true,
compiledPackages = packages, compiledPackages = packages,
scenario = false, scenario = false,
submissionTime = Time.Timestamp.now(), submissionTime = Time.Timestamp.now(),

View File

@ -73,15 +73,5 @@ object LanguageVersion {
*/ */
val unstable = v1_dev 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
} }
} }

View File

@ -18,7 +18,6 @@ import com.daml.lf.speedy.SExpr.LfDefRef
import com.daml.lf.validation.Validation import com.daml.lf.validation.Validation
import com.daml.lf.testing.parser import com.daml.lf.testing.parser
import com.daml.lf.language.LanguageVersion import com.daml.lf.language.LanguageVersion
import com.daml.lf.transaction.VersionTimeline
import java.io.{File, PrintWriter, StringWriter} import java.io.{File, PrintWriter, StringWriter}
import java.nio.file.{Path, Paths} import java.nio.file.{Path, Paths}
import java.io.PrintStream import java.io.PrintStream
@ -169,11 +168,9 @@ object Repl {
private val build = Speedy.Machine private val build = Speedy.Machine
.newBuilder(PureCompiledPackages(packages).right.get, Time.Timestamp.MinValue, nextSeed()) .newBuilder(PureCompiledPackages(packages).right.get, Time.Timestamp.MinValue, nextSeed())
.fold(err => sys.error(err.toString), identity) .fold(err => sys.error(err.toString), identity)
def run(submissionVersion: LanguageVersion, expr: Expr) def run(expr: Expr)
: (Speedy.Machine, Either[(SError, Ledger.Ledger), (Double, Int, Ledger.Ledger)]) = { : (Speedy.Machine, Either[(SError, Ledger.Ledger), (Double, Int, Ledger.Ledger)]) =
val mach = build(VersionTimeline.checkSubmitterInMaintainers(submissionVersion), expr) (build(expr), ScenarioRunner(build(expr)).run())
(mach, ScenarioRunner(mach).run())
}
} }
case class Command(help: String, action: (State, Seq[String]) => State) case class Command(help: String, action: (State, Seq[String]) => State)
@ -402,13 +399,12 @@ object Repl {
case None => case None =>
println("Error: definition '" + id + "' not found. Try :list.") println("Error: definition '" + id + "' not found. Try :list.")
usage usage
case Some((lfVer, DValue(_, _, body, _))) => case Some(DValue(_, _, body, _)) =>
val expr = argExprs.foldLeft(body)((e, arg) => EApp(e, arg)) val expr = argExprs.foldLeft(body)((e, arg) => EApp(e, arg))
val machine = val machine =
Speedy.Machine.fromExpr( Speedy.Machine.fromExpr(
expr = expr, expr = expr,
checkSubmitterInMaintainers = VersionTimeline.checkSubmitterInMaintainers(lfVer),
compiledPackages = PureCompiledPackages(state.packages).right.get, compiledPackages = PureCompiledPackages(state.packages).right.get,
scenario = false, scenario = false,
submissionTime = Time.Timestamp.now(), 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 { idAndArgs match {
case id :: args => case id :: args =>
lookup(state, id) match { lookup(state, id) match {
case None => case None =>
println("Error: " + id + " not found.") println("Error: " + id + " not found.")
None None
case Some((lfVer, DValue(_, _, body, _))) => case Some(DValue(_, _, body, _)) =>
val argExprs = args.map(s => assertRight(parser.parseExpr(s))) 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(_) => case Some(_) =>
println("Error: " + id + " is not a value.") println("Error: " + id + " is not a value.")
None None
} }
case _ => case _ =>
usage(); None usage()
None
} }
def invokeScenario(state: State, idAndArgs: Seq[String]): (Boolean, State) = { def invokeScenario(state: State, idAndArgs: Seq[String]): (Boolean, State) = {
buildExpr(state, idAndArgs) buildExpr(state, idAndArgs)
.map { .map { expr =>
case (lfVer, expr) => val (machine, errOrLedger) =
val (machine, errOrLedger) = state.scenarioRunner.run(expr)
state.scenarioRunner.run(lfVer, expr) errOrLedger match {
errOrLedger match { case Left((err, ledger @ _)) =>
case Left((err, ledger @ _)) => println(prettyError(err, machine.ptx).render(128))
println(prettyError(err, machine.ptx).render(128)) (false, state)
(false, state) case Right((diff @ _, steps @ _, ledger)) =>
case Right((diff @ _, steps @ _, ledger)) => // NOTE(JM): cannot print this, output used in tests.
// NOTE(JM): cannot print this, output used in tests. //println(s"done in ${diff.formatted("%.2f")}ms, ${steps} steps")
//println(s"done in ${diff.formatted("%.2f")}ms, ${steps} steps") println(prettyLedger(ledger).render(128))
println(prettyLedger(ledger).render(128)) (true, state)
(true, state) }
}
} }
.getOrElse((false, state)) .getOrElse((false, state))
} }
@ -496,16 +492,16 @@ object Repl {
definition <- mod.definitions definition <- mod.definitions
(dfnName, dfn) = definition (dfnName, dfn) = definition
bodyScenario <- List(dfn).collect { case DValue(TScenario(_), _, body, _) => body } 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 failures = 0
var successes = 0 var successes = 0
val state = state0 val state = state0
var totalTime = 0.0 var totalTime = 0.0
var totalSteps = 0 var totalSteps = 0
allScenarios.foreach { allScenarios.foreach {
case (name, (lfVer, body)) => case (name, body) =>
print(name + ": ") print(name + ": ")
val (machine, errOrLedger) = state.scenarioRunner.run(lfVer, body) val (machine, errOrLedger) = state.scenarioRunner.run(body)
errOrLedger match { errOrLedger match {
case Left((err, ledger @ _)) => case Left((err, ledger @ _)) =>
println( println(
@ -545,7 +541,7 @@ object Repl {
LfDefRef(DefinitionRef(packageId, qualName)) 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]) = val (defRef, optPackageId): (String, Option[PackageId]) =
id.split("@").toList match { id.split("@").toList match {
case defRef :: packageId :: Nil => case defRef :: packageId :: Nil =>
@ -558,20 +554,18 @@ object Repl {
} }
optPackageId match { optPackageId match {
case Some(packageId) => case Some(packageId) =>
state.packages for {
.get(packageId) pkg <- state.packages.get(packageId)
.flatMap(pkg => pkg.modules.get(qualName.module)) module <- pkg.modules.get(qualName.module)
.flatMap(module => defn <- module.definitions.get(qualName.name)
module.definitions.get(qualName.name).map(defn => (module.languageVersion, defn))) } yield defn
case None => case None =>
state.packages.view state.packages.values.view
.flatMap { case (pkgId @ _, pkg) => pkg.modules.get(qualName.module).toList } .flatMap(pkg =>
.flatMap( for {
module => module <- pkg.modules.get(qualName.module).toList
module.definitions defn <- module.definitions.get(qualName.name).toList
.get(qualName.name) } yield defn)
.toList
.map(defn => (module.languageVersion, defn)))
.headOption .headOption
} }

View File

@ -17,7 +17,6 @@ class ScenarioRunnerTest extends AsyncWordSpec with Matchers with ScalaFutures {
val e = Ast.EScenario(ScenarioGetParty(Ast.EPrimLit(Ast.PLText(("foo-bar"))))) val e = Ast.EScenario(ScenarioGetParty(Ast.EPrimLit(Ast.PLText(("foo-bar")))))
val m = Speedy.Machine.fromExpr( val m = Speedy.Machine.fromExpr(
expr = e, expr = e,
checkSubmitterInMaintainers = true,
compiledPackages = PureCompiledPackages(Map.empty).right.get, compiledPackages = PureCompiledPackages(Map.empty).right.get,
scenario = true, scenario = true,
submissionTime = Time.Timestamp.now(), submissionTime = Time.Timestamp.now(),

View File

@ -180,9 +180,4 @@ object VersionTimeline {
.getOrElse(minimum) .getOrElse(minimum)
} }
def checkSubmitterInMaintainers(lfVers: LanguageVersion): Boolean = {
import Implicits._
!(lfVers precedes LanguageVersion.Features.checkSubmitterInMaintainersVersion)
}
} }

View File

@ -212,11 +212,10 @@ object Converter {
Array(SEVar(2), SEVar(1)))) Array(SEVar(2), SEVar(1))))
val machine = val machine =
Speedy.Machine.fromSExpr( Speedy.Machine.fromSExpr(
SEApp(SEValue(fun), Array(extractStruct)), sexpr = SEApp(SEValue(fun), Array(extractStruct)),
false, compiledPackages = compiledPackages,
compiledPackages, submissionTime = Time.Timestamp.now(),
Time.Timestamp.now(), seeding = InitialSeeding.NoSeed
InitialSeeding.NoSeed
) )
@tailrec @tailrec
def iter(): Either[String, (SValue, SValue)] = { def iter(): Either[String, (SValue, SValue)] = {

View File

@ -288,10 +288,9 @@ class Runner(
val machine = val machine =
Speedy.Machine.fromSExpr( Speedy.Machine.fromSExpr(
sexpr = script.expr, sexpr = script.expr,
checkSubmitterInMaintainers = false,
compiledPackages = extendedCompiledPackages, compiledPackages = extendedCompiledPackages,
submissionTime = Timestamp.now(), submissionTime = Timestamp.now(),
seeds = InitialSeeding.NoSeed, seeding = InitialSeeding.NoSeed,
) )
// Removing the early return only makes this harder to read. // Removing the early return only makes this harder to read.

View File

@ -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. 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. 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: .. _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``. 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. 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`` ``exerciseByKey``

View File

@ -27,7 +27,6 @@ object Tests {
"CommandSubmissionCompletionIT" -> (new CommandSubmissionCompletion(_)), "CommandSubmissionCompletionIT" -> (new CommandSubmissionCompletion(_)),
"CommandDeduplicationIT" -> (new CommandDeduplication(_)), "CommandDeduplicationIT" -> (new CommandDeduplication(_)),
"ContractKeysIT" -> (new ContractKeys(_)), "ContractKeysIT" -> (new ContractKeys(_)),
"ContractKeysSubmitterIsMaintainerIT" -> (new ContractKeysSubmitterIsMaintainer(_)),
"DivulgenceIT" -> (new Divulgence(_)), "DivulgenceIT" -> (new Divulgence(_)),
"HealthServiceIT" -> (new HealthService(_)), "HealthServiceIT" -> (new HealthService(_)),
"IdentityIT" -> (new Identity(_)), "IdentityIT" -> (new Identity(_)),

View File

@ -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",
)
}
}
}

View File

@ -8,9 +8,9 @@ import java.time.Instant
import com.daml.lf.CompiledPackages import com.daml.lf.CompiledPackages
import com.daml.lf.data._ import com.daml.lf.data._
import com.daml.lf.language.Ast.{DDataType, DTypeSyn, DValue, Definition} 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.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.ScenarioTransactionId
import com.daml.lf.types.{Ledger => L} import com.daml.lf.types.{Ledger => L}
import com.daml.platform.packages.InMemoryPackageStore import com.daml.platform.packages.InMemoryPackageStore
@ -112,12 +112,11 @@ object ScenarioLoader {
compiledPackages: CompiledPackages, compiledPackages: CompiledPackages,
scenario: String): (L.Ledger, Ref.DefinitionRef) = { scenario: String): (L.Ledger, Ref.DefinitionRef) = {
val scenarioQualName = getScenarioQualifiedName(packages, scenario) val scenarioQualName = getScenarioQualifiedName(packages, scenario)
val candidateScenarios: List[(Ref.DefinitionRef, LanguageVersion, Definition)] = val candidateScenarios: List[(Ref.DefinitionRef, Ast.Definition)] =
getCandidateScenarios(packages, scenarioQualName) getCandidateScenarios(packages, scenarioQualName)
val (scenarioRef, scenarioLfVers, scenarioDef) = val (scenarioRef, scenarioDef) = identifyScenario(packages, scenario, candidateScenarios)
identifyScenario(packages, scenario, candidateScenarios)
val scenarioExpr = getScenarioExpr(scenarioRef, scenarioDef) val scenarioExpr = getScenarioExpr(scenarioRef, scenarioDef)
val speedyMachine = getSpeedyMachine(scenarioLfVers, scenarioExpr, compiledPackages) val speedyMachine = getSpeedyMachine(scenarioExpr, compiledPackages)
val scenarioLedger = getScenarioLedger(scenarioRef, speedyMachine) val scenarioLedger = getScenarioLedger(scenarioRef, speedyMachine)
(scenarioLedger, scenarioRef) (scenarioLedger, scenarioRef)
} }
@ -133,13 +132,11 @@ object ScenarioLoader {
} }
private def getSpeedyMachine( private def getSpeedyMachine(
submissionVersion: LanguageVersion,
scenarioExpr: Ast.Expr, scenarioExpr: Ast.Expr,
compiledPackages: CompiledPackages): Speedy.Machine = compiledPackages: CompiledPackages): Speedy.Machine =
Speedy.Machine.newBuilder(compiledPackages, Time.Timestamp.now(), None) match { Speedy.Machine.newBuilder(compiledPackages, Time.Timestamp.now(), None) match {
case Left(err) => throw new RuntimeException(s"Could not build speedy machine: $err") case Left(err) => throw new RuntimeException(s"Could not build speedy machine: $err")
case Right(build) => case Right(build) => build(scenarioExpr)
build(VersionTimeline.checkSubmitterInMaintainers(submissionVersion), scenarioExpr)
} }
private def getScenarioExpr(scenarioRef: Ref.DefinitionRef, scenarioDef: Definition): Ast.Expr = { private def getScenarioExpr(scenarioRef: Ref.DefinitionRef, scenarioDef: Definition): Ast.Expr = {
@ -157,8 +154,8 @@ object ScenarioLoader {
private def identifyScenario( private def identifyScenario(
packages: InMemoryPackageStore, packages: InMemoryPackageStore,
scenario: String, scenario: String,
candidateScenarios: List[(Ref.DefinitionRef, LanguageVersion, Definition)]) candidateScenarios: List[(Ref.DefinitionRef, Definition)])
: (Ref.DefinitionRef, LanguageVersion, Definition) = { : (Ref.DefinitionRef, Definition) = {
candidateScenarios match { candidateScenarios match {
case Nil => case Nil =>
throw new RuntimeException( throw new RuntimeException(
@ -173,7 +170,7 @@ object ScenarioLoader {
private def getCandidateScenarios( private def getCandidateScenarios(
packages: InMemoryPackageStore, packages: InMemoryPackageStore,
scenarioQualName: Ref.QualifiedName scenarioQualName: Ref.QualifiedName
): List[(Ref.Identifier, LanguageVersion, Definition)] = { ): List[(Ref.Identifier, Ast.Definition)] = {
packages packages
.listLfPackagesSync() .listLfPackagesSync()
.flatMap { .flatMap {
@ -183,11 +180,7 @@ object ScenarioLoader {
.getOrElse(sys.error(s"Listed package $packageId not found")) .getOrElse(sys.error(s"Listed package $packageId not found"))
pkg.lookupIdentifier(scenarioQualName) match { pkg.lookupIdentifier(scenarioQualName) match {
case Right(x) => case Right(x) =>
List( List((Ref.Identifier(packageId, scenarioQualName) -> x))
(
Ref.Identifier(packageId, scenarioQualName),
pkg.modules(scenarioQualName.module).languageVersion,
x))
case Left(_) => List() case Left(_) => List()
} }
}(breakOut) }(breakOut)

View File

@ -138,10 +138,9 @@ object Trigger extends StrictLogging {
) )
val machine = Speedy.Machine.fromSExpr( val machine = Speedy.Machine.fromSExpr(
sexpr = heartbeat, sexpr = heartbeat,
checkSubmitterInMaintainers = false,
compiledPackages = compiledPackages, compiledPackages = compiledPackages,
submissionTime = Timestamp.now(), submissionTime = Timestamp.now(),
seeds = InitialSeeding.NoSeed, seeding = InitialSeeding.NoSeed,
) )
Machine.stepToValue(machine) Machine.stepToValue(machine)
machine.toSValue match { machine.toSValue match {
@ -163,10 +162,9 @@ object Trigger extends StrictLogging {
val machine = val machine =
Speedy.Machine.fromSExpr( Speedy.Machine.fromSExpr(
sexpr = registeredTemplates, sexpr = registeredTemplates,
checkSubmitterInMaintainers = false,
compiledPackages = compiledPackages, compiledPackages = compiledPackages,
submissionTime = Timestamp.now(), submissionTime = Timestamp.now(),
seeds = InitialSeeding.NoSeed, seeding = InitialSeeding.NoSeed,
) )
Machine.stepToValue(machine) Machine.stepToValue(machine)
machine.toSValue match { machine.toSValue match {
@ -318,10 +316,9 @@ class Runner(
var machine = Speedy.Machine.fromSExpr( var machine = Speedy.Machine.fromSExpr(
sexpr = null, sexpr = null,
checkSubmitterInMaintainers = false,
compiledPackages = compiledPackages, compiledPackages = compiledPackages,
submissionTime = Timestamp.now(), submissionTime = Timestamp.now(),
seeds = InitialSeeding.NoSeed seeding = InitialSeeding.NoSeed
) )
val createdExpr: SExpr = SEValue(converter.fromACS(acs) match { val createdExpr: SExpr = SEValue(converter.fromACS(acs) match {
case Left(err) => throw new ConverterException(err) case Left(err) => throw new ConverterException(err)