From c07b1175d7bed99786e7a54f8dad61ef5a252454 Mon Sep 17 00:00:00 2001 From: Remy Haemmerle Date: Thu, 13 Aug 2026 14:52:56 +0200 Subject: [PATCH] Convert legacy exceptions to FailureStatus in the script runner instead of the engine Always invoke the engine with `convertLegacyExceptions = false` and perform the legacy exception -> FailureStatus conversion in the script Runner. The conversion logic (makeFailureStatus and convertLegacyException) is consolidated on Runner and reused from ScriptF.Throw, removing the duplicated implementation. Also make convertLegacyExceptions an explicit parameter (no default) and drop the now-unused Free.getResult. --- .../daml/lf/engine/free/Free.scala | 47 +++----- .../daml/lf/engine/script/v2/Runner.scala | 101 ++++++++++++++---- .../daml/lf/engine/script/v2/ScriptF.scala | 51 ++------- 3 files changed, 101 insertions(+), 98 deletions(-) diff --git a/sdk/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/free/Free.scala b/sdk/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/free/Free.scala index e3ff58c89075..1dbe145e7a29 100644 --- a/sdk/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/free/Free.scala +++ b/sdk/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/free/Free.scala @@ -7,12 +7,7 @@ package free import data.{ImmArray, Ref} import speedy.{MachineLogger, Pretty, SError} -import ScriptEngine.{ - ExtendedValue, - ExtendedValueClosureBlob, - ExtendedValueComputationMode, - runExtendedValueComputation, -} +import ScriptEngine.{ExtendedValue, ExtendedValueClosureBlob, ExtendedValueComputationMode} import value.Value._ import scalaz.std.either._ import scalaz.std.vector._ @@ -116,31 +111,16 @@ private[lf] object Free { def toErrOr: ErrOr[X] = e.left.map(ConversionError) } - def getResult( - freeClosure: ExtendedValueClosureBlob, // LF Type: () -> Free ScriptF (a, ()) - compiledPackages: CompiledPackages, - machineLogger: MachineLogger, - convertLegacyExceptions: Boolean, - ): Result[ExtendedValue, Question, ExtendedValue] = - new Runner( - freeClosure, - compiledPackages: CompiledPackages, - machineLogger: MachineLogger, - convertLegacyExceptions, - ).getResult() - def getResultF( freeClosure: ExtendedValueClosureBlob, // LF Type: () -> Free ScriptF (a, ()) compiledPackages: CompiledPackages, machineLogger: MachineLogger, - convertLegacyExceptions: Boolean, cancelled: () => Option[RuntimeException], )(implicit ec: ExecutionContext): Future[Result[ExtendedValue, Question, ExtendedValue]] = new Runner( freeClosure: ExtendedValueClosureBlob, compiledPackages: CompiledPackages, machineLogger: MachineLogger, - convertLegacyExceptions, cancelled, ).getResultF() @@ -148,7 +128,6 @@ private[lf] object Free { freeClosure: ExtendedValueClosureBlob, // LF Type: () -> Free ScriptF (a, ()) compiledPackages: CompiledPackages, machineLogger: MachineLogger, - convertLegacyExceptions: Boolean, cancelled: () => Option[RuntimeException] = () => None, ) { @@ -168,17 +147,19 @@ private[lf] object Free { closure: ExtendedValueClosureBlob, args: List[ExtendedValue], ): Result.NoQuestion[ExtendedValue] = - runExtendedValueComputation( - computationMode = ExtendedValueComputationMode.ByClosure(closure, args), - cancelled = cancelled, - compiledPackages = compiledPackages, - iterationsBetweenInterruptions = 100000, - logger = machineLogger, - convertLegacyExceptions = convertLegacyExceptions, - ).fold( - err => Result.failed(err.fold(identity, free.InterpretationError(_))), - Result.successful(_), - ) + ScriptEngine + .runExtendedValueComputation( + computationMode = ExtendedValueComputationMode.ByClosure(closure, args), + cancelled = cancelled, + compiledPackages = compiledPackages, + iterationsBetweenInterruptions = 100000, + logger = machineLogger, + convertLegacyExceptions = false, + ) + .fold( + err => Result.failed(err.fold(identity, free.InterpretationError(_))), + Result.successful(_), + ) def parseQuestion( v: ExtendedValue diff --git a/sdk/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/v2/Runner.scala b/sdk/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/v2/Runner.scala index d9d9b9c5f703..f9f410c6c63d 100644 --- a/sdk/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/v2/Runner.scala +++ b/sdk/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/v2/Runner.scala @@ -6,30 +6,33 @@ package engine package script package v2 -import org.apache.pekko.stream.Materializer import com.daml.grpc.adapter.ExecutionSequencerFactory +import com.digitalasset.canton.logging.NamedLoggerFactory import com.digitalasset.canton.tracing.TraceContext -import com.digitalasset.daml.lf.data.ImmArray +import com.digitalasset.daml.lf.data.{ImmArray, Ref} +import com.digitalasset.daml.lf.engine.ScriptEngine.{ + ExtendedValue, + ExtendedValueClosureBlob, + ExtendedValueComputationMode, + runExtendedValueComputation, +} import com.digitalasset.daml.lf.engine.free.Free import com.digitalasset.daml.lf.engine.script.Runner.IdeLedgerContext import com.digitalasset.daml.lf.engine.script.ledgerinteraction.{ ScriptLedgerClient => UnversionedScriptLedgerClient } import com.digitalasset.daml.lf.engine.script.v2.ledgerinteraction.ScriptLedgerClient +import com.digitalasset.daml.lf.language.Ast +import com.digitalasset.daml.lf.interpretation.{Error => IE} import com.digitalasset.daml.lf.script.IdeLedger -import com.digitalasset.daml.lf.engine.ScriptEngine.{ - ExtendedValue, - ExtendedValueClosureBlob, - ExtendedValueComputationMode, - runExtendedValueComputation, -} -import com.digitalasset.daml.lf.speedy.MachineLogger -import com.digitalasset.daml.lf.transaction.{NextGenContractStateMachine => ContractStateMachine} -import com.digitalasset.daml.lf.value.Value._ import com.digitalasset.daml.lf.script.converter.ConverterException -import com.digitalasset.canton.logging.NamedLoggerFactory +import com.digitalasset.daml.lf.speedy.{MachineLogger, SError} +import com.digitalasset.daml.lf.transaction.{NextGenContractStateMachine => ContractStateMachine} +import com.digitalasset.daml.lf.value.Value +import org.apache.pekko.stream.Materializer import scala.concurrent.{ExecutionContext, Future} +import scala.util.{Failure, Success} private[lf] class Runner( unversionedRunner: script.Runner, @@ -99,14 +102,14 @@ private[lf] class Runner( } // Takes a Script X and runs it - def runResolved(scriptValue: ExtendedValue, convertLegacyExceptions: Boolean = true)(implicit + def runResolved(scriptValue: ExtendedValue, convertLegacyExceptions: Boolean)(implicit ec: ExecutionContext, esf: ExecutionSequencerFactory, mat: Materializer, - ): Future[ExtendedValue] = + ): Future[ExtendedValue] = handleLegacyExceptions(convertLegacyExceptions)( for { freeClosure <- scriptValue match { - case ValueRecord(_, ImmArray((_, freeClosure: ExtendedValueClosureBlob), _)) => + case Value.ValueRecord(_, ImmArray((_, freeClosure: ExtendedValueClosureBlob), _)) => Future.successful(freeClosure) case a => Future.failed(new RuntimeException(s"Expected Script a but got $a")) } @@ -115,7 +118,6 @@ private[lf] class Runner( freeClosure, unversionedRunner.extendedCompiledPackages, machineLogger, - convertLegacyExceptions, canceled, ) result <- @@ -125,21 +127,21 @@ private[lf] class Runner( .recover { case err: RuntimeException => Result.failed(err) } ) } yield result + ) // Takes something that resolves/computes to a Script X, then runs the script - def run(comp: ExtendedValueComputationMode, convertLegacyExceptions: Boolean = true)(implicit + def run(comp: ExtendedValueComputationMode, convertLegacyExceptions: Boolean)(implicit ec: ExecutionContext, esf: ExecutionSequencerFactory, mat: Materializer, ): Future[ExtendedValue] = for { - scriptValue <- runComputation(comp, convertLegacyExceptions) + scriptValue <- handleLegacyExceptions(convertLegacyExceptions)(runComputation(comp)) result <- runResolved(scriptValue, convertLegacyExceptions) } yield result def runComputation( - comp: ExtendedValueComputationMode, - convertLegacyExceptions: Boolean = true, + comp: ExtendedValueComputationMode )(implicit ec: ExecutionContext): Future[ExtendedValue] = Future { runExtendedValueComputation( @@ -148,7 +150,7 @@ private[lf] class Runner( unversionedRunner.extendedCompiledPackages, machineLogger, iterationsBetweenInterruptions = 100000, - convertLegacyExceptions, + convertLegacyExceptions = false, ).fold( err => throw err.fold(identity, free.InterpretationError(_)), identity, @@ -173,9 +175,12 @@ private[lf] class Runner( ( unversionedRunner.script match { case ScriptAction.NoParam(id, _) => - run(ExtendedValueComputationMode.ByIdentifier(id)) + run(ExtendedValueComputationMode.ByIdentifier(id), convertLegacyExceptions = true) case ScriptAction.Param(id, paramType, Some(param), _) => - run(ExtendedValueComputationMode.ByIdentifier(id, Some(List(param)))) + run( + ExtendedValueComputationMode.ByIdentifier(id, Some(List(param))), + convertLegacyExceptions = true, + ) case _ => Future.failed( new RuntimeException("impossible") @@ -183,4 +188,54 @@ private[lf] class Runner( }, ideLedgerContext, ) + + def makeFailureStatus(excpType: Ref.TypeConId, msg: String) = + free.InterpretationError( + SError.SErrorDamlException( + IE.FailureStatus( + "UNHANDLED_EXCEPTION/" + excpType.qualifiedName.toString, + Ast.FCInvalidGivenCurrentSystemStateOther.cantonCategoryId, + msg, + Map(), + ) + ) + ) + + def handleLegacyExceptions[X]( + convertLegacyExceptions: Boolean + )(x: Future[X])(implicit ec: ExecutionContext) = + x.recoverWith { + case free.InterpretationError( + SError.SErrorDamlException(IE.UnhandledException(Ast.TTyCon(excpType), value)) + ) if convertLegacyExceptions => + convertLegacyException(excpType, value) + } + + def convertLegacyException(excpType: Ref.TypeConId, value: ExtendedValue)(implicit + ec: ExecutionContext + ): Future[Nothing] = { + runComputation( + ExtendedValueComputationMode.ByExceptionMessage(excpType, value) + ).transform { result => + val error = result match { + case Success(Value.ValueText(msg)) => + makeFailureStatus(excpType, msg) + case Success(_) => + new RuntimeException(s"Message computation for exception $excpType did not give Text") + case Failure( + free.InterpretationError( + SError.SErrorDamlException( + IE.UnhandledException(Ast.TTyCon(messageExceptionName), _) + ) + ) + ) => + makeFailureStatus( + excpType, + s"", + ) + case Failure(error) => error + } + Failure(error) + } + } } diff --git a/sdk/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/v2/ScriptF.scala b/sdk/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/v2/ScriptF.scala index fda44ed36b9b..71bdd473ba30 100644 --- a/sdk/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/v2/ScriptF.scala +++ b/sdk/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/v2/ScriptF.scala @@ -131,19 +131,10 @@ object ScriptF { mat: Materializer, esf: ExecutionSequencerFactory, ): Future[ExtendedValue] = { - def makeFailureStatus(name: Identifier, msg: String) = - Future.failed( - free.InterpretationError( - SError.SErrorDamlException( - IE.FailureStatus( - "UNHANDLED_EXCEPTION/" + name.qualifiedName.toString, - Ast.FCInvalidGivenCurrentSystemStateOther.cantonCategoryId, - msg, - Map(), - ) - ) - ) - ) + + def raiseFailureStatus(excpType: TypeConId, msg: String): Future[Nothing] = + Future.failed(runner.makeFailureStatus(excpType, msg)) + def userManagementDef(name: String) = env.scriptIds.damlScriptModule("Daml.Script.Internal.Questions.UserManagement", name) val invalidUserId = userManagementDef("InvalidUserId") @@ -159,7 +150,7 @@ object ScriptF { ), true, ) => - makeFailureStatus(invalidUserId, msg) + raiseFailureStatus(invalidUserId, msg) case ( ExtendedValueAny( _, @@ -170,7 +161,7 @@ object ScriptF { ), true, ) => - makeFailureStatus(userAlreadyExists, "User already exists: " + userId) + raiseFailureStatus(userAlreadyExists, "User already exists: " + userId) case ( ExtendedValueAny( _, @@ -181,35 +172,11 @@ object ScriptF { ), true, ) => - makeFailureStatus(userNotFound, "User not found: " + userId) - case (ExtendedValueAny(Ast.TTyCon(name), v), true) => + raiseFailureStatus(userNotFound, "User not found: " + userId) + case (ExtendedValueAny(Ast.TTyCon(excpType), value), true) => // Since we cannot call `SBThrow` from the engine, we must re-implement the legacy exception to FailureStatus conversion logic here // This involves calculating the exception message by calling the engine again. - runner - .runComputation( - ExtendedValueComputationMode - .ByExceptionMessage(name, v), - false, - ) - .transformWith { - case Success(ValueText(message)) => makeFailureStatus(name, message) - case Success(_) => - Future.failed( - new RuntimeException(s"Message computation for exception $name did not give Text") - ) - case Failure( - free.InterpretationError( - SError.SErrorDamlException( - IE.UnhandledException(Ast.TTyCon(messageExceptionName), _) - ) - ) - ) => - makeFailureStatus( - name, - s"", - ) - case Failure(e) => Future.failed(e) - } + runner.convertLegacyException(excpType, value) case (ExtendedValueAny(ty, _), true) => Future.failed( new RuntimeException(