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
Speedy.Machine
.build(
checkSubmitterInMaintainers = false,
sexpr = defn,
compiledPackages = PureCompiledPackages(allPackages, defns),
submissionTime,

View File

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

View File

@ -98,7 +98,7 @@ final class ConcurrentCompiledPackages extends MutableCompiledPackages {
}
}
ResultDone(())
ResultDone.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.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"))

View File

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

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
.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

View File

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

View File

@ -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..

View File

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

View File

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

View File

@ -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.

View File

@ -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(),

View File

@ -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(),

View File

@ -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(),

View File

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

View File

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

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 m = Speedy.Machine.fromExpr(
expr = e,
checkSubmitterInMaintainers = true,
compiledPackages = PureCompiledPackages(Map.empty).right.get,
scenario = true,
submissionTime = Time.Timestamp.now(),

View File

@ -180,9 +180,4 @@ object VersionTimeline {
.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))))
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)] = {

View File

@ -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.

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.
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``

View File

@ -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(_)),

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.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)

View File

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