diff --git a/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/Converter.scala b/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/Converter.scala index c966ec290dad..9d797edf0676 100644 --- a/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/Converter.scala +++ b/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/Converter.scala @@ -8,7 +8,7 @@ package script import com.daml.ledger.api.domain.PartyDetails import com.daml.ledger.api.v1.transaction.{TransactionTree, TreeEvent} import com.daml.ledger.api.v1.value -import com.daml.ledger.api.validation.ValueValidator +import com.daml.ledger.api.validation.NoLoggingValueValidator import com.daml.lf.data.Ref._ import com.daml.lf.data._ import com.daml.lf.engine.script.ledgerinteraction.ScriptLedgerClient @@ -699,7 +699,7 @@ object Converter { tplId <- Converter.fromApiIdentifier(created.getTemplateId) cid <- ContractId.fromString(created.contractId) arg <- - ValueValidator + NoLoggingValueValidator .validateRecord(created.getCreateArguments) .left .map(err => s"Failed to validate create argument: $err") @@ -713,7 +713,7 @@ object Converter { tplId <- Converter.fromApiIdentifier(exercised.getTemplateId) cid <- ContractId.fromString(exercised.contractId) choice <- ChoiceName.fromString(exercised.choice) - choiceArg <- ValueValidator + choiceArg <- NoLoggingValueValidator .validateValue(exercised.getChoiceArgument) .left .map(err => s"Failed to validate exercise argument: $err") diff --git a/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/ledgerinteraction/GrpcLedgerClient.scala b/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/ledgerinteraction/GrpcLedgerClient.scala index 3c4ed821398a..608eb71bbb9a 100644 --- a/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/ledgerinteraction/GrpcLedgerClient.scala +++ b/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/ledgerinteraction/GrpcLedgerClient.scala @@ -17,7 +17,7 @@ import com.daml.ledger.api.v1.testing.time_service.TimeServiceGrpc.TimeServiceSt import com.daml.ledger.api.v1.testing.time_service.{GetTimeRequest, SetTimeRequest, TimeServiceGrpc} import com.daml.ledger.api.v1.transaction.TreeEvent import com.daml.ledger.api.v1.transaction_filter.{Filters, InclusiveFilters, TransactionFilter} -import com.daml.ledger.api.validation.ValueValidator +import com.daml.ledger.api.validation.NoLoggingValueValidator import com.daml.ledger.client.LedgerClient import com.daml.lf.command import com.daml.lf.data.Ref._ @@ -73,12 +73,13 @@ class GrpcLedgerClient(val grpcClient: LedgerClient, val applicationId: Applicat acsResponses.map(acsPages => acsPages.toVector.flatMap(page => page.activeContracts.toVector.map(createdEvent => { - val argument = ValueValidator.validateRecord(createdEvent.getCreateArguments) match { - case Left(err) => throw new ConverterException(err.toString) - case Right(argument) => argument - } + val argument = + NoLoggingValueValidator.validateRecord(createdEvent.getCreateArguments) match { + case Left(err) => throw new ConverterException(err.toString) + case Right(argument) => argument + } val key: Option[Value] = createdEvent.contractKey.map { key => - ValueValidator.validateValue(key) match { + NoLoggingValueValidator.validateValue(key) match { case Left(err) => throw new ConverterException(err.toString) case Right(argument) => argument } @@ -315,7 +316,10 @@ class GrpcLedgerClient(val grpcClient: LedgerClient, val applicationId: Applicat } yield ScriptLedgerClient.CreateResult(cid) case TreeEvent(TreeEvent.Kind.Exercised(exercised)) => for { - result <- ValueValidator.validateValue(exercised.getExerciseResult).left.map(_.toString) + result <- NoLoggingValueValidator + .validateValue(exercised.getExerciseResult) + .left + .map(_.toString) templateId <- Converter.fromApiIdentifier(exercised.getTemplateId) choice <- ChoiceName.fromString(exercised.choice) } yield ScriptLedgerClient.ExerciseResult(templateId, choice, result) diff --git a/ledger-service/http-json/src/main/scala/com/digitalasset/http/util/ApiValueToLfValueConverter.scala b/ledger-service/http-json/src/main/scala/com/digitalasset/http/util/ApiValueToLfValueConverter.scala index 93657f6a0000..086c90b12142 100644 --- a/ledger-service/http-json/src/main/scala/com/digitalasset/http/util/ApiValueToLfValueConverter.scala +++ b/ledger-service/http-json/src/main/scala/com/digitalasset/http/util/ApiValueToLfValueConverter.scala @@ -4,7 +4,7 @@ package com.daml.http.util import com.daml.lf -import com.daml.ledger.api.validation.ValueValidator +import com.daml.ledger.api.validation.NoLoggingValueValidator import com.daml.ledger.api.{v1 => lav1} import io.grpc.StatusRuntimeException import scalaz.{Show, \/} @@ -23,6 +23,6 @@ object ApiValueToLfValueConverter { lav1.value.Value => Error \/ lf.value.Value def apiValueToLfValue: ApiValueToLfValue = { a: lav1.value.Value => - \/.fromEither(ValueValidator.validateValue(a)).leftMap(e => Error(e)) + \/.fromEither(NoLoggingValueValidator.validateValue(a)).leftMap(e => Error(e)) } } diff --git a/ledger/error/src/main/scala/com/daml/error/BaseError.scala b/ledger/error/src/main/scala/com/daml/error/BaseError.scala index 1a60528ead5a..6cdfbe9e295c 100644 --- a/ledger/error/src/main/scala/com/daml/error/BaseError.scala +++ b/ledger/error/src/main/scala/com/daml/error/BaseError.scala @@ -47,17 +47,17 @@ trait BaseError extends LocationMixin { def resources: Seq[(ErrorResource, String)] = Seq() def logWithContext(extra: Map[String, String] = Map())(implicit - errorCodeLoggingContext: ErrorCodeLoggingContext + contextualizedErrorLogger: ContextualizedErrorLogger ): Unit = - errorCodeLoggingContext.logError(this, extra) + contextualizedErrorLogger.logError(this, extra) def asGrpcStatusFromContext(implicit - errorCodeLoggingContext: ErrorCodeLoggingContext + contextualizedErrorLogger: ContextualizedErrorLogger ): Status = code.asGrpcStatus(this) def asGrpcErrorFromContext(implicit - errorCodeLoggingContext: ErrorCodeLoggingContext + contextualizedErrorLogger: ContextualizedErrorLogger ): StatusRuntimeException = code.asGrpcError(this) @@ -110,7 +110,7 @@ object BaseError { extends BaseError { /** The logging context obtained when we created the error, usually passed in as implicit */ - def loggingContext: ErrorCodeLoggingContext + def loggingContext: ContextualizedErrorLogger /** Flag to control if an error should be logged at creation * diff --git a/ledger/error/src/main/scala/com/daml/error/ErrorCodeLoggingContext.scala b/ledger/error/src/main/scala/com/daml/error/ContextualizedErrorLogger.scala similarity index 80% rename from ledger/error/src/main/scala/com/daml/error/ErrorCodeLoggingContext.scala rename to ledger/error/src/main/scala/com/daml/error/ContextualizedErrorLogger.scala index cdc4619f6022..f357533380db 100644 --- a/ledger/error/src/main/scala/com/daml/error/ErrorCodeLoggingContext.scala +++ b/ledger/error/src/main/scala/com/daml/error/ContextualizedErrorLogger.scala @@ -4,34 +4,29 @@ package com.daml.error import com.daml.error.ErrorCode.formatContextAsString -import com.daml.error.ErrorCodeLoggingContext.loggingValueToString +import com.daml.error.ContextualizedErrorLogger.loggingValueToString import com.daml.logging.entries.LoggingValue import com.daml.logging.{ContextualizedLogger, LoggingContext} import org.slf4j.event.Level /** Abstracts away from the logging tech stack used. */ -trait ErrorCodeLoggingContext extends CanLog { +trait ContextualizedErrorLogger { def properties: Map[String, String] def correlationId: Option[String] def logError(err: BaseError, extra: Map[String, String]): Unit -} - -trait CanLog { def info(message: String): Unit def info(message: String, throwable: Throwable): Unit - def warn(message: String): Unit def warn(message: String, throwable: Throwable): Unit - def error(message: String): Unit def error(message: String, throwable: Throwable): Unit } -class DamlErrorCodeLoggingContext( +class DamlContextualizedErrorLogger( logger: ContextualizedLogger, loggingContext: LoggingContext, val correlationId: Option[String], -) extends ErrorCodeLoggingContext { +) extends ContextualizedErrorLogger { override def properties: Map[String, String] = loggingContext.entries.contents.view.map { case (key, value) => key -> loggingValueToString(value) @@ -70,7 +65,7 @@ class DamlErrorCodeLoggingContext( } } -object ErrorCodeLoggingContext { +object ContextualizedErrorLogger { // TODO error-codes: Extract this function into `LoggingContext` companion (and test) private[error] val loggingValueToString: LoggingValue => String = { case LoggingValue.Empty => "" @@ -88,3 +83,15 @@ object ErrorCodeLoggingContext { case LoggingValue.OfJson(json) => json.toString() } } + +object NoLogging extends ContextualizedErrorLogger { + override def properties: Map[String, String] = Map.empty + override def correlationId: Option[String] = None + override def logError(err: BaseError, extra: Map[String, String]): Unit = () + override def info(message: String): Unit = () + override def info(message: String, throwable: Throwable): Unit = () + override def warn(message: String): Unit = () + override def warn(message: String, throwable: Throwable): Unit = () + override def error(message: String): Unit = () + override def error(message: String, throwable: Throwable): Unit = () +} diff --git a/ledger/error/src/main/scala/com/daml/error/ErrorCode.scala b/ledger/error/src/main/scala/com/daml/error/ErrorCode.scala index 400f986da710..c7cb532664da 100644 --- a/ledger/error/src/main/scala/com/daml/error/ErrorCode.scala +++ b/ledger/error/src/main/scala/com/daml/error/ErrorCode.scala @@ -60,7 +60,7 @@ abstract class ErrorCode(val id: String, val category: ErrorCategory)(implicit s"${codeStr(correlationId)}: ${ErrorCode.truncateCause(cause)}" def asGrpcStatus(err: BaseError)(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ): Status = { val ErrorCode.StatusInfo(codeGrpc, message, contextMap, correlationId) = getStatusInfo(err) @@ -131,7 +131,7 @@ abstract class ErrorCode(val id: String, val category: ErrorCategory)(implicit } def asGrpcError(err: BaseError)(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ): StatusRuntimeException = { val status = asGrpcStatus(err) // Builder methods for metadata are not exposed, so going route via creating an exception @@ -152,7 +152,7 @@ abstract class ErrorCode(val id: String, val category: ErrorCategory)(implicit def getStatusInfo( err: BaseError - )(implicit loggingContext: ErrorCodeLoggingContext): ErrorCode.StatusInfo = { + )(implicit loggingContext: ContextualizedErrorLogger): ErrorCode.StatusInfo = { val correlationId = loggingContext.correlationId val message = if (code.category.securitySensitive) @@ -171,7 +171,7 @@ abstract class ErrorCode(val id: String, val category: ErrorCategory)(implicit private[error] def getTruncatedContext( err: BaseError - )(implicit loggingContext: ErrorCodeLoggingContext): Map[String, String] = { + )(implicit loggingContext: ContextualizedErrorLogger): Map[String, String] = { val raw = (err.context ++ loggingContext.properties).toSeq.filter(_._2.nonEmpty).sortBy(_._2.length) val maxPerEntry = ErrorCode.MaxContentBytes / Math.max(1, raw.size) diff --git a/ledger/participant-integration-api/src/main/scala/platform/apiserver/ErrorCodesVersionSwitcher.scala b/ledger/error/src/main/scala/com/daml/error/ErrorCodesVersionSwitcher.scala similarity index 92% rename from ledger/participant-integration-api/src/main/scala/platform/apiserver/ErrorCodesVersionSwitcher.scala rename to ledger/error/src/main/scala/com/daml/error/ErrorCodesVersionSwitcher.scala index 07f2689f0613..3c0621e45429 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/apiserver/ErrorCodesVersionSwitcher.scala +++ b/ledger/error/src/main/scala/com/daml/error/ErrorCodesVersionSwitcher.scala @@ -1,9 +1,8 @@ // Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -package com.daml.platform.apiserver +package com.daml.error -import com.daml.error.ValueSwitch import io.grpc.StatusRuntimeException import scala.concurrent.Future diff --git a/ledger/error/src/main/scala/com/daml/error/definitions/LedgerApiErrors.scala b/ledger/error/src/main/scala/com/daml/error/definitions/LedgerApiErrors.scala index 4d389a0b2f39..25fe24578d77 100644 --- a/ledger/error/src/main/scala/com/daml/error/definitions/LedgerApiErrors.scala +++ b/ledger/error/src/main/scala/com/daml/error/definitions/LedgerApiErrors.scala @@ -17,6 +17,55 @@ import com.daml.lf.interpretation.{Error => LfInterpretationError} object LedgerApiErrors extends LedgerApiErrorGroup { + @Explanation("This rejection is given when the requested service has already been closed.") + @Resolution("Contact the participant operator.") + object ServiceNotRunning + extends ErrorCode( + id = "SERVICE_NOT_RUNNING", + // TODO error codes: Re-check this error category + ErrorCategory.TransientServerFailure, + ) { + case class Reject()(implicit + loggingContext: ContextualizedErrorLogger + ) extends LoggingTransactionErrorImpl( + cause = "Service has been shut down." + ) + } + + object ReadErrors extends ErrorGroup() { + @Explanation("This rejection is given when a read request tries to access pruned data.") + @Resolution("Use an offset that is after the pruning offset.") + object ParticipantPrunedDataAccessed + extends ErrorCode( + id = "PARTICIPANT_PRUNED_DATA_ACCESSED", + // TODO error codes: Rename error category to cover this scenario + // where the data accessed is before the allowed pruning begin + ErrorCategory.InvalidGivenCurrentSystemStateSeekAfterEnd, + ) { + case class Reject(message: String)(implicit + loggingContext: ContextualizedErrorLogger + ) extends LoggingTransactionErrorImpl( + cause = message + ) + } + + @Explanation( + "This rejection is given when a read request uses an offset beyond the current ledger end." + ) + @Resolution("Use an offset that is before the ledger end.") + object RequestedOffsetAfterLedgerEnd + extends ErrorCode( + id = "REQUESTED_OFFSET_OUT_OF_RANGE", + ErrorCategory.InvalidGivenCurrentSystemStateSeekAfterEnd, + ) { + case class Reject(message: String)(implicit + loggingContext: ContextualizedErrorLogger + ) extends LoggingTransactionErrorImpl( + cause = message + ) + } + } + // the authorization checks are here only for documentation purpose. // TODO error codes: Extract these errors in ledger-api-auth and use them in [[com.daml.ledger.api.auth.Authorizer]] // (i.e. in lieu of ErrorFactories.permissionDenied() and ErrorFactories.unauthenticated()) @@ -32,7 +81,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { ErrorCategory.AuthInterceptorInvalidAuthenticationCredentials, ) { case class Reject()(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = "The command is missing a JWT token" ) @@ -48,7 +97,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { object PermissionDenied extends ErrorCode(id = "PERMISSION_DENIED", ErrorCategory.InsufficientPermission) { case class Reject()(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = "The provided JWT token is not sufficient to authorize the intended command" ) @@ -67,9 +116,10 @@ object LedgerApiErrors extends LedgerApiErrorGroup { ErrorCategory.InvalidGivenCurrentSystemStateResourceMissing, ) { case class Reject(override val cause: String)(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( - cause = cause + cause = cause, + definiteAnswer = true, ) } @@ -78,11 +128,11 @@ object LedgerApiErrors extends LedgerApiErrorGroup { ) @Resolution("Inspect the reason given and correct your application.") object MissingField - extends ErrorCode(id = "MISSING_FIELDS", ErrorCategory.InvalidIndependentOfSystemState) { - case class Reject(_reason: String)(implicit - loggingContext: ErrorCodeLoggingContext + extends ErrorCode(id = "MISSING_FIELD", ErrorCategory.InvalidIndependentOfSystemState) { + case class Reject(missingField: String)(implicit + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( - cause = s"The submitted command is missing a mandatory field: ${_reason}" + cause = s"The submitted command is missing a mandatory field: $missingField" ) } @@ -93,7 +143,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { object InvalidArgument extends ErrorCode(id = "INVALID_ARGUMENT", ErrorCategory.InvalidIndependentOfSystemState) { case class Reject(_reason: String)(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = s"The submitted command has invalid arguments: ${_reason}" ) @@ -106,7 +156,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { object InvalidField extends ErrorCode(id = "INVALID_FIELD", ErrorCategory.InvalidIndependentOfSystemState) { case class Reject(_reason: String)(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = s"The submitted command has a field with invalid value: ${_reason}" ) @@ -125,7 +175,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { ) { case class Reject()(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = "A command with the given command id has already been successfully processed" ) @@ -145,7 +195,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { ) { case class Reject(_reason: String)(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = s"The participant failed to determine the max ledger time for this command: ${_reason}" @@ -180,7 +230,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { languageVersion: language.LanguageVersion, allowedLanguageVersions: VersionRange[language.LanguageVersion], )(implicit - val loggingContext: ErrorCodeLoggingContext + val loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = buildCause(packageId, languageVersion, allowedLanguageVersions) ) @@ -199,7 +249,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { packageId: PackageId, reference: Reference, )(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = LookupError.MissingPackage.pretty(packageId, reference) ) @@ -216,7 +266,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { ErrorCategory.MaliciousOrFaultyBehaviour, ) { case class Reject(validationErrorCause: String)(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = validationErrorCause ) @@ -234,7 +284,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { case class Reject( err: LfError.Preprocessing.Error )(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = err.message ) @@ -252,7 +302,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { ) { case class Error(override val cause: String)(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = cause ) @@ -270,7 +320,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { ) { case class Error(override val cause: String)(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = cause ) @@ -291,7 +341,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { override val cause: String, _err: LfInterpretationError.ContractNotActive, )(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = cause ) { @@ -315,7 +365,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { override val cause: String, _key: GlobalKey, )(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = cause ) { @@ -343,7 +393,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { override val cause: String, _cid: Value.ContractId, )(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = cause ) { @@ -370,7 +420,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { override val cause: String, _key: GlobalKey, )(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = cause ) { @@ -392,7 +442,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { ) { case class Reject()(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = "The ledger configuration is not available." ) @@ -411,7 +461,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { ) { case class Reject(override val cause: String)(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = cause ) @@ -429,7 +479,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { case class PackageSelfConsistency( err: LfError.Package.SelfConsistency )(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = err.message ) @@ -437,7 +487,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { case class PackageInternal( err: LfError.Package.Internal )(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = err.message ) @@ -445,11 +495,11 @@ object LedgerApiErrors extends LedgerApiErrorGroup { case class Preprocessing( err: LfError.Preprocessing.Internal )(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl(cause = err.message) case class Validation(reason: ReplayMismatch)(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = s"Observed un-expected replay mismatch: ${reason}" ) @@ -459,7 +509,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { message: String, detailMessage: Option[String], )(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = s"Daml-Engine interpretation failed with internal error: ${where} / ${message}" ) @@ -481,7 +531,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup { offsetValue: String, message: String, )(implicit - override val loggingContext: ErrorCodeLoggingContext + override val loggingContext: ContextualizedErrorLogger ) extends BaseError.Impl( cause = s"Offset in ${fieldName} not specified in hexadecimal: ${offsetValue}: ${message}" ) diff --git a/ledger/error/src/main/scala/com/daml/error/definitions/PackageServiceError.scala b/ledger/error/src/main/scala/com/daml/error/definitions/PackageServiceError.scala index efaedd4bf3cf..ecce5bcb455c 100644 --- a/ledger/error/src/main/scala/com/daml/error/definitions/PackageServiceError.scala +++ b/ledger/error/src/main/scala/com/daml/error/definitions/PackageServiceError.scala @@ -28,7 +28,7 @@ object PackageServiceError extends PackageServiceErrorGroup { ErrorCategory.InvalidIndependentOfSystemState, ) { final case class Error(reason: String)(implicit - val loggingContext: ErrorCodeLoggingContext + val loggingContext: ContextualizedErrorLogger ) extends BaseError.Impl( cause = "Dar file name is invalid" ) @@ -41,7 +41,7 @@ object PackageServiceError extends PackageServiceErrorGroup { object InvalidDar extends ErrorCode(id = "INVALID_DAR", ErrorCategory.InvalidIndependentOfSystemState) { final case class Error(entries: Seq[String], throwable: Throwable)(implicit - val loggingContext: ErrorCodeLoggingContext + val loggingContext: ContextualizedErrorLogger ) extends BaseError.Impl( cause = "Dar file is corrupt", throwableO = Some(throwable), @@ -53,7 +53,7 @@ object PackageServiceError extends PackageServiceErrorGroup { object InvalidZipEntry extends ErrorCode(id = "INVALID_ZIP_ENTRY", ErrorCategory.InvalidIndependentOfSystemState) { final case class Error(name: String, entries: Seq[String])(implicit - val loggingContext: ErrorCodeLoggingContext + val loggingContext: ContextualizedErrorLogger ) extends BaseError.Impl( cause = "Dar zip file is corrupt" ) @@ -70,7 +70,7 @@ object PackageServiceError extends PackageServiceErrorGroup { ErrorCategory.InvalidIndependentOfSystemState, ) { final case class Error(entries: Seq[String])(implicit - val loggingContext: ErrorCodeLoggingContext + val loggingContext: ContextualizedErrorLogger ) extends BaseError.Impl( cause = "Unsupported legacy Dar zip file" ) @@ -82,7 +82,7 @@ object PackageServiceError extends PackageServiceErrorGroup { object ZipBomb extends ErrorCode(id = "ZIP_BOMB", ErrorCategory.InvalidIndependentOfSystemState) { final case class Error(msg: String)(implicit - val loggingContext: ErrorCodeLoggingContext + val loggingContext: ContextualizedErrorLogger ) extends BaseError.Impl( cause = "Dar zip file seems to be a zip bomb." ) @@ -96,7 +96,7 @@ object PackageServiceError extends PackageServiceErrorGroup { object ParseError extends ErrorCode(id = "DAR_PARSE_ERROR", ErrorCategory.InvalidIndependentOfSystemState) { final case class Error(reason: String)(implicit - val loggingContext: ErrorCodeLoggingContext + val loggingContext: ContextualizedErrorLogger ) extends BaseError.Impl( cause = "Failed to parse the dar file content." ) @@ -113,25 +113,25 @@ object PackageServiceError extends PackageServiceErrorGroup { ErrorCategory.SystemInternalAssumptionViolated, ) { final case class Validation(nameOfFunc: String, msg: String, detailMsg: String = "")(implicit - val loggingContext: ErrorCodeLoggingContext + val loggingContext: ContextualizedErrorLogger ) extends BaseError.Impl( cause = "Internal package validation error." ) with PackageServiceError final case class Error(missing: Set[PackageId])(implicit - val loggingContext: ErrorCodeLoggingContext + val loggingContext: ContextualizedErrorLogger ) extends BaseError.Impl( cause = "Failed to resolve package ids locally." ) with PackageServiceError final case class Generic(reason: String)(implicit - val loggingContext: ErrorCodeLoggingContext + val loggingContext: ContextualizedErrorLogger ) extends BaseError.Impl( cause = "Generic error (please check the reason string)." ) with PackageServiceError final case class Unhandled(throwable: Throwable)(implicit - val loggingContext: ErrorCodeLoggingContext + val loggingContext: ContextualizedErrorLogger ) extends BaseError.Impl( cause = "Failed with an unknown error cause", throwableO = Some(throwable), @@ -143,7 +143,7 @@ object PackageServiceError extends PackageServiceErrorGroup { def fromDamlLfEnginePackageError(err: Either[Error.Package.Error, Unit])(implicit executionContext: ExecutionContext, - loggingContext: ErrorCodeLoggingContext, + loggingContext: ContextualizedErrorLogger, ): EitherT[Future, PackageServiceError, Unit] = EitherT.fromEither[Future](err.left.map { case Error.Package.Internal(nameOfFunc, msg) => @@ -167,7 +167,7 @@ object PackageServiceError extends PackageServiceErrorGroup { ErrorCategory.InvalidIndependentOfSystemState, ) { final case class Error(validationError: validation.ValidationError)(implicit - val loggingContext: ErrorCodeLoggingContext + val loggingContext: ContextualizedErrorLogger ) extends BaseError.Impl( cause = "Package validation failed." ) @@ -179,7 +179,7 @@ object PackageServiceError extends PackageServiceErrorGroup { languageVersion: language.LanguageVersion, allowedLanguageVersions: VersionRange[language.LanguageVersion], )(implicit - val loggingContext: ErrorCodeLoggingContext + val loggingContext: ContextualizedErrorLogger ) extends BaseError.Impl( cause = LedgerApiErrors.Package.AllowedLanguageVersions .buildCause(packageId, languageVersion, allowedLanguageVersions) @@ -199,7 +199,7 @@ object PackageServiceError extends PackageServiceErrorGroup { packageIds: Set[Ref.PackageId], missingDependencies: Set[Ref.PackageId], )(implicit - val loggingContext: ErrorCodeLoggingContext + val loggingContext: ContextualizedErrorLogger ) extends BaseError.Impl( cause = "The set of packages in the dar is not self-consistent and is missing dependencies" diff --git a/ledger/error/src/main/scala/com/daml/error/definitions/ProtoDeserializationError.scala b/ledger/error/src/main/scala/com/daml/error/definitions/ProtoDeserializationError.scala index bf715ad2f804..3a14edb8040b 100644 --- a/ledger/error/src/main/scala/com/daml/error/definitions/ProtoDeserializationError.scala +++ b/ledger/error/src/main/scala/com/daml/error/definitions/ProtoDeserializationError.scala @@ -10,7 +10,7 @@ import com.google.protobuf.InvalidProtocolBufferException trait ProtoDeserializationError extends Product with Serializable { def toAdminError(implicit - loggingContext: ErrorCodeLoggingContext + loggingContext: ContextualizedErrorLogger ): BaseError = ProtoDeserializationFailure.Wrap(this) } @@ -51,7 +51,7 @@ object ProtoDeserializationError extends ProtoDeserializationErrorGroup { ErrorCategory.InvalidIndependentOfSystemState, ) { case class Wrap(reason: ProtoDeserializationError)(implicit - val loggingContext: ErrorCodeLoggingContext + val loggingContext: ContextualizedErrorLogger ) extends BaseError.Impl( cause = "Deserialization of protobuf message failed" ) diff --git a/ledger/error/src/main/scala/com/daml/error/definitions/PruningServiceError.scala b/ledger/error/src/main/scala/com/daml/error/definitions/PruningServiceError.scala index 485484380e31..f7e3d6e49dd3 100644 --- a/ledger/error/src/main/scala/com/daml/error/definitions/PruningServiceError.scala +++ b/ledger/error/src/main/scala/com/daml/error/definitions/PruningServiceError.scala @@ -16,7 +16,7 @@ object PruningServiceError extends PruningServiceErrorGroup { ErrorCategory.SystemInternalAssumptionViolated, ) { final case class Error(reason: String)(implicit - val loggingContext: ErrorCodeLoggingContext + val loggingContext: ContextualizedErrorLogger ) extends BaseError.Impl( cause = "Internal error such as the inability to write to the database" ) diff --git a/ledger/error/src/main/scala/com/daml/error/definitions/RejectionGenerators.scala b/ledger/error/src/main/scala/com/daml/error/definitions/RejectionGenerators.scala index d300745da067..42d24af9ec9a 100644 --- a/ledger/error/src/main/scala/com/daml/error/definitions/RejectionGenerators.scala +++ b/ledger/error/src/main/scala/com/daml/error/definitions/RejectionGenerators.scala @@ -3,7 +3,7 @@ package com.daml.error.definitions -import com.daml.error.{BaseError, ErrorCause, ErrorCode, ErrorCodeLoggingContext} +import com.daml.error.{BaseError, ErrorCause, ErrorCode, ContextualizedErrorLogger} import com.daml.ledger.participant.state import com.daml.lf.engine.Error.{Interpretation, Package, Preprocessing, Validation} import io.grpc.Status.Code @@ -45,17 +45,17 @@ class RejectionGenerators(conformanceMode: Boolean) { } def toGrpc(reject: BaseError)(implicit - errorLoggingContext: ErrorCodeLoggingContext + errorLoggingContext: ContextualizedErrorLogger ): StatusRuntimeException = enforceConformance(reject.asGrpcErrorFromContext) def duplicateCommand(implicit - errorLoggingContext: ErrorCodeLoggingContext + errorLoggingContext: ContextualizedErrorLogger ): StatusRuntimeException = toGrpc(LedgerApiErrors.CommandPreparation.DuplicateCommand.Reject()) def commandExecutorError(cause: ErrorCauseExport)(implicit - errorLoggingContext: ErrorCodeLoggingContext + errorLoggingContext: ContextualizedErrorLogger ): StatusRuntimeException = { def processPackageError(err: LfError.Package.Error): BaseError = err match { @@ -192,7 +192,7 @@ class RejectionGenerators(conformanceMode: Boolean) { // Instead of using this, construct proper validation errors in callers of this method // and only convert to StatusRuntimeExceptions when dispatched (e.g. in ApiSubmissionService) def validationFailure(reject: StatusRuntimeException)(implicit - errorCodeLoggingContext: ErrorCodeLoggingContext + contextualizedErrorLogger: ContextualizedErrorLogger ): StatusRuntimeException = { val description = reject.getStatus.getDescription reject.getStatus.getCode match { @@ -204,13 +204,13 @@ class RejectionGenerators(conformanceMode: Boolean) { } else if (description.startsWith("Invalid field:")) { toGrpc(LedgerApiErrors.CommandValidation.InvalidField.Reject(description)) } else { - errorCodeLoggingContext.warn(s"Unknown invalid argument rejection: ${reject.getStatus}") + contextualizedErrorLogger.warn(s"Unknown invalid argument rejection: ${reject.getStatus}") reject } case Code.NOT_FOUND if description.startsWith("Ledger ID") => toGrpc(LedgerApiErrors.CommandValidation.LedgerIdMismatch.Reject(description)) case _ => - errorCodeLoggingContext.warn(s"Unknown rejection: ${reject.getStatus}") + contextualizedErrorLogger.warn(s"Unknown rejection: ${reject.getStatus}") reject } } diff --git a/ledger/error/src/main/scala/com/daml/error/definitions/TransactionError.scala b/ledger/error/src/main/scala/com/daml/error/definitions/TransactionError.scala index 36940da2101d..a6e9444ada75 100644 --- a/ledger/error/src/main/scala/com/daml/error/definitions/TransactionError.scala +++ b/ledger/error/src/main/scala/com/daml/error/definitions/TransactionError.scala @@ -3,7 +3,7 @@ package com.daml.error.definitions import com.daml.error.ErrorCode.{formatContextAsString, truncateResourceForTransport} -import com.daml.error.{BaseError, ErrorCode, ErrorCodeLoggingContext} +import com.daml.error.{BaseError, ErrorCode, ContextualizedErrorLogger} import com.daml.ledger.participant.state.v2.Update.CommandRejected.{ FinalReason, RejectionReasonTemplate, @@ -14,7 +14,7 @@ import io.grpc.{Status, StatusRuntimeException} trait TransactionError extends BaseError { def createRejection( correlationId: Option[String] - )(implicit loggingContext: ErrorCodeLoggingContext): RejectionReasonTemplate = { + )(implicit loggingContext: ContextualizedErrorLogger): RejectionReasonTemplate = { FinalReason(rpcStatus(correlationId)) } @@ -25,13 +25,13 @@ trait TransactionError extends BaseError { def rpcStatus( correlationId: Option[String] - )(implicit loggingContext: ErrorCodeLoggingContext): RpcStatus = + )(implicit loggingContext: ContextualizedErrorLogger): RpcStatus = _rpcStatus(None, correlationId) def _rpcStatus( overrideCode: Option[Status.Code], correlationId: Option[String], - )(implicit loggingContext: ErrorCodeLoggingContext): RpcStatus = { + )(implicit loggingContext: ContextualizedErrorLogger): RpcStatus = { // yes, this is a horrible duplication of ErrorCode.asGrpcError. why? because // scalapb does not really support grpc rich errors. there is literally no method @@ -103,7 +103,7 @@ abstract class LoggingTransactionErrorImpl( definiteAnswer: Boolean = false, )(implicit code: ErrorCode, - loggingContext: ErrorCodeLoggingContext, + loggingContext: ContextualizedErrorLogger, ) extends TransactionErrorImpl(cause, throwableO, definiteAnswer)(code) { def asGrpcError: StatusRuntimeException = asGrpcErrorFromContext diff --git a/ledger/error/src/test/suite/scala/com/daml/error/ErrorCodeSpec.scala b/ledger/error/src/test/suite/scala/com/daml/error/ErrorCodeSpec.scala index ef83711d1c23..eac93cf3aeb4 100644 --- a/ledger/error/src/test/suite/scala/com/daml/error/ErrorCodeSpec.scala +++ b/ledger/error/src/test/suite/scala/com/daml/error/ErrorCodeSpec.scala @@ -20,8 +20,8 @@ import scala.jdk.CollectionConverters._ class ErrorCodeSpec extends AnyFlatSpec with Matchers with BeforeAndAfter { implicit private val testLoggingContext: LoggingContext = LoggingContext.ForTesting private val logger = ContextualizedLogger.get(getClass) - private val errorLoggingContext: Option[String] => DamlErrorCodeLoggingContext = correlationId => - new DamlErrorCodeLoggingContext(logger, testLoggingContext, correlationId) + private val errorLoggingContext: Option[String] => DamlContextualizedErrorLogger = + correlationId => new DamlContextualizedErrorLogger(logger, testLoggingContext, correlationId) private val className = classOf[ErrorCode].getSimpleName @@ -91,7 +91,7 @@ class ErrorCodeSpec extends AnyFlatSpec with Matchers with BeforeAndAfter { private def logSeriousError( cause: String = "the error argument", extra: Map[String, String] = Map.empty, - )(implicit errorLoggingContext: ErrorCodeLoggingContext): Unit = + )(implicit errorLoggingContext: ContextualizedErrorLogger): Unit = SeriousError .Error(cause) .logWithContext(extra) diff --git a/ledger/ledger-api-auth/BUILD.bazel b/ledger/ledger-api-auth/BUILD.bazel index 684a0c1a794e..2c1b049e7053 100644 --- a/ledger/ledger-api-auth/BUILD.bazel +++ b/ledger/ledger-api-auth/BUILD.bazel @@ -27,6 +27,7 @@ da_scala_library( "//daml-lf/data", "//ledger-api/grpc-definitions:ledger_api_proto_scala", "//ledger-service/jwt", + "//ledger/error", "//ledger/ledger-api-common", "@maven//:com_auth0_java_jwt", "@maven//:io_grpc_grpc_api", diff --git a/ledger/ledger-api-auth/src/main/scala/com/digitalasset/ledger/api/auth/Authorizer.scala b/ledger/ledger-api-auth/src/main/scala/com/digitalasset/ledger/api/auth/Authorizer.scala index fbdd1c293418..1e7966657232 100644 --- a/ledger/ledger-api-auth/src/main/scala/com/digitalasset/ledger/api/auth/Authorizer.scala +++ b/ledger/ledger-api-auth/src/main/scala/com/digitalasset/ledger/api/auth/Authorizer.scala @@ -3,14 +3,14 @@ package com.daml.ledger.api.auth -import java.time.Instant - +import com.daml.error.{ContextualizedErrorLogger, NoLogging} import com.daml.ledger.api.auth.interceptor.AuthorizationInterceptor import com.daml.ledger.api.v1.transaction_filter.TransactionFilter import com.daml.platform.server.api.validation.ErrorFactories.{permissionDenied, unauthenticated} import io.grpc.stub.{ServerCallStreamObserver, StreamObserver} import org.slf4j.LoggerFactory +import java.time.Instant import scala.collection.compat._ import scala.concurrent.Future import scala.util.{Failure, Success, Try} @@ -21,6 +21,8 @@ import scala.util.{Failure, Success, Try} final class Authorizer(now: () => Instant, ledgerId: String, participantId: String) { private val logger = LoggerFactory.getLogger(this.getClass) + // TODO error codes: Enable logging + private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = NoLogging /** Validates all properties of claims that do not depend on the request, * such as expiration time or ledger ID. diff --git a/ledger/ledger-api-common/BUILD.bazel b/ledger/ledger-api-common/BUILD.bazel index 5395c0165f47..f59120241aed 100644 --- a/ledger/ledger-api-common/BUILD.bazel +++ b/ledger/ledger-api-common/BUILD.bazel @@ -34,6 +34,7 @@ da_scala_library( "//daml-lf/transaction", "//language-support/scala/bindings", "//ledger-api/rs-grpc-bridge", + "//ledger/error", "//ledger/ledger-api-akka", "//ledger/ledger-api-domain", "//ledger/ledger-api-health", @@ -124,6 +125,7 @@ da_scala_test_suite( "//ledger-api/rs-grpc-bridge", "//ledger-api/rs-grpc-testing-utils", "//ledger-api/testing-utils", + "//ledger/error", "//ledger/ledger-api-akka", "//ledger/ledger-api-client", "//ledger/ledger-api-domain", diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/CommandsValidator.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/CommandsValidator.scala index aacad83afd84..25ab82124a13 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/CommandsValidator.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/CommandsValidator.scala @@ -4,6 +4,7 @@ package com.daml.ledger.api.validation import com.daml.api.util.{DurationConversion, TimestampConversion} +import com.daml.error.ContextualizedErrorLogger import com.daml.ledger.api.domain import com.daml.ledger.api.domain.LedgerId import com.daml.ledger.api.v1.commands.Command.Command.{ @@ -14,6 +15,7 @@ import com.daml.ledger.api.v1.commands.Command.Command.{ ExerciseByKey => ProtoExerciseByKey, } import com.daml.ledger.api.v1.commands.{Command => ProtoCommand, Commands => ProtoCommands} +import com.daml.ledger.api.validation.CommandsValidator.{Submitters, effectiveSubmitters} import com.daml.lf.command._ import com.daml.lf.data._ import com.daml.lf.value.{Value => Lf} @@ -35,6 +37,8 @@ final class CommandsValidator(ledgerId: LedgerId) { currentLedgerTime: Instant, currentUtcTime: Instant, maxDeduplicationTime: Option[Duration], + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, domain.Commands] = for { cmdLegerId <- requireLedgerString(commands.ledgerId, "ledger_id") @@ -46,7 +50,7 @@ final class CommandsValidator(ledgerId: LedgerId) { .map(domain.ApplicationId(_)) commandId <- requireLedgerString(commands.commandId, "command_id").map(domain.CommandId(_)) submissionId <- requireSubmissionId(commands.submissionId) - submitters <- CommandsValidator.validateSubmitters(commands) + submitters <- validateSubmitters(commands) commandz <- requireNonEmpty(commands.commands, "commands") validatedCommands <- validateInnerCommands(commandz) ledgerEffectiveTime <- validateLedgerTime(currentLedgerTime, commands) @@ -83,6 +87,8 @@ final class CommandsValidator(ledgerId: LedgerId) { private def validateLedgerTime( currentTime: Instant, commands: ProtoCommands, + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, Instant] = { val minLedgerTimeAbs = commands.minLedgerTimeAbs val minLedgerTimeRel = commands.minLedgerTimeRel @@ -103,6 +109,8 @@ final class CommandsValidator(ledgerId: LedgerId) { private def validateInnerCommands( commands: Seq[ProtoCommand] + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, immutable.Seq[ApiCommand]] = commands.foldLeft[Either[StatusRuntimeException, Vector[ApiCommand]]]( Right(Vector.empty[ApiCommand]) @@ -115,6 +123,8 @@ final class CommandsValidator(ledgerId: LedgerId) { private def validateInnerCommand( command: ProtoCommand.Command + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, ApiCommand] = command match { case c: ProtoCreate => @@ -180,6 +190,25 @@ final class CommandsValidator(ledgerId: LedgerId) { Left(missingField("command", definiteAnswer = Some(false))) } + private def validateSubmitters( + commands: ProtoCommands + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): Either[StatusRuntimeException, Submitters[Ref.Party]] = { + def actAsMustNotBeEmpty(effectiveActAs: Set[Ref.Party]) = + Either.cond( + effectiveActAs.nonEmpty, + (), + missingField("party or act_as", definiteAnswer = Some(false)), + ) + + val submitters = effectiveSubmitters(commands) + for { + actAs <- requireParties(submitters.actAs) + readAs <- requireParties(submitters.readAs) + _ <- actAsMustNotBeEmpty(actAs) + } yield Submitters(actAs, readAs) + } } object CommandsValidator { @@ -207,22 +236,4 @@ object CommandsValidator { commands.actAs.toSet + commands.party val noSubmitters: Submitters[String] = Submitters(Set.empty, Set.empty) - - def validateSubmitters( - commands: ProtoCommands - ): Either[StatusRuntimeException, Submitters[Ref.Party]] = { - def actAsMustNotBeEmpty(effectiveActAs: Set[Ref.Party]) = - Either.cond( - effectiveActAs.nonEmpty, - (), - missingField("party or act_as", definiteAnswer = Some(false)), - ) - - val submitters = effectiveSubmitters(commands) - for { - actAs <- requireParties(submitters.actAs) - readAs <- requireParties(submitters.readAs) - _ <- actAsMustNotBeEmpty(actAs) - } yield Submitters(actAs, readAs) - } } diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/CompletionServiceRequestValidator.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/CompletionServiceRequestValidator.scala index a947e071b22d..14cb31e1034a 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/CompletionServiceRequestValidator.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/CompletionServiceRequestValidator.scala @@ -3,6 +3,7 @@ package com.daml.ledger.api.validation +import com.daml.error.ContextualizedErrorLogger import com.daml.lf.data.Ref import com.daml.ledger.api.domain.{ApplicationId, LedgerId, LedgerOffset} import com.daml.ledger.api.messages.command.completion @@ -11,18 +12,19 @@ import com.daml.ledger.api.v1.command_completion_service.{ CompletionEndRequest, CompletionStreamRequest => GrpcCompletionStreamRequest, } -import com.daml.platform.server.api.validation.FieldValidations +import com.daml.platform.server.api.validation.FieldValidations._ import io.grpc.StatusRuntimeException import com.daml.platform.server.api.validation.ErrorFactories._ -class CompletionServiceRequestValidator(ledgerId: LedgerId, partyNameChecker: PartyNameChecker) - extends FieldValidations { +class CompletionServiceRequestValidator(ledgerId: LedgerId, partyNameChecker: PartyNameChecker) { private val partyValidator = new PartyValidator(partyNameChecker) def validateCompletionStreamRequest( request: GrpcCompletionStreamRequest, ledgerEnd: LedgerOffset.Absolute, + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, CompletionStreamRequest] = for { _ <- matchLedgerId(ledgerId)(LedgerId(request.ledgerId)) @@ -48,6 +50,8 @@ class CompletionServiceRequestValidator(ledgerId: LedgerId, partyNameChecker: Pa def validateCompletionEndRequest( req: CompletionEndRequest + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, completion.CompletionEndRequest] = for { ledgerId <- matchLedgerId(ledgerId)(LedgerId(req.ledgerId)) diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/LedgerOffsetValidator.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/LedgerOffsetValidator.scala index 37991dd9e992..d4df17f843e3 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/LedgerOffsetValidator.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/LedgerOffsetValidator.scala @@ -3,13 +3,14 @@ package com.daml.ledger.api.validation +import com.daml.error.ContextualizedErrorLogger import com.daml.ledger.api.domain import com.daml.ledger.api.v1.ledger_offset.LedgerOffset import com.daml.ledger.api.v1.ledger_offset.LedgerOffset.LedgerBoundary import com.daml.platform.server.api.validation.ErrorFactories.{ invalidArgument, missingField, - outOfRange, + offsetAfterLedgerEnd, } import com.daml.platform.server.api.validation.FieldValidations.requireLedgerString import io.grpc.StatusRuntimeException @@ -23,6 +24,8 @@ object LedgerOffsetValidator { def validateOptional( ledgerOffset: Option[LedgerOffset], fieldName: String, + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, Option[domain.LedgerOffset]] = ledgerOffset .map(validate(_, fieldName)) @@ -33,6 +36,8 @@ object LedgerOffsetValidator { def validate( ledgerOffset: LedgerOffset, fieldName: String, + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, domain.LedgerOffset] = { ledgerOffset.value match { case LedgerOffset.Value.Absolute(value) => @@ -48,10 +53,16 @@ object LedgerOffsetValidator { offsetType: String, ledgerOffset: domain.LedgerOffset, ledgerEnd: domain.LedgerOffset.Absolute, + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, Unit] = ledgerOffset match { case abs: domain.LedgerOffset.Absolute if abs > ledgerEnd => - Left(outOfRange(s"$offsetType offset ${abs.value} is after ledger end ${ledgerEnd.value}")) + Left( + offsetAfterLedgerEnd( + s"$offsetType offset ${abs.value} is after ledger end ${ledgerEnd.value}" + ) + ) case _ => Right(()) } @@ -60,6 +71,8 @@ object LedgerOffsetValidator { offsetType: String, ledgerOffset: Option[domain.LedgerOffset], ledgerEnd: domain.LedgerOffset.Absolute, + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, Unit] = ledgerOffset.fold[Either[StatusRuntimeException, Unit]](Right(()))( offsetIsBeforeEndIfAbsolute(offsetType, _, ledgerEnd) @@ -68,6 +81,8 @@ object LedgerOffsetValidator { private def convertLedgerBoundary( fieldName: String, value: LedgerBoundary, + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, domain.LedgerOffset] = { value match { case LedgerBoundary.Unrecognized(invalid) => diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/PartyValidator.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/PartyValidator.scala index 1eac953df81b..9fde087f974e 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/PartyValidator.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/PartyValidator.scala @@ -3,6 +3,7 @@ package com.daml.ledger.api.validation +import com.daml.error.ContextualizedErrorLogger import com.daml.lf.data.Ref.Party import com.daml.platform.server.api.validation.ErrorFactories.invalidArgument import com.daml.platform.server.api.validation.FieldValidations.requireParties @@ -11,13 +12,17 @@ import io.grpc.StatusRuntimeException class PartyValidator(partyNameChecker: PartyNameChecker) { type Result[X] = Either[StatusRuntimeException, X] - def requireKnownParties(parties: Iterable[String]): Result[Set[Party]] = + def requireKnownParties( + parties: Iterable[String] + )(implicit contextualizedErrorLogger: ContextualizedErrorLogger): Result[Set[Party]] = for { ps <- requireParties(parties.toSet) knownParties <- requireKnownParties(ps) - } yield (knownParties) + } yield knownParties - private def requireKnownParties(partiesInRequest: Set[Party]): Result[Set[Party]] = { + private def requireKnownParties( + partiesInRequest: Set[Party] + )(implicit contextualizedErrorLogger: ContextualizedErrorLogger): Result[Set[Party]] = { val unknownParties = partiesInRequest.filterNot(partyNameChecker.isKnownParty) if (unknownParties.nonEmpty) Left(invalidArgument(None)(s"Unknown parties: ${unknownParties.mkString("[", ", ", "]")}")) diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/SubmitAndWaitRequestValidator.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/SubmitAndWaitRequestValidator.scala index 283130af19b9..c02f2eab24ed 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/SubmitAndWaitRequestValidator.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/SubmitAndWaitRequestValidator.scala @@ -3,6 +3,8 @@ package com.daml.ledger.api.validation +import com.daml.error.ContextualizedErrorLogger + import java.time.{Duration, Instant} import com.daml.ledger.api.messages.command.submission @@ -17,6 +19,8 @@ class SubmitAndWaitRequestValidator(commandsValidator: CommandsValidator) { currentLedgerTime: Instant, currentUtcTime: Instant, maxDeduplicationTime: Option[Duration], + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, submission.SubmitRequest] = for { commands <- requirePresence(req.commands, "commands") diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/SubmitRequestValidator.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/SubmitRequestValidator.scala index d91938bbb62c..998c3385742a 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/SubmitRequestValidator.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/SubmitRequestValidator.scala @@ -3,6 +3,8 @@ package com.daml.ledger.api.validation +import com.daml.error.ContextualizedErrorLogger + import java.time.{Duration, Instant} import com.daml.ledger.api.messages.command.submission @@ -17,6 +19,8 @@ class SubmitRequestValidator(commandsValidator: CommandsValidator) { currentLedgerTime: Instant, currentUtcTime: Instant, maxDeduplicationTime: Option[Duration], + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, submission.SubmitRequest] = for { commands <- requirePresence(req.commands, "commands") diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/TransactionFilterValidator.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/TransactionFilterValidator.scala index 3c5d6ed6ee3c..840dcc1557e3 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/TransactionFilterValidator.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/TransactionFilterValidator.scala @@ -3,6 +3,7 @@ package com.daml.ledger.api.validation +import com.daml.error.ContextualizedErrorLogger import com.daml.ledger.api.domain import com.daml.ledger.api.domain.InclusiveFilters import com.daml.ledger.api.v1.transaction_filter.{Filters, TransactionFilter} @@ -17,6 +18,8 @@ object TransactionFilterValidator { def validate( txFilter: TransactionFilter + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, domain.TransactionFilter] = { if (txFilter.filtersByParty.isEmpty) { Left(ErrorFactories.invalidArgument(None)("filtersByParty cannot be empty")) @@ -32,7 +35,9 @@ object TransactionFilterValidator { } } - def validateFilters(filters: Filters): Either[StatusRuntimeException, domain.Filters] = { + def validateFilters(filters: Filters)(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): Either[StatusRuntimeException, domain.Filters] = { filters.inclusive .fold[Either[StatusRuntimeException, domain.Filters]](Right(domain.Filters.noFilter)) { inclusive => diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/TransactionServiceRequestValidator.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/TransactionServiceRequestValidator.scala index 25941ed2508c..f45024929f0c 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/TransactionServiceRequestValidator.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/TransactionServiceRequestValidator.scala @@ -3,6 +3,7 @@ package com.daml.ledger.api.validation +import com.daml.error.ContextualizedErrorLogger import com.daml.lf.data.Ref import com.daml.ledger.api.domain import com.daml.ledger.api.domain.{LedgerId, LedgerOffset} @@ -32,7 +33,9 @@ class TransactionServiceRequestValidator( private val partyValidator = new PartyValidator(partyNameChecker) - private def matchId(input: LedgerId): Result[LedgerId] = matchLedgerId(ledgerId)(input) + private def matchId(input: LedgerId)(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): Result[LedgerId] = matchLedgerId(ledgerId)(input) case class PartialValidation( ledgerId: domain.LedgerId, @@ -42,7 +45,9 @@ class TransactionServiceRequestValidator( knownParties: Set[Ref.Party], ) - private def commonValidations(req: GetTransactionsRequest): Result[PartialValidation] = { + private def commonValidations( + req: GetTransactionsRequest + )(implicit contextualizedErrorLogger: ContextualizedErrorLogger): Result[PartialValidation] = { for { ledgerId <- matchId(LedgerId(req.ledgerId)) filter <- requirePresence(req.filter, "filter") @@ -63,6 +68,8 @@ class TransactionServiceRequestValidator( def validate( req: GetTransactionsRequest, ledgerEnd: LedgerOffset.Absolute, + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Result[transaction.GetTransactionsRequest] = { for { @@ -92,6 +99,8 @@ class TransactionServiceRequestValidator( def validateTree( req: GetTransactionsRequest, ledgerEnd: LedgerOffset.Absolute, + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Result[GetTransactionTreesRequest] = { for { @@ -118,7 +127,9 @@ class TransactionServiceRequestValidator( } } - def validateLedgerEnd(req: GetLedgerEndRequest): Result[transaction.GetLedgerEndRequest] = { + def validateLedgerEnd(req: GetLedgerEndRequest)(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): Result[transaction.GetLedgerEndRequest] = { for { ledgerId <- matchId(LedgerId(req.ledgerId)) } yield { @@ -128,6 +139,8 @@ class TransactionServiceRequestValidator( def validateTransactionById( req: GetTransactionByIdRequest + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Result[transaction.GetTransactionByIdRequest] = { for { ledgerId <- matchId(LedgerId(req.ledgerId)) @@ -146,6 +159,8 @@ class TransactionServiceRequestValidator( def validateTransactionByEventId( req: GetTransactionByEventIdRequest + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Result[transaction.GetTransactionByEventIdRequest] = { for { ledgerId <- matchId(LedgerId(req.ledgerId)) @@ -163,7 +178,7 @@ class TransactionServiceRequestValidator( private def transactionFilterToPartySet( transactionFilter: TransactionFilter - ) = + )(implicit contextualizedErrorLogger: ContextualizedErrorLogger) = transactionFilter.filtersByParty .collectFirst { case (party, Filters(Some(inclusive))) => invalidArgument(None)( diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/ValueValidator.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/ValueValidator.scala index 36ed7bcf8281..244444115836 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/ValueValidator.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/ledger/api/validation/ValueValidator.scala @@ -3,6 +3,7 @@ package com.daml.ledger.api.validation +import com.daml.error.{ContextualizedErrorLogger, NoLogging} import com.daml.lf.data._ import com.daml.lf.value.Value.{ContractId, ValueUnit} import com.daml.ledger.api.domain @@ -16,10 +17,12 @@ import io.grpc.StatusRuntimeException import scalaz.syntax.bifunctor._ import scalaz.std.either._ -object ValueValidator { +trait ValueValidator { private[validation] def validateRecordFields( recordFields: Seq[api.RecordField] + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, ImmArray[(Option[Ref.Name], domain.Value)]] = recordFields .foldLeft[Either[StatusRuntimeException, BackStack[(Option[Ref.Name], domain.Value)]]]( @@ -34,7 +37,9 @@ object ValueValidator { }) .map(_.toImmArray) - def validateRecord(rec: api.Record): Either[StatusRuntimeException, Lf.ValueRecord] = + def validateRecord(rec: api.Record)(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): Either[StatusRuntimeException, Lf.ValueRecord] = for { recId <- validateOptionalIdentifier(rec.recordId) fields <- validateRecordFields(rec.fields) @@ -43,7 +48,9 @@ object ValueValidator { private val validNumericString = """[+-]?\d{1,38}(\.\d{0,37})?""".r.pattern - def validateValue(v0: api.Value): Either[StatusRuntimeException, domain.Value] = v0.sum match { + def validateValue(v0: api.Value)(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): Either[StatusRuntimeException, domain.Value] = v0.sum match { case Sum.ContractId(cId) => ContractId .fromString(cId) @@ -147,7 +154,23 @@ object ValueValidator { private[validation] def validateOptionalIdentifier( variantIdO: Option[api.Identifier] + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, Option[Ref.Identifier]] = variantIdO.map(validateIdentifier(_).map(Some.apply)).getOrElse(Right(None)) } + +object ValueValidator extends ValueValidator + +/** Implementation of self-service error codes brings logging-on-creation to + * error factories and validators, used in the Ledger API where automatic logging is desired. + * For places where the ValueValidator is needed without logging(e.g. daml-script, navigator), use this implementation instead. + */ +object NoLoggingValueValidator extends ValueValidator { + def validateRecord(rec: api.Record): Either[StatusRuntimeException, Lf.ValueRecord] = + ValueValidator.validateRecord(rec)(NoLogging) + + def validateValue(v0: api.Value): Either[StatusRuntimeException, Lf] = + ValueValidator.validateValue(v0)(NoLogging) +} diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/ValidationLogger.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/ValidationLogger.scala index 5070ad0d80d7..f62a53df42ee 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/ValidationLogger.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/ValidationLogger.scala @@ -4,10 +4,12 @@ package com.daml.platform.server.api import com.daml.logging.{ContextualizedLogger, LoggingContext} -import org.slf4j.Logger object ValidationLogger { - def logFailure[Request](request: Request, t: Throwable)(implicit logger: Logger): Throwable = { + def logFailure[Request](request: Request, t: Throwable)(implicit + logger: ContextualizedLogger, + loggingContext: LoggingContext, + ): Throwable = { logger.debug(s"Request validation failed for $request. Message: ${t.getMessage}") logger.info(t.getMessage) t diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/services/grpc/GrpcCommandCompletionService.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/services/grpc/GrpcCommandCompletionService.scala index 0fef62811530..13456844d4c9 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/services/grpc/GrpcCommandCompletionService.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/services/grpc/GrpcCommandCompletionService.scala @@ -5,6 +5,7 @@ package com.daml.platform.server.api.services.grpc import akka.stream.Materializer import akka.stream.scaladsl.Source +import com.daml.error.DamlContextualizedErrorLogger import com.daml.grpc.adapter.ExecutionSequencerFactory import com.daml.ledger.api.domain import com.daml.ledger.api.domain.LedgerId @@ -14,9 +15,9 @@ import com.daml.ledger.api.messages.command.completion.{ import com.daml.ledger.api.v1.command_completion_service._ import com.daml.ledger.api.v1.ledger_offset.LedgerOffset import com.daml.ledger.api.validation.{CompletionServiceRequestValidator, PartyNameChecker} +import com.daml.logging.{ContextualizedLogger, LoggingContext} import com.daml.platform.server.api.ValidationLogger import com.daml.platform.server.api.services.domain.CommandCompletionService -import org.slf4j.{Logger, LoggerFactory} import scala.concurrent.{ExecutionContext, Future} @@ -43,10 +44,13 @@ class GrpcCommandCompletionService( protected val mat: Materializer, protected val esf: ExecutionSequencerFactory, executionContext: ExecutionContext, + loggingContext: LoggingContext, ) extends CommandCompletionServiceAkkaGrpc { private val validator = new CompletionServiceRequestValidator(ledgerId, partyNameChecker) - protected implicit val logger: Logger = LoggerFactory.getLogger(service.getClass) + protected implicit val logger: ContextualizedLogger = ContextualizedLogger.get(getClass) + private implicit val contextualizedErrorLogger: DamlContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, loggingContext, None) override def completionStreamSource( request: CompletionStreamRequest diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/services/grpc/GrpcCommandService.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/services/grpc/GrpcCommandService.scala index 3478f9061e5b..4da59e653b4f 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/services/grpc/GrpcCommandService.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/services/grpc/GrpcCommandService.scala @@ -3,19 +3,19 @@ package com.daml.platform.server.api.services.grpc -import java.time.{Duration, Instant} - +import com.daml.error.{DamlContextualizedErrorLogger, ContextualizedErrorLogger} import com.daml.ledger.api.SubmissionIdGenerator import com.daml.ledger.api.domain.LedgerId import com.daml.ledger.api.v1.command_service.CommandServiceGrpc.CommandService import com.daml.ledger.api.v1.command_service._ import com.daml.ledger.api.validation.{CommandsValidator, SubmitAndWaitRequestValidator} +import com.daml.logging.{ContextualizedLogger, LoggingContext} import com.daml.platform.api.grpc.GrpcApiService import com.daml.platform.server.api.{ProxyCloseable, ValidationLogger} import com.google.protobuf.empty.Empty import io.grpc.ServerServiceDefinition -import org.slf4j.{Logger, LoggerFactory} +import java.time.{Duration, Instant} import scala.concurrent.{ExecutionContext, Future} class GrpcCommandService( @@ -25,14 +25,18 @@ class GrpcCommandService( currentUtcTime: () => Instant, maxDeduplicationTime: () => Option[Duration], generateSubmissionId: SubmissionIdGenerator, -)(implicit executionContext: ExecutionContext) +)(implicit executionContext: ExecutionContext, loggingContext: LoggingContext) extends CommandService with GrpcApiService with ProxyCloseable { - protected implicit val logger: Logger = LoggerFactory.getLogger(service.getClass) + protected implicit val logger: ContextualizedLogger = ContextualizedLogger.get(getClass) + private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, loggingContext, None) - private[this] val validator = new SubmitAndWaitRequestValidator(new CommandsValidator(ledgerId)) + private[this] val validator = new SubmitAndWaitRequestValidator( + new CommandsValidator(ledgerId) + ) override def submitAndWait(request: SubmitAndWaitRequest): Future[Empty] = { val requestWithSubmissionId = generateSubmissionIdIfEmpty(request) diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/services/grpc/GrpcCommandSubmissionService.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/services/grpc/GrpcCommandSubmissionService.scala index 23ccfcacb37d..72baaa66ecb8 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/services/grpc/GrpcCommandSubmissionService.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/services/grpc/GrpcCommandSubmissionService.scala @@ -3,6 +3,7 @@ package com.daml.platform.server.api.services.grpc +import com.daml.error.{DamlContextualizedErrorLogger, ContextualizedErrorLogger} import com.daml.ledger.api.SubmissionIdGenerator import com.daml.ledger.api.domain.LedgerId import com.daml.ledger.api.v1.command_submission_service.CommandSubmissionServiceGrpc.{ @@ -13,6 +14,7 @@ import com.daml.ledger.api.v1.command_submission_service.{ SubmitRequest => ApiSubmitRequest, } import com.daml.ledger.api.validation.{CommandsValidator, SubmitRequestValidator} +import com.daml.logging.{ContextualizedLogger, LoggingContext} import com.daml.metrics.{Metrics, Timed} import com.daml.platform.api.grpc.GrpcApiService import com.daml.platform.server.api.services.domain.CommandSubmissionService @@ -20,7 +22,6 @@ import com.daml.platform.server.api.{ProxyCloseable, ValidationLogger} import com.daml.telemetry.{DefaultTelemetry, SpanAttribute, TelemetryContext} import com.google.protobuf.empty.Empty import io.grpc.ServerServiceDefinition -import org.slf4j.{Logger, LoggerFactory} import java.time.{Duration, Instant} import scala.concurrent.{ExecutionContext, Future} @@ -33,13 +34,18 @@ class GrpcCommandSubmissionService( maxDeduplicationTime: () => Option[Duration], submissionIdGenerator: SubmissionIdGenerator, metrics: Metrics, -)(implicit executionContext: ExecutionContext) +)(implicit executionContext: ExecutionContext, loggingContext: LoggingContext) extends ApiCommandSubmissionService with ProxyCloseable with GrpcApiService { - protected implicit val logger: Logger = LoggerFactory.getLogger(service.getClass) - + protected implicit val logger: ContextualizedLogger = ContextualizedLogger.get(getClass) + private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = + new DamlContextualizedErrorLogger( + logger, + loggingContext, + None, + ) private val validator = new SubmitRequestValidator(new CommandsValidator(ledgerId)) override def submit(request: ApiSubmitRequest): Future[Empty] = { diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/services/grpc/GrpcTransactionService.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/services/grpc/GrpcTransactionService.scala index 46f6a378855c..64b0ed4b960d 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/services/grpc/GrpcTransactionService.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/services/grpc/GrpcTransactionService.scala @@ -6,18 +6,18 @@ package com.daml.platform.server.api.services.grpc import akka.NotUsed import akka.stream.Materializer import akka.stream.scaladsl.Source +import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger} import com.daml.grpc.adapter.ExecutionSequencerFactory import com.daml.ledger.api.domain.LedgerId import com.daml.ledger.api.v1.ledger_offset.LedgerOffset import com.daml.ledger.api.v1.transaction_service._ import com.daml.ledger.api.validation.TransactionServiceRequestValidator.Result import com.daml.ledger.api.validation.{PartyNameChecker, TransactionServiceRequestValidator} +import com.daml.logging.{ContextualizedLogger, LoggingContext} import com.daml.platform.api.grpc.GrpcApiService import com.daml.platform.server.api.ValidationLogger import com.daml.platform.server.api.services.domain.TransactionService -import com.daml.platform.server.api.validation.{ErrorFactories, FieldValidations} import io.grpc.ServerServiceDefinition -import org.slf4j.{Logger, LoggerFactory} import scala.concurrent.{ExecutionContext, Future} @@ -29,12 +29,13 @@ final class GrpcTransactionService( protected val esf: ExecutionSequencerFactory, protected val mat: Materializer, executionContext: ExecutionContext, + loggingContext: LoggingContext, ) extends TransactionServiceAkkaGrpc - with GrpcApiService - with ErrorFactories - with FieldValidations { + with GrpcApiService { - protected implicit val logger: Logger = LoggerFactory.getLogger(service.getClass) + protected implicit val logger: ContextualizedLogger = ContextualizedLogger.get(getClass) + private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, loggingContext, None) private val validator = new TransactionServiceRequestValidator(ledgerId, partyNameChecker) @@ -42,7 +43,7 @@ final class GrpcTransactionService( override protected def getTransactionsSource( request: GetTransactionsRequest ): Source[GetTransactionsResponse, NotUsed] = { - logger.debug("Received new transaction request {}", request) + logger.debug(s"Received new transaction request $request") Source.future(service.getLedgerEnd(request.ledgerId)).flatMapConcat { ledgerEnd => val validation = validator.validate(request, ledgerEnd) @@ -58,7 +59,7 @@ final class GrpcTransactionService( override protected def getTransactionTreesSource( request: GetTransactionsRequest ): Source[GetTransactionTreesResponse, NotUsed] = { - logger.debug("Received new transaction tree request {}", request) + logger.debug(s"Received new transaction tree request $request") Source.future(service.getLedgerEnd(request.ledgerId)).flatMapConcat { ledgerEnd => val validation = validator.validateTree(request, ledgerEnd) diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/ActiveContractsServiceValidation.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/ActiveContractsServiceValidation.scala index 5f50a90d0711..b1f24be61537 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/ActiveContractsServiceValidation.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/ActiveContractsServiceValidation.scala @@ -3,6 +3,7 @@ package com.daml.platform.server.api.validation +import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger} import com.daml.ledger.api.domain.LedgerId import com.daml.ledger.api.v1.active_contracts_service.ActiveContractsServiceGrpc.ActiveContractsService import com.daml.ledger.api.v1.active_contracts_service.{ @@ -10,35 +11,37 @@ import com.daml.ledger.api.v1.active_contracts_service.{ GetActiveContractsRequest, GetActiveContractsResponse, } +import com.daml.logging.{ContextualizedLogger, LoggingContext} import com.daml.platform.api.grpc.GrpcApiService import com.daml.platform.server.api.{ProxyCloseable, ValidationLogger} import io.grpc.ServerServiceDefinition import io.grpc.stub.StreamObserver -import org.slf4j.{Logger, LoggerFactory} +import FieldValidations._ import scala.concurrent.ExecutionContext class ActiveContractsServiceValidation( protected val service: ActiveContractsService with AutoCloseable, val ledgerId: LedgerId, -)(implicit executionContext: ExecutionContext) +)(implicit executionContext: ExecutionContext, loggingContext: LoggingContext) extends ActiveContractsService with ProxyCloseable - with GrpcApiService - with FieldValidations { + with GrpcApiService { - protected implicit val logger: Logger = LoggerFactory.getLogger(service.getClass) + protected implicit val logger: ContextualizedLogger = ContextualizedLogger.get(service.getClass) + private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, loggingContext, None) override def getActiveContracts( request: GetActiveContractsRequest, responseObserver: StreamObserver[GetActiveContractsResponse], - ): Unit = { + ): Unit = matchLedgerId(ledgerId)(LedgerId(request.ledgerId)) .fold( t => responseObserver.onError(ValidationLogger.logFailure(request, t)), _ => service.getActiveContracts(request, responseObserver), ) - } + override def bindService(): ServerServiceDefinition = ActiveContractsServiceGrpc.bindService(this, executionContext) } diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/ErrorFactories.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/ErrorFactories.scala index 69b2d2eb26ad..6d7ea01906b6 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/ErrorFactories.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/ErrorFactories.scala @@ -3,9 +3,15 @@ package com.daml.platform.server.api.validation +import com.daml.error.definitions.LedgerApiErrors +import com.daml.error.{ContextualizedErrorLogger, ErrorCodesVersionSwitcher} import com.daml.ledger.api.domain.LedgerId import com.daml.ledger.grpc.GrpcStatuses import com.daml.platform.server.api.ApiException +import com.daml.platform.server.api.validation.ErrorFactories.{ + addDefiniteAnswerDetails, + definiteAnswers, +} import com.google.protobuf.{Any => AnyProto} import com.google.rpc.{ErrorInfo, Status} import io.grpc.Status.Code @@ -13,18 +19,25 @@ import io.grpc.StatusRuntimeException import io.grpc.protobuf.StatusProto import scalaz.syntax.tag._ -trait ErrorFactories { - - import ErrorFactories._ +class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitcher) { - def duplicateCommandException: StatusRuntimeException = - grpcError( - Status - .newBuilder() - .setCode(Code.ALREADY_EXISTS.value()) - .setMessage("Duplicate command") - .addDetails(definiteAnswers(false)) - .build() + def duplicateCommandException(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): StatusRuntimeException = + errorCodesVersionSwitcher.choose( + v1 = { + val exception = grpcError( + Status + .newBuilder() + .setCode(Code.ALREADY_EXISTS.value()) + .setMessage("Duplicate command") + .addDetails(definiteAnswers(false)) + .build() + ) + contextualizedErrorLogger.info(exception.getMessage) + exception + }, + v2 = LedgerApiErrors.CommandPreparation.DuplicateCommand.Reject().asGrpcError, ) /** @param expected Expected ledger id. @@ -36,43 +49,68 @@ trait ErrorFactories { expected: LedgerId, received: LedgerId, definiteAnswer: Option[Boolean], - ): StatusRuntimeException = { + )(implicit contextualizedErrorLogger: ContextualizedErrorLogger): StatusRuntimeException = { require(!definiteAnswer.contains(true), "Wrong ledger ID can never be a definite answer.") - val statusBuilder = Status - .newBuilder() - .setCode(Code.NOT_FOUND.value()) - .setMessage( - s"Ledger ID '${received.unwrap}' not found. Actual Ledger ID is '${expected.unwrap}'." - ) - addDefiniteAnswerDetails(definiteAnswer, statusBuilder) - grpcError(statusBuilder.build()) + errorCodesVersionSwitcher.choose( + v1 = { + val statusBuilder = Status + .newBuilder() + .setCode(Code.NOT_FOUND.value()) + .setMessage( + s"Ledger ID '${received.unwrap}' not found. Actual Ledger ID is '${expected.unwrap}'." + ) + addDefiniteAnswerDetails(definiteAnswer, statusBuilder) + grpcError(statusBuilder.build()) + }, + v2 = LedgerApiErrors.CommandValidation.LedgerIdMismatch + .Reject( + s"Ledger ID '${received.unwrap}' not found. Actual Ledger ID is '${expected.unwrap}'." + ) + .asGrpcError, + ) } /** @param fieldName A missing field's name. * @param definiteAnswer A flag that says whether it is a definite answer. Provided only in the context of command deduplication. * @return An exception with the [[Code.INVALID_ARGUMENT]] status code. */ - def missingField(fieldName: String, definiteAnswer: Option[Boolean]): StatusRuntimeException = { - val statusBuilder = Status - .newBuilder() - .setCode(Code.INVALID_ARGUMENT.value()) - .setMessage(s"Missing field: $fieldName") - addDefiniteAnswerDetails(definiteAnswer, statusBuilder) - grpcError(statusBuilder.build()) - } + def missingField(fieldName: String, definiteAnswer: Option[Boolean])(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): StatusRuntimeException = + errorCodesVersionSwitcher.choose( + v1 = { + val statusBuilder = Status + .newBuilder() + .setCode(Code.INVALID_ARGUMENT.value()) + .setMessage(s"Missing field: $fieldName") + addDefiniteAnswerDetails(definiteAnswer, statusBuilder) + grpcError(statusBuilder.build()) + }, + v2 = LedgerApiErrors.CommandValidation.MissingField + .Reject(fieldName) + .asGrpcError, + ) /** @param definiteAnswer A flag that says whether it is a definite answer. Provided only in the context of command deduplication. * @param message A status' message. * @return An exception with the [[Code.INVALID_ARGUMENT]] status code. */ - def invalidArgument(definiteAnswer: Option[Boolean])(message: String): StatusRuntimeException = { - val statusBuilder = Status - .newBuilder() - .setCode(Code.INVALID_ARGUMENT.value()) - .setMessage(s"Invalid argument: $message") - addDefiniteAnswerDetails(definiteAnswer, statusBuilder) - grpcError(statusBuilder.build()) - } + def invalidArgument(definiteAnswer: Option[Boolean])(message: String)(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): StatusRuntimeException = + errorCodesVersionSwitcher.choose( + v1 = { + val statusBuilder = Status + .newBuilder() + .setCode(Code.INVALID_ARGUMENT.value()) + .setMessage(s"Invalid argument: $message") + addDefiniteAnswerDetails(definiteAnswer, statusBuilder) + grpcError(statusBuilder.build()) + }, + v2 = LedgerApiErrors.CommandValidation.InvalidArgument + .Reject(message) + .asGrpcError, + ) /** @param fieldName An invalid field's name. * @param message A status' message. @@ -83,22 +121,34 @@ trait ErrorFactories { fieldName: String, message: String, definiteAnswer: Option[Boolean], - ): StatusRuntimeException = { - val statusBuilder = Status - .newBuilder() - .setCode(Code.INVALID_ARGUMENT.value()) - .setMessage(s"Invalid field $fieldName: $message") - addDefiniteAnswerDetails(definiteAnswer, statusBuilder) - grpcError(statusBuilder.build()) - } + )(implicit contextualizedErrorLogger: ContextualizedErrorLogger): StatusRuntimeException = + errorCodesVersionSwitcher.choose( + v1 = { + val statusBuilder = Status + .newBuilder() + .setCode(Code.INVALID_ARGUMENT.value()) + .setMessage(s"Invalid field $fieldName: $message") + addDefiniteAnswerDetails(definiteAnswer, statusBuilder) + grpcError(statusBuilder.build()) + }, + v2 = LedgerApiErrors.CommandValidation.InvalidField + .Reject(s"Invalid field $fieldName: $message") + .asGrpcError, + ) - def outOfRange(description: String): StatusRuntimeException = - grpcError( - Status - .newBuilder() - .setCode(Code.OUT_OF_RANGE.value()) - .setMessage(description) - .build() + def offsetAfterLedgerEnd(description: String)(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): StatusRuntimeException = + // TODO error codes: Pass the offsets as arguments to this method and build the description here + errorCodesVersionSwitcher.choose( + v1 = grpcError( + Status + .newBuilder() + .setCode(Code.OUT_OF_RANGE.value()) + .setMessage(description) + .build() + ), + v2 = LedgerApiErrors.ReadErrors.RequestedOffsetAfterLedgerEnd.Reject(description).asGrpcError, ) /** @param message A status' message. @@ -106,6 +156,8 @@ trait ErrorFactories { * @return An exception with the [[Code.ABORTED]] status code. */ def aborted(message: String, definiteAnswer: Option[Boolean]): StatusRuntimeException = { + // TODO error codes: This error code is not specific enough. + // Break down into more specific errors. val statusBuilder = Status .newBuilder() .setCode(Code.ABORTED.value()) @@ -115,63 +167,98 @@ trait ErrorFactories { } // permission denied is intentionally without description to ensure we don't leak security relevant information by accident - def permissionDenied(): StatusRuntimeException = - grpcError( + def permissionDenied()(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): StatusRuntimeException = errorCodesVersionSwitcher.choose( + v1 = grpcError( Status .newBuilder() .setCode(Code.PERMISSION_DENIED.value()) .build() - ) + ), + v2 = LedgerApiErrors.AuthorizationChecks.PermissionDenied.Reject().asGrpcError, + ) - def unauthenticated(): StatusRuntimeException = - grpcError( + def unauthenticated()(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): StatusRuntimeException = errorCodesVersionSwitcher.choose( + v1 = grpcError( Status .newBuilder() .setCode(Code.UNAUTHENTICATED.value()) .build() - ) + ), + v2 = LedgerApiErrors.AuthorizationChecks.Unauthenticated.Reject().asGrpcError, + ) /** @param definiteAnswer A flag that says whether it is a definite answer. Provided only in the context of command deduplication. * @return An exception with the [[Code.UNAVAILABLE]] status code. */ - def missingLedgerConfig(definiteAnswer: Option[Boolean]): StatusRuntimeException = { - val statusBuilder = Status - .newBuilder() - .setCode(Code.UNAVAILABLE.value()) - .setMessage("The ledger configuration is not available.") - addDefiniteAnswerDetails(definiteAnswer, statusBuilder) - grpcError(statusBuilder.build()) - } + def missingLedgerConfig(definiteAnswer: Option[Boolean])(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): StatusRuntimeException = + errorCodesVersionSwitcher.choose( + v1 = { + val statusBuilder = Status + .newBuilder() + .setCode(Code.UNAVAILABLE.value()) + .setMessage("The ledger configuration is not available.") + addDefiniteAnswerDetails(definiteAnswer, statusBuilder) + grpcError(statusBuilder.build()) + }, + v2 = LedgerApiErrors.InterpreterErrors.LookupErrors.LedgerConfigurationNotFound + .Reject() + .asGrpcError, + ) - def missingLedgerConfigUponRequest(): StatusRuntimeException = - grpcError( + // TODO error codes: Duplicate of missingLedgerConfig + def missingLedgerConfigUponRequest(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): StatusRuntimeException = errorCodesVersionSwitcher.choose( + v1 = grpcError( Status .newBuilder() .setCode(Code.NOT_FOUND.value()) .setMessage("The ledger configuration is not available.") .build() - ) + ), + v2 = LedgerApiErrors.InterpreterErrors.LookupErrors.LedgerConfigurationNotFound + .Reject() + .asGrpcError, + ) - def participantPrunedDataAccessed(message: String): StatusRuntimeException = - grpcError( - Status - .newBuilder() - .setCode(Code.NOT_FOUND.value()) - .setMessage(message) - .build() + def participantPrunedDataAccessed(message: String)(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): StatusRuntimeException = + errorCodesVersionSwitcher.choose( + v1 = grpcError( + Status + .newBuilder() + .setCode(Code.NOT_FOUND.value()) + .setMessage(message) + .build() + ), + v2 = LedgerApiErrors.ReadErrors.ParticipantPrunedDataAccessed.Reject(message).asGrpcError, ) /** @param definiteAnswer A flag that says whether it is a definite answer. Provided only in the context of command deduplication. * @return An exception with the [[Code.UNAVAILABLE]] status code. */ - def serviceNotRunning(definiteAnswer: Option[Boolean]): StatusRuntimeException = { - val statusBuilder = Status - .newBuilder() - .setCode(Code.UNAVAILABLE.value()) - .setMessage("Service has been shut down.") - addDefiniteAnswerDetails(definiteAnswer, statusBuilder) - grpcError(statusBuilder.build()) - } + def serviceNotRunning(definiteAnswer: Option[Boolean])(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): StatusRuntimeException = + errorCodesVersionSwitcher.choose( + v1 = { + val statusBuilder = Status + .newBuilder() + .setCode(Code.UNAVAILABLE.value()) + .setMessage("Service has been shut down.") + addDefiniteAnswerDetails(definiteAnswer, statusBuilder) + grpcError(statusBuilder.build()) + }, + // TODO error codes: Add service name to the error cause + v2 = LedgerApiErrors.ServiceNotRunning.Reject().asGrpcError, + ) /** Transforms Protobuf [[Status]] objects, possibly including metadata packed as [[ErrorInfo]] objects, * into exceptions with metadata in the trailers. @@ -186,7 +273,14 @@ trait ErrorFactories { ) } -object ErrorFactories extends ErrorFactories { +/** Object exposing the legacy error factories. + * TODO error codes: Remove default implementation once all Ledger API services + * output versioned error codes. + */ +object ErrorFactories extends ErrorFactories(new ErrorCodesVersionSwitcher(false)) { + def apply(errorCodesVersionSwitcher: ErrorCodesVersionSwitcher): ErrorFactories = + new ErrorFactories(errorCodesVersionSwitcher) + private[daml] lazy val definiteAnswers = Map( true -> AnyProto.pack[ErrorInfo]( ErrorInfo.newBuilder().putMetadata(GrpcStatuses.DefiniteAnswerKey, "true").build() diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/FieldValidations.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/FieldValidations.scala index 22c917333bbc..cacef76ef83b 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/FieldValidations.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/FieldValidations.scala @@ -3,66 +3,70 @@ package com.daml.platform.server.api.validation -import com.daml.ledger.api.{DeduplicationPeriod, domain} +import com.daml.error.ContextualizedErrorLogger import com.daml.ledger.api.domain.LedgerId import com.daml.ledger.api.v1.commands.Commands.{DeduplicationPeriod => DeduplicationPeriodProto} import com.daml.ledger.api.v1.value.Identifier +import com.daml.ledger.api.{DeduplicationPeriod, domain} import com.daml.lf.data.Ref import com.daml.lf.data.Ref.Party import com.daml.lf.value.Value.ContractId -import com.daml.platform.server.api.validation.ErrorFactories._ import com.google.protobuf.duration.{Duration => DurationProto} import io.grpc.StatusRuntimeException import java.time.Duration -import scala.util.Try -trait FieldValidations { +// TODO error codes: Remove default usage of ErrorFactories +class FieldValidations private (errorFactories: ErrorFactories) { + import errorFactories._ def matchLedgerId( ledgerId: LedgerId - )(received: LedgerId): Either[StatusRuntimeException, LedgerId] = + )(received: LedgerId)(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): Either[StatusRuntimeException, LedgerId] = if (ledgerId == received) Right(received) else Left(ledgerIdMismatch(ledgerId, received, definiteAnswer = Some(false))) - def requireNonEmptyString(s: String, fieldName: String): Either[StatusRuntimeException, String] = + def requireNonEmptyString(s: String, fieldName: String)(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): Either[StatusRuntimeException, String] = Either.cond(s.nonEmpty, s, missingField(fieldName, definiteAnswer = Some(false))) - def requireIdentifier(s: String): Either[StatusRuntimeException, Ref.Name] = + def requireIdentifier(s: String)(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): Either[StatusRuntimeException, Ref.Name] = Ref.Name.fromString(s).left.map(invalidArgument(definiteAnswer = Some(false))) def requireName( s: String, fieldName: String, + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, Ref.Name] = if (s.isEmpty) Left(missingField(fieldName, definiteAnswer = Some(false))) else Ref.Name.fromString(s).left.map(invalidField(fieldName, _, definiteAnswer = Some(false))) - def requireNumber(s: String, fieldName: String): Either[StatusRuntimeException, Long] = - for { - s <- requireNonEmptyString(s, fieldName) - number <- Try(s.toLong).toEither.left.map(t => - invalidField(fieldName, t.getMessage, definiteAnswer = Some(false)) - ) - } yield number - def requirePackageId( s: String, fieldName: String, + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, Ref.PackageId] = if (s.isEmpty) Left(missingField(fieldName, definiteAnswer = Some(false))) else Ref.PackageId.fromString(s).left.map(invalidField(fieldName, _, definiteAnswer = Some(false))) - def requirePackageId(s: String): Either[StatusRuntimeException, Ref.PackageId] = - Ref.PackageId.fromString(s).left.map(invalidArgument(definiteAnswer = Some(false))) - - def requireParty(s: String): Either[StatusRuntimeException, Ref.Party] = + def requireParty(s: String)(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): Either[StatusRuntimeException, Ref.Party] = Ref.Party.fromString(s).left.map(invalidArgument(definiteAnswer = Some(false))) - def requireParties(parties: Set[String]): Either[StatusRuntimeException, Set[Party]] = + def requireParties(parties: Set[String])(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): Either[StatusRuntimeException, Set[Party]] = parties.foldLeft[Either[StatusRuntimeException, Set[Party]]](Right(Set.empty)) { (acc, partyTxt) => for { @@ -74,6 +78,8 @@ trait FieldValidations { def requireLedgerString( s: String, fieldName: String, + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, Ref.LedgerString] = if (s.isEmpty) Left(missingField(fieldName, definiteAnswer = Some(false))) else @@ -82,10 +88,14 @@ trait FieldValidations { .left .map(invalidField(fieldName, _, definiteAnswer = Some(false))) - def requireLedgerString(s: String): Either[StatusRuntimeException, Ref.LedgerString] = + def requireLedgerString(s: String)(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): Either[StatusRuntimeException, Ref.LedgerString] = Ref.LedgerString.fromString(s).left.map(invalidArgument(definiteAnswer = Some(false))) - def requireSubmissionId(s: String): Either[StatusRuntimeException, domain.SubmissionId] = { + def requireSubmissionId(s: String)(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): Either[StatusRuntimeException, domain.SubmissionId] = { val fieldName = "submission_id" if (s.isEmpty) { Left(missingField(fieldName, definiteAnswer = Some(false))) @@ -101,6 +111,8 @@ trait FieldValidations { def requireContractId( s: String, fieldName: String, + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, ContractId] = if (s.isEmpty) Left(missingField(fieldName, definiteAnswer = Some(false))) else ContractId.fromString(s).left.map(invalidField(fieldName, _, definiteAnswer = Some(false))) @@ -108,17 +120,23 @@ trait FieldValidations { def requireDottedName( s: String, fieldName: String, + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, Ref.DottedName] = Ref.DottedName.fromString(s).left.map(invalidField(fieldName, _, definiteAnswer = Some(false))) def requireNonEmpty[M[_] <: Iterable[_], T]( s: M[T], fieldName: String, + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, M[T]] = if (s.nonEmpty) Right(s) else Left(missingField(fieldName, definiteAnswer = Some(false))) - def requirePresence[T](option: Option[T], fieldName: String): Either[StatusRuntimeException, T] = + def requirePresence[T](option: Option[T], fieldName: String)(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): Either[StatusRuntimeException, T] = option.fold[Either[StatusRuntimeException, T]]( Left(missingField(fieldName, definiteAnswer = Some(false))) )(Right(_)) @@ -129,6 +147,8 @@ trait FieldValidations { deduplicationPeriod: DeduplicationPeriodProto, optMaxDeduplicationTime: Option[Duration], fieldName: String, + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, DeduplicationPeriod] = { optMaxDeduplicationTime.fold[Either[StatusRuntimeException, DeduplicationPeriod]]( @@ -161,7 +181,9 @@ trait FieldValidations { }) } - def validateIdentifier(identifier: Identifier): Either[StatusRuntimeException, Ref.Identifier] = + def validateIdentifier(identifier: Identifier)(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): Either[StatusRuntimeException, Ref.Identifier] = for { packageId <- requirePackageId(identifier.packageId, "package_id") mn <- requireDottedName(identifier.moduleName, "module_name") @@ -170,4 +192,10 @@ trait FieldValidations { } -object FieldValidations extends FieldValidations +/** Default implementation exposing field validations with the legacy error factories. + * TODO error codes: Remove default implementation once all consumers output versioned error codes. + */ +object FieldValidations extends FieldValidations(ErrorFactories) { + def apply(errorFactories: ErrorFactories): FieldValidations = + new FieldValidations(errorFactories) +} diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/LedgerConfigurationServiceValidation.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/LedgerConfigurationServiceValidation.scala index e64505e32168..99129d7c1a47 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/LedgerConfigurationServiceValidation.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/LedgerConfigurationServiceValidation.scala @@ -3,6 +3,7 @@ package com.daml.platform.server.api.validation +import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger} import com.daml.ledger.api.domain.LedgerId import com.daml.ledger.api.v1.ledger_configuration_service.LedgerConfigurationServiceGrpc.LedgerConfigurationService import com.daml.ledger.api.v1.ledger_configuration_service.{ @@ -10,24 +11,26 @@ import com.daml.ledger.api.v1.ledger_configuration_service.{ GetLedgerConfigurationResponse, LedgerConfigurationServiceGrpc, } +import com.daml.logging.{ContextualizedLogger, LoggingContext} import com.daml.platform.api.grpc.GrpcApiService +import com.daml.platform.server.api.validation.FieldValidations.matchLedgerId import com.daml.platform.server.api.{ProxyCloseable, ValidationLogger} import io.grpc.ServerServiceDefinition import io.grpc.stub.StreamObserver -import org.slf4j.{Logger, LoggerFactory} import scala.concurrent.ExecutionContext class LedgerConfigurationServiceValidation( protected val service: LedgerConfigurationService with GrpcApiService, protected val ledgerId: LedgerId, -)(implicit executionContext: ExecutionContext) +)(implicit executionContext: ExecutionContext, loggingContext: LoggingContext) extends LedgerConfigurationService with ProxyCloseable - with GrpcApiService - with FieldValidations { + with GrpcApiService { - protected implicit val logger: Logger = LoggerFactory.getLogger(service.getClass) + protected implicit val logger: ContextualizedLogger = ContextualizedLogger.get(service.getClass) + private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, loggingContext, None) override def getLedgerConfiguration( request: GetLedgerConfigurationRequest, diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/PackageServiceValidation.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/PackageServiceValidation.scala index db23aee8e367..eb844ca8b4db 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/PackageServiceValidation.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/PackageServiceValidation.scala @@ -3,13 +3,15 @@ package com.daml.platform.server.api.validation +import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger} import com.daml.ledger.api.domain.LedgerId import com.daml.ledger.api.v1.package_service.PackageServiceGrpc.PackageService import com.daml.ledger.api.v1.package_service._ +import com.daml.logging.{ContextualizedLogger, LoggingContext} import com.daml.platform.api.grpc.GrpcApiService +import com.daml.platform.server.api.validation.FieldValidations.matchLedgerId import com.daml.platform.server.api.{ProxyCloseable, ValidationLogger} import io.grpc.ServerServiceDefinition -import org.slf4j.{Logger, LoggerFactory} import scala.Function.const import scala.concurrent.{ExecutionContext, Future} @@ -17,13 +19,14 @@ import scala.concurrent.{ExecutionContext, Future} class PackageServiceValidation( protected val service: PackageService with AutoCloseable, val ledgerId: LedgerId, -)(implicit executionContext: ExecutionContext) +)(implicit executionContext: ExecutionContext, loggingContext: LoggingContext) extends PackageService with ProxyCloseable - with GrpcApiService - with FieldValidations { + with GrpcApiService { - protected implicit val logger: Logger = LoggerFactory.getLogger(service.getClass) + protected implicit val logger: ContextualizedLogger = ContextualizedLogger.get(getClass) + private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, loggingContext, None) override def listPackages(request: ListPackagesRequest): Future[ListPackagesResponse] = matchLedgerId(ledgerId)(LedgerId(request.ledgerId)) diff --git a/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/ValueConversionRoundTripTest.scala b/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/ValueConversionRoundTripTest.scala index fed388e22b68..331593ea29f0 100644 --- a/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/ValueConversionRoundTripTest.scala +++ b/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/ValueConversionRoundTripTest.scala @@ -3,6 +3,7 @@ package com.daml.ledger.api +import com.daml.error.NoLogging import com.daml.lf.data.Time import com.daml.ledger.api.v1.value.Value.Sum import com.daml.ledger.api.v1.{value => api} @@ -24,7 +25,7 @@ class ValueConversionRoundTripTest private def roundTrip(v: api.Value): Either[String, api.Value] = for { - lfValue <- ValueValidator.validateValue(v).left.map(_.getMessage) + lfValue <- ValueValidator.validateValue(v)(NoLogging).left.map(_.getMessage) apiValue <- LfEngineToApi.lfValueToApiValue(true, lfValue) } yield apiValue diff --git a/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/CompletionServiceRequestValidatorTest.scala b/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/CompletionServiceRequestValidatorTest.scala index a2a5bd5bc46f..128931877963 100644 --- a/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/CompletionServiceRequestValidatorTest.scala +++ b/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/CompletionServiceRequestValidatorTest.scala @@ -3,6 +3,7 @@ package com.daml.ledger.api.validation +import com.daml.error.{ContextualizedErrorLogger, NoLogging} import com.daml.ledger.api.domain import com.daml.ledger.api.v1.command_completion_service.{ CompletionEndRequest, @@ -14,7 +15,7 @@ import io.grpc.Status.Code._ import org.scalatest.wordspec.AnyWordSpec class CompletionServiceRequestValidatorTest extends AnyWordSpec with ValidatorTestUtils { - + private implicit val noLogging: ContextualizedErrorLogger = NoLogging private val completionReq = CompletionStreamRequest( expectedLedgerId, expectedApplicationId, diff --git a/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/IdentifierValidatorTest.scala b/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/IdentifierValidatorTest.scala index 4840e6a810e7..787853d15a81 100644 --- a/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/IdentifierValidatorTest.scala +++ b/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/IdentifierValidatorTest.scala @@ -3,6 +3,7 @@ package com.daml.ledger.api.validation +import com.daml.error.{ContextualizedErrorLogger, NoLogging} import com.daml.ledger.api.DomainMocks import com.daml.ledger.api.v1.value.Identifier import io.grpc.Status.Code.INVALID_ARGUMENT @@ -10,6 +11,7 @@ import org.scalatest.wordspec.AsyncWordSpec import com.daml.platform.server.api.validation.FieldValidations._ class IdentifierValidatorTest extends AsyncWordSpec with ValidatorTestUtils { + private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = NoLogging object api { val identifier = Identifier("package", moduleName = "module", entityName = "entity") diff --git a/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/SubmitRequestValidatorTest.scala b/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/SubmitRequestValidatorTest.scala index 9b7cabf5cfb0..a9a2ebb3b3d9 100644 --- a/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/SubmitRequestValidatorTest.scala +++ b/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/SubmitRequestValidatorTest.scala @@ -4,6 +4,7 @@ package com.daml.ledger.api.validation import com.daml.api.util.{DurationConversion, TimestampConversion} +import com.daml.error.{ContextualizedErrorLogger, NoLogging} import com.daml.ledger.api.DomainMocks.{applicationId, commandId, submissionId, workflowId} import com.daml.ledger.api.domain.{LedgerId, Commands => ApiCommands} import com.daml.ledger.api.v1.commands.Commands.{DeduplicationPeriod => DeduplicationPeriodProto} @@ -30,8 +31,8 @@ class SubmitRequestValidatorTest extends AnyWordSpec with ValidatorTestUtils with TableDrivenPropertyChecks { - private val ledgerId = LedgerId("ledger-id") + private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = NoLogging private object api { val identifier = Identifier("package", moduleName = "module", entityName = "entity") diff --git a/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/TransactionServiceRequestValidatorTest.scala b/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/TransactionServiceRequestValidatorTest.scala index 609d8344b651..f5de337f0225 100644 --- a/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/TransactionServiceRequestValidatorTest.scala +++ b/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/TransactionServiceRequestValidatorTest.scala @@ -3,6 +3,7 @@ package com.daml.ledger.api.validation +import com.daml.error.{ContextualizedErrorLogger, NoLogging} import com.daml.ledger.api.domain import com.daml.ledger.api.v1.ledger_offset.LedgerOffset import com.daml.ledger.api.v1.ledger_offset.LedgerOffset.LedgerBoundary @@ -18,6 +19,7 @@ import io.grpc.Status.Code._ import org.scalatest.wordspec.AnyWordSpec class TransactionServiceRequestValidatorTest extends AnyWordSpec with ValidatorTestUtils { + private implicit val noLogging: ContextualizedErrorLogger = NoLogging private val txReq = GetTransactionsRequest( expectedLedgerId, diff --git a/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/platform/server/api/services/grpc/GrpcCommandSubmissionServiceSpec.scala b/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/platform/server/api/services/grpc/GrpcCommandSubmissionServiceSpec.scala index 6aa14c469184..1a7e18afef60 100644 --- a/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/platform/server/api/services/grpc/GrpcCommandSubmissionServiceSpec.scala +++ b/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/platform/server/api/services/grpc/GrpcCommandSubmissionServiceSpec.scala @@ -4,7 +4,6 @@ package com.daml.platform.server.api.services.grpc import java.time.{Duration, Instant} - import com.codahale.metrics.MetricRegistry import com.daml.ledger.api.domain.LedgerId import com.daml.ledger.api.messages.command.submission.SubmitRequest @@ -12,6 +11,7 @@ import com.daml.ledger.api.testing.utils.MockMessages._ import com.daml.ledger.api.v1.commands.{Command, CreateCommand} import com.daml.ledger.api.v1.value.{Identifier, Record, RecordField, Value} import com.daml.lf.data.Ref +import com.daml.logging.LoggingContext import com.daml.metrics.Metrics import com.daml.platform.server.api.services.domain.CommandSubmissionService import com.daml.telemetry.{SpanAttribute, TelemetryContext, TelemetrySpecBase} @@ -27,6 +27,7 @@ class GrpcCommandSubmissionServiceSpec with MockitoSugar with Matchers with ArgumentMatchersSugar { + private implicit val loggingContext: LoggingContext = LoggingContext.ForTesting import GrpcCommandSubmissionServiceSpec._ diff --git a/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/platform/server/api/validation/ErrorFactoriesSpec.scala b/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/platform/server/api/validation/ErrorFactoriesSpec.scala index d2944b2d3874..2ba482d91820 100644 --- a/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/platform/server/api/validation/ErrorFactoriesSpec.scala +++ b/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/platform/server/api/validation/ErrorFactoriesSpec.scala @@ -3,10 +3,18 @@ package com.daml +import com.daml.error.{ + DamlContextualizedErrorLogger, + ContextualizedErrorLogger, + ErrorCodesVersionSwitcher, +} import com.daml.ledger.api.domain.LedgerId +import com.daml.logging.{ContextualizedLogger, LoggingContext} +import com.daml.platform.server.api.validation.ErrorFactories import com.daml.platform.server.api.validation.ErrorFactories._ import com.google.rpc.Status import io.grpc.Status.Code +import io.grpc.StatusRuntimeException import io.grpc.protobuf.StatusProto import org.scalatest.matchers.should.Matchers import org.scalatest.prop.TableDrivenPropertyChecks @@ -15,17 +23,34 @@ import org.scalatest.wordspec.AnyWordSpec import scala.jdk.CollectionConverters._ class ErrorFactoriesSpec extends AnyWordSpec with Matchers with TableDrivenPropertyChecks { + private val correlationId = "trace-id" + private val logger = ContextualizedLogger.get(getClass) + private val loggingContext = LoggingContext.ForTesting + + private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, loggingContext, Some(correlationId)) "ErrorFactories" should { "return the DuplicateCommandException" in { - val status = StatusProto.fromThrowable(duplicateCommandException) - status.getCode shouldBe Code.ALREADY_EXISTS.value() - status.getMessage shouldBe "Duplicate command" - status.getDetailsList.asScala shouldBe Seq(definiteAnswers(false)) + assertVersionedError(_.duplicateCommandException)( + v1_code = Code.ALREADY_EXISTS, + v1_message = "Duplicate command", + v1_details = Seq(definiteAnswers(false)), + v2_code = Code.ALREADY_EXISTS, + v2_message = + s"DUPLICATE_COMMAND(10,$correlationId): A command with the given command id has already been successfully processed", + ) } "return a permissionDenied error" in { - permissionDenied().getStatus.getCode shouldBe Code.PERMISSION_DENIED + assertVersionedError(_.permissionDenied())( + v1_code = Code.PERMISSION_DENIED, + v1_message = "", + v1_details = Seq.empty, + v2_code = Code.PERMISSION_DENIED, + v2_message = + s"An error occurred. Please contact the operator and inquire about the request $correlationId", + ) } "return a missingLedgerConfig error" in { @@ -36,14 +61,20 @@ class ErrorFactoriesSpec extends AnyWordSpec with Matchers with TableDrivenPrope ) forEvery(testCases) { (definiteAnswer, expectedDetails) => - val exception = missingLedgerConfig(definiteAnswer) - val status = StatusProto.fromThrowable(exception) - status.getCode shouldBe Code.UNAVAILABLE.value() - status.getDetailsList.asScala shouldBe expectedDetails + assertVersionedError(_.missingLedgerConfig(definiteAnswer))( + v1_code = Code.UNAVAILABLE, + v1_message = "The ledger configuration is not available.", + v1_details = expectedDetails, + v2_code = Code.NOT_FOUND, + v2_message = + s"LEDGER_CONFIGURATION_NOT_FOUND(11,$correlationId): The ledger configuration is not available.", + ) } } "return an aborted error" in { + // TODO error codes: This error code is not specific enough. + // Break down into more specific errors. val testCases = Table( ("definite answer", "expected details"), (None, Seq.empty), @@ -67,16 +98,26 @@ class ErrorFactoriesSpec extends AnyWordSpec with Matchers with TableDrivenPrope ) forEvery(testCases) { (definiteAnswer, expectedDetails) => - val exception = invalidField("my field", "my message", definiteAnswer) - val status = StatusProto.fromThrowable(exception) - status.getCode shouldBe Code.INVALID_ARGUMENT.value() - status.getMessage shouldBe "Invalid field my field: my message" - status.getDetailsList.asScala shouldBe expectedDetails + assertVersionedError(_.invalidField("my field", "my message", definiteAnswer))( + v1_code = Code.INVALID_ARGUMENT, + v1_message = "Invalid field my field: my message", + v1_details = expectedDetails, + v2_code = Code.INVALID_ARGUMENT, + v2_message = + s"INVALID_FIELD(8,$correlationId): The submitted command has a field with invalid value: Invalid field my field: my message", + ) } } "return an unauthenticated error" in { - unauthenticated().getStatus.getCode shouldBe Code.UNAUTHENTICATED + assertVersionedError(_.unauthenticated())( + v1_code = Code.UNAUTHENTICATED, + v1_message = "", + v1_details = Seq.empty, + v2_code = Code.UNAUTHENTICATED, + v2_message = + s"An error occurred. Please contact the operator and inquire about the request $correlationId", + ) } "return a ledgerIdMismatch error" in { @@ -87,11 +128,16 @@ class ErrorFactoriesSpec extends AnyWordSpec with Matchers with TableDrivenPrope ) forEvery(testCases) { (definiteAnswer, expectedDetails) => - val exception = ledgerIdMismatch(LedgerId("expected"), LedgerId("received"), definiteAnswer) - val status = StatusProto.fromThrowable(exception) - status.getCode shouldBe Code.NOT_FOUND.value() - status.getMessage shouldBe "Ledger ID 'received' not found. Actual Ledger ID is 'expected'." - status.getDetailsList.asScala shouldBe expectedDetails + assertVersionedError( + _.ledgerIdMismatch(LedgerId("expected"), LedgerId("received"), definiteAnswer) + )( + v1_code = Code.NOT_FOUND, + v1_message = "Ledger ID 'received' not found. Actual Ledger ID is 'expected'.", + v1_details = expectedDetails, + v2_code = Code.NOT_FOUND, + v2_message = + s"LEDGER_ID_MISMATCH(11,$correlationId): Ledger ID 'received' not found. Actual Ledger ID is 'expected'.", + ) } } @@ -104,15 +150,23 @@ class ErrorFactoriesSpec extends AnyWordSpec with Matchers with TableDrivenPrope } "return a participantPrunedDataAccessed error" in { - val exception = participantPrunedDataAccessed("my message").getStatus - exception.getCode shouldBe Code.NOT_FOUND - exception.getDescription shouldBe "my message" + assertVersionedError(_.participantPrunedDataAccessed("my message"))( + v1_code = Code.NOT_FOUND, + v1_message = "my message", + v1_details = Seq.empty, + v2_code = Code.OUT_OF_RANGE, + v2_message = s"PARTICIPANT_PRUNED_DATA_ACCESSED(12,$correlationId): my message", + ) } - "return an outOfRange error" in { - val exception = outOfRange("my message").getStatus - exception.getCode shouldBe Code.OUT_OF_RANGE - exception.getDescription shouldBe "my message" + "return an offsetAfterLedgerEnd error" in { + assertVersionedError(_.offsetAfterLedgerEnd("my message"))( + v1_code = Code.OUT_OF_RANGE, + v1_message = "my message", + v1_details = Seq.empty, + v2_code = Code.OUT_OF_RANGE, + v2_message = s"REQUESTED_OFFSET_OUT_OF_RANGE(12,$correlationId): my message", + ) } "return a serviceNotRunning error" in { @@ -123,18 +177,25 @@ class ErrorFactoriesSpec extends AnyWordSpec with Matchers with TableDrivenPrope ) forEvery(testCases) { (definiteAnswer, expectedDetails) => - val exception = serviceNotRunning(definiteAnswer) - val status = StatusProto.fromThrowable(exception) - status.getCode shouldBe Code.UNAVAILABLE.value() - status.getMessage shouldBe "Service has been shut down." - status.getDetailsList.asScala shouldBe expectedDetails + assertVersionedError(_.serviceNotRunning(definiteAnswer))( + v1_code = Code.UNAVAILABLE, + v1_message = "Service has been shut down.", + v1_details = expectedDetails, + v2_code = Code.UNAVAILABLE, + v2_message = s"SERVICE_NOT_RUNNING(1,$correlationId): Service has been shut down.", + ) } } "return a missingLedgerConfigUponRequest error" in { - val exception = missingLedgerConfigUponRequest().getStatus - exception.getCode shouldBe Code.NOT_FOUND - exception.getDescription shouldBe "The ledger configuration is not available." + assertVersionedError(_.missingLedgerConfigUponRequest)( + v1_code = Code.NOT_FOUND, + v1_message = "The ledger configuration is not available.", + v1_details = Seq.empty, + v2_code = Code.NOT_FOUND, + v2_message = + s"LEDGER_CONFIGURATION_NOT_FOUND(11,$correlationId): The ledger configuration is not available.", + ) } "return a missingField error" in { @@ -145,11 +206,14 @@ class ErrorFactoriesSpec extends AnyWordSpec with Matchers with TableDrivenPrope ) forEvery(testCases) { (definiteAnswer, expectedDetails) => - val exception = missingField("my field", definiteAnswer) - val status = StatusProto.fromThrowable(exception) - status.getCode shouldBe Code.INVALID_ARGUMENT.value() - status.getMessage shouldBe "Missing field: my field" - status.getDetailsList.asScala shouldBe expectedDetails + assertVersionedError(_.missingField("my field", definiteAnswer))( + v1_code = Code.INVALID_ARGUMENT, + v1_message = "Missing field: my field", + v1_details = expectedDetails, + v2_code = Code.INVALID_ARGUMENT, + v2_message = + s"MISSING_FIELD(8,$correlationId): The submitted command is missing a mandatory field: my field", + ) } } @@ -161,11 +225,14 @@ class ErrorFactoriesSpec extends AnyWordSpec with Matchers with TableDrivenPrope ) forEvery(testCases) { (definiteAnswer, expectedDetails) => - val exception = invalidArgument(definiteAnswer)("my message") - val status = StatusProto.fromThrowable(exception) - status.getCode shouldBe Code.INVALID_ARGUMENT.value() - status.getMessage shouldBe "Invalid argument: my message" - status.getDetailsList.asScala shouldBe expectedDetails + assertVersionedError(_.invalidArgument(definiteAnswer)("my message"))( + v1_code = Code.INVALID_ARGUMENT, + v1_message = "Invalid argument: my message", + v1_details = expectedDetails, + v2_code = Code.INVALID_ARGUMENT, + v2_message = + s"INVALID_ARGUMENT(8,$correlationId): The submitted command has invalid arguments: my message", + ) } } @@ -175,4 +242,32 @@ class ErrorFactoriesSpec extends AnyWordSpec with Matchers with TableDrivenPrope exception.getStackTrace shouldBe Array.empty } } + + private def assertVersionedError( + error: ErrorFactories => StatusRuntimeException + )(v1_code: Code, v1_message: String, v1_details: Seq[Any], v2_code: Code, v2_message: String) = { + val errorFactoriesV1 = ErrorFactories(new ErrorCodesVersionSwitcher(false)) + val errorFactoriesV2 = ErrorFactories(new ErrorCodesVersionSwitcher(true)) + assertV1Error(error(errorFactoriesV1))(v1_code, v1_message, v1_details) + assertV2Error(error(errorFactoriesV2))(v2_code, v2_message) + } + + private def assertV1Error( + statusRuntimeException: StatusRuntimeException + )(expectedCode: Code, expectedMessage: String, expectedDetails: Seq[Any]) = { + val status = StatusProto.fromThrowable(statusRuntimeException) + status.getCode shouldBe expectedCode.value() + status.getMessage shouldBe expectedMessage + status.getDetailsList.asScala shouldBe expectedDetails + } + + private def assertV2Error( + statusRuntimeException: StatusRuntimeException + )(expectedCode: Code, expectedMessage: String) = { + val status = StatusProto.fromThrowable(statusRuntimeException) + status.getCode shouldBe expectedCode.value() + status.getMessage shouldBe expectedMessage + // TODO error codes: Assert error details + // TODO error codes: Assert logging + } } diff --git a/ledger/participant-integration-api/src/main/scala/platform/apiserver/ApiServices.scala b/ledger/participant-integration-api/src/main/scala/platform/apiserver/ApiServices.scala index 240efff9e118..d3ccb7ce8593 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/apiserver/ApiServices.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/apiserver/ApiServices.scala @@ -5,6 +5,7 @@ package com.daml.platform.apiserver import akka.stream.Materializer import com.daml.api.util.TimeProvider +import com.daml.error.ErrorCodesVersionSwitcher import com.daml.grpc.adapter.ExecutionSequencerFactory import com.daml.ledger.api.auth.Authorizer import com.daml.ledger.api.auth.services._ diff --git a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiActiveContractsService.scala b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiActiveContractsService.scala index 30777abb758a..2bf4fc1401b6 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiActiveContractsService.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiActiveContractsService.scala @@ -6,6 +6,7 @@ package com.daml.platform.apiserver.services import akka.NotUsed import akka.stream.Materializer import akka.stream.scaladsl.Source +import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger} import com.daml.grpc.adapter.ExecutionSequencerFactory import com.daml.ledger.api.domain.LedgerId import com.daml.ledger.api.v1.active_contracts_service.ActiveContractsServiceGrpc.ActiveContractsService @@ -34,6 +35,8 @@ private[apiserver] final class ApiActiveContractsService private ( with GrpcApiService { private implicit val logger: ContextualizedLogger = ContextualizedLogger.get(this.getClass) + private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, loggingContext, None) override protected def getActiveContractsSource( request: GetActiveContractsRequest diff --git a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiCommandService.scala b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiCommandService.scala index a74cb89db2bc..32210449df08 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiCommandService.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiCommandService.scala @@ -7,6 +7,7 @@ import akka.NotUsed import akka.stream.Materializer import akka.stream.scaladsl.{Flow, Keep, Source} import com.daml.api.util.TimeProvider +import com.daml.error.DamlContextualizedErrorLogger import com.daml.ledger.api.SubmissionIdGenerator import com.daml.ledger.api.domain.LedgerId import com.daml.ledger.api.v1.command_completion_service.{ @@ -77,6 +78,7 @@ private[apiserver] final class ApiCommandService private[services] ( private def submitAndWaitInternal(request: SubmitAndWaitRequest)(implicit loggingContext: LoggingContext ): Future[Either[TrackedCompletionFailure, CompletionSuccess]] = { + val contextualizedErrorLogger = new DamlContextualizedErrorLogger(logger, loggingContext, None) val commands = request.getCommands withEnrichedLoggingContext( logging.submissionId(commands.submissionId), @@ -91,7 +93,7 @@ private[apiserver] final class ApiCommandService private[services] ( submissionTracker.track(CommandSubmission(commands, timeout)) } else { Future.failed( - ErrorFactories.serviceNotRunning(definiteAnswer = Some(false)) + ErrorFactories.serviceNotRunning(definiteAnswer = Some(false))(contextualizedErrorLogger) ) }.andThen(logger.logErrorsOnCall[Completion]) } diff --git a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiLedgerIdentityService.scala b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiLedgerIdentityService.scala index 8a136f43540b..b4adae9f6590 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiLedgerIdentityService.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiLedgerIdentityService.scala @@ -3,6 +3,7 @@ package com.daml.platform.apiserver.services +import com.daml.error.{DamlContextualizedErrorLogger, ContextualizedErrorLogger} import com.daml.ledger.api.domain.LedgerId import com.daml.ledger.api.v1.ledger_identity_service.LedgerIdentityServiceGrpc.{ LedgerIdentityService => GrpcLedgerIdentityService @@ -25,11 +26,12 @@ private[apiserver] final class ApiLedgerIdentityService private ( )(implicit executionContext: ExecutionContext, loggingContext: LoggingContext) extends GrpcLedgerIdentityService with GrpcApiService { + private val logger = ContextualizedLogger.get(this.getClass) + private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, loggingContext, None) @volatile var closed = false - private val logger = ContextualizedLogger.get(this.getClass) - override def getLedgerIdentity( request: GetLedgerIdentityRequest ): Future[GetLedgerIdentityResponse] = { diff --git a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiSubmissionService.scala b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiSubmissionService.scala index 290cd55917fb..8a90e8c4f456 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiSubmissionService.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiSubmissionService.scala @@ -4,8 +4,13 @@ package com.daml.platform.apiserver.services import com.daml.api.util.TimeProvider -import com.daml.error.definitions.{ErrorCauseExport, LedgerApiErrors, RejectionGenerators} -import com.daml.error.{DamlErrorCodeLoggingContext, ErrorCause} +import com.daml.error.definitions.{ErrorCauseExport, RejectionGenerators} +import com.daml.error.{ + ContextualizedErrorLogger, + DamlContextualizedErrorLogger, + ErrorCause, + ErrorCodesVersionSwitcher, +} import com.daml.ledger.api.domain.{LedgerId, Commands => ApiCommands} import com.daml.ledger.api.messages.command.submission.SubmitRequest import com.daml.ledger.api.{DeduplicationPeriod, SubmissionIdGenerator} @@ -21,9 +26,9 @@ import com.daml.logging.LoggingContext.withEnrichedLoggingContext import com.daml.logging.{ContextualizedLogger, LoggingContext} import com.daml.metrics.Metrics import com.daml.platform.api.grpc.GrpcApiService +import com.daml.platform.apiserver.SeedService import com.daml.platform.apiserver.configuration.LedgerConfigurationSubscription import com.daml.platform.apiserver.execution.{CommandExecutionResult, CommandExecutor} -import com.daml.platform.apiserver.{ErrorCodesVersionSwitcher, SeedService} import com.daml.platform.server.api.services.domain.CommandSubmissionService import com.daml.platform.server.api.services.grpc.GrpcCommandSubmissionService import com.daml.platform.server.api.validation.ErrorFactories @@ -99,16 +104,16 @@ private[apiserver] final class ApiSubmissionService private[services] ( commandExecutor: CommandExecutor, configuration: ApiSubmissionService.Configuration, metrics: Metrics, - errorCodesVersionSwitcher: ErrorCodesVersionSwitcher, + val errorCodesVersionSwitcher: ErrorCodesVersionSwitcher, )(implicit executionContext: ExecutionContext, loggingContext: LoggingContext) extends CommandSubmissionService - with ErrorFactories with AutoCloseable { private val logger = ContextualizedLogger.get(this.getClass) // TODO error codes: review conformance mode usages wherever RejectionGenerators is instantiated private val rejectionGenerators = new RejectionGenerators(conformanceMode = true) + private val errorFactories = ErrorFactories(errorCodesVersionSwitcher) override def submit( request: SubmitRequest @@ -130,10 +135,13 @@ private[apiserver] final class ApiSubmissionService private[services] ( .transform(handleSubmissionResult) } case None => - failedOnMissingLedgerConfiguration() + Future.failed( + errorFactories.missingLedgerConfig(definiteAnswer = Some(false))( + new DamlContextualizedErrorLogger(logger, loggingContext, None) + ) + ) } - evaluatedCommand - .andThen(logger.logErrorsOnCall[Unit]) + evaluatedCommand.andThen(logger.logErrorsOnCall[Unit]) } private def deduplicateAndRecordOnLedger( @@ -162,7 +170,11 @@ private[apiserver] final class ApiSubmissionService private[services] ( } case _: CommandDeduplicationDuplicate => metrics.daml.commands.deduplicatedCommands.mark() - failedOnDuplicateCommand() + Future.failed( + errorFactories.duplicateCommandException( + new DamlContextualizedErrorLogger(logger, loggingContext, None) + ) + ) } private def handleSubmissionResult(result: Try[state.SubmissionResult])(implicit @@ -291,7 +303,11 @@ private[apiserver] final class ApiSubmissionService private[services] ( /** This method encodes logic related to legacy error codes (V1). * Cf. self-service error codes (V2) in //ledger/error */ - private def toStatusExceptionV1(errorCause: ErrorCause): StatusRuntimeException = + private def toStatusExceptionV1( + errorCause: ErrorCause + )(implicit contextualizedErrorLogger: ContextualizedErrorLogger): StatusRuntimeException = { + // Explicitly instantiate here a V1-enabled ErrorFactories to support the legacy error code dispatching logic. + val v1ErrorFactories = ErrorFactories(new ErrorCodesVersionSwitcher(false)) errorCause match { case cause @ ErrorCause.DamlLf(error) => error match { @@ -302,47 +318,25 @@ private[apiserver] final class ApiSubmissionService private[services] ( ), _, ) | LfError.Validation(LfError.Validation.ReplayMismatch(_)) => - ErrorFactories.aborted(cause.explain, definiteAnswer = Some(false)) + v1ErrorFactories.aborted(cause.explain, definiteAnswer = Some(false)) case _ => - ErrorFactories.invalidArgument(definiteAnswer = Some(false))(cause.explain) + v1ErrorFactories.invalidArgument(definiteAnswer = Some(false))(cause.explain) } case cause: ErrorCause.LedgerTime => - ErrorFactories.aborted(cause.explain, definiteAnswer = Some(false)) + v1ErrorFactories.aborted(cause.explain, definiteAnswer = Some(false)) } - - private def failedOnMissingLedgerConfiguration()(implicit - loggingContext: LoggingContext - ): Future[Unit] = { - errorCodesVersionSwitcher.chooseAsFailedFuture( - v1 = ErrorFactories.missingLedgerConfig(definiteAnswer = Some(false)), - v2 = LedgerApiErrors.InterpreterErrors.LookupErrors.LedgerConfigurationNotFound - .Reject()(new DamlErrorCodeLoggingContext(logger, loggingContext, None)) - .asGrpcError, - ) - } - - private def failedOnDuplicateCommand()(implicit loggingContext: LoggingContext): Future[Unit] = { - errorCodesVersionSwitcher.chooseAsFailedFuture( - v1 = { - val exception = duplicateCommandException - logger.debug(exception.getMessage) - exception - }, - v2 = rejectionGenerators.duplicateCommand( - new DamlErrorCodeLoggingContext(logger, loggingContext, None) - ), - ) } private def failedOnCommandExecution( error: ErrorCause )(implicit loggingContext: LoggingContext): Future[CommandExecutionResult] = { + implicit val contextualizedErrorLogger: ContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, loggingContext, None) + errorCodesVersionSwitcher.chooseAsFailedFuture( v1 = toStatusExceptionV1(error), v2 = rejectionGenerators - .commandExecutorError(cause = ErrorCauseExport.fromErrorCause(error))( - new DamlErrorCodeLoggingContext(logger, loggingContext, None) - ), + .commandExecutorError(cause = ErrorCauseExport.fromErrorCause(error)), ) } diff --git a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiTimeService.scala b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiTimeService.scala index 4a38b055dcb9..e176ff2ad766 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiTimeService.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/ApiTimeService.scala @@ -7,6 +7,7 @@ import akka.NotUsed import akka.stream.Materializer import akka.stream.scaladsl.Source import com.daml.api.util.TimestampConversion._ +import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger} import com.daml.grpc.adapter.ExecutionSequencerFactory import com.daml.ledger.api.domain.LedgerId import com.daml.ledger.api.v1.testing.time_service.TimeServiceGrpc.TimeService @@ -16,7 +17,8 @@ import com.daml.platform.akkastreams.dispatcher.SignalDispatcher import com.daml.platform.api.grpc.GrpcApiService import com.daml.platform.apiserver.TimeServiceBackend import com.daml.platform.server.api.ValidationLogger -import com.daml.platform.server.api.validation.{ErrorFactories, FieldValidations} +import com.daml.platform.server.api.validation.ErrorFactories +import com.daml.platform.server.api.validation.FieldValidations._ import com.google.protobuf.empty.Empty import io.grpc.{ServerServiceDefinition, StatusRuntimeException} import scalaz.syntax.tag._ @@ -33,10 +35,11 @@ private[apiserver] final class ApiTimeService private ( executionContext: ExecutionContext, loggingContext: LoggingContext, ) extends TimeServiceAkkaGrpc - with FieldValidations with GrpcApiService { - private implicit val logger: ContextualizedLogger = ContextualizedLogger.get(this.getClass) + private implicit val logger: ContextualizedLogger = ContextualizedLogger.get(getClass) + private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, loggingContext, None) logger.debug( s"${getClass.getSimpleName} initialized with ledger ID ${ledgerId.unwrap}, start time ${backend.getCurrentTime}" diff --git a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiConfigManagementService.scala b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiConfigManagementService.scala index 4630c080bf6e..647295c8d36b 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiConfigManagementService.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiConfigManagementService.scala @@ -4,10 +4,10 @@ package com.daml.platform.apiserver.services.admin import java.time.{Duration => JDuration} - import akka.stream.Materializer import akka.stream.scaladsl.Source import com.daml.api.util.{DurationConversion, TimeProvider, TimestampConversion} +import com.daml.error.{DamlContextualizedErrorLogger, ContextualizedErrorLogger} import com.daml.ledger.api.domain import com.daml.ledger.api.domain.{ConfigurationEntry, LedgerOffset} import com.daml.ledger.api.v1.admin.config_management_service.ConfigManagementServiceGrpc.ConfigManagementService @@ -43,6 +43,8 @@ private[apiserver] final class ApiConfigManagementService private ( ) extends ConfigManagementService with GrpcApiService { private implicit val logger: ContextualizedLogger = ContextualizedLogger.get(this.getClass) + private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, loggingContext, None) override def close(): Unit = () @@ -57,10 +59,8 @@ private[apiserver] final class ApiConfigManagementService private ( case Some((_, configuration)) => Future.successful(configurationToResponse(configuration)) case None => - logger.warn( - "Could not get the current time model. The index does not yet have any ledger configuration." - ) - Future.failed(ErrorFactories.missingLedgerConfigUponRequest()) + // TODO error codes: Duplicate of missingLedgerConfig + Future.failed(ErrorFactories.missingLedgerConfigUponRequest) } .andThen(logger.logErrorsOnCall[GetTimeModelResponse]) } @@ -86,6 +86,8 @@ private[apiserver] final class ApiConfigManagementService private ( implicit val telemetryContext: TelemetryContext = DefaultTelemetry.contextFromGrpcThreadLocalContext() + implicit val contextualizedErrorLogger: ContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, loggingContext, None) val response = for { // Validate and convert the request parameters @@ -157,6 +159,8 @@ private[apiserver] final class ApiConfigManagementService private ( private def validateParameters( request: SetTimeModelRequest + )(implicit + contextualizedErrorLogger: ContextualizedErrorLogger ): Either[StatusRuntimeException, SetTimeModelParameters] = { import validation.FieldValidations._ for { diff --git a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiPackageManagementService.scala b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiPackageManagementService.scala index 51b6c4d85726..8425c50c92e3 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiPackageManagementService.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiPackageManagementService.scala @@ -5,10 +5,10 @@ package com.daml.platform.apiserver.services.admin import java.time.Duration import java.util.zip.ZipInputStream - import akka.stream.Materializer import akka.stream.scaladsl.Source import com.daml.daml_lf_dev.DamlLf.Archive +import com.daml.error.{DamlContextualizedErrorLogger, ContextualizedErrorLogger} import com.daml.ledger.api.domain.{LedgerOffset, PackageEntry} import com.daml.ledger.api.v1.admin.package_management_service.PackageManagementServiceGrpc.PackageManagementService import com.daml.ledger.api.v1.admin.package_management_service._ @@ -55,6 +55,8 @@ private[apiserver] final class ApiPackageManagementService private ( with GrpcApiService { private implicit val logger: ContextualizedLogger = ContextualizedLogger.get(this.getClass) + private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, loggingContext, None) private val synchronousResponse = new SynchronousResponse( new SynchronousResponseStrategy( @@ -171,6 +173,9 @@ private[apiserver] object ApiPackageManagementService { PackageEntry, PackageEntry.PackageUploadAccepted, ] { + private implicit val logger: ContextualizedLogger = ContextualizedLogger.get(this.getClass) + private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, loggingContext, None) override def currentLedgerEnd(): Future[Option[LedgerOffset.Absolute]] = ledgerEndService.currentLedgerEnd().map(Some(_)) diff --git a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiParticipantPruningService.scala b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiParticipantPruningService.scala index baa952b540b1..1c480b6a0658 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiParticipantPruningService.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiParticipantPruningService.scala @@ -3,8 +3,9 @@ package com.daml.platform.apiserver.services.admin -import java.util.UUID +import com.daml.error.{DamlContextualizedErrorLogger, ContextualizedErrorLogger} +import java.util.UUID import com.daml.ledger.api.v1.admin.participant_pruning_service.{ ParticipantPruningServiceGrpc, PruneRequest, @@ -34,6 +35,8 @@ final class ApiParticipantPruningService private ( with GrpcApiService { private implicit val logger: ContextualizedLogger = ContextualizedLogger.get(this.getClass) + private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, logCtx, None) override def bindService(): ServerServiceDefinition = ParticipantPruningServiceGrpc.bindService(this, executionContext) @@ -47,7 +50,7 @@ final class ApiParticipantPruningService private ( .map(err => ErrorFactories.invalidArgument(None)(s"submission_id $err")) submissionIdOrErr.fold( - t => Future.failed(ValidationLogger.logFailure(request, t)(logger.withoutContext)), + t => Future.failed(ValidationLogger.logFailure(request, t)), submissionId => LoggingContext.withEnrichedLoggingContext(logging.submissionId(submissionId)) { implicit logCtx => @@ -132,6 +135,7 @@ final class ApiParticipantPruningService private ( .toEither .left .map(t => + // TODO error codes: Use LedgerApiErrors.NonHexOffset ErrorFactories.invalidArgument(None)( s"prune_up_to needs to be a hexadecimal string and not $pruneUpToString: ${t.getMessage}" ) @@ -147,6 +151,7 @@ final class ApiParticipantPruningService private ( if (pruneUpToString < ledgerEnd.value) Future.successful(()) else Future.failed( + // TODO error codes: Use LedgerApiErrors.ReadErrors.requestedOffsetAfterLedgerEnd ErrorFactories.invalidArgument(None)( s"prune_up_to needs to be before ledger end ${ledgerEnd.value}" ) diff --git a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiPartyManagementService.scala b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiPartyManagementService.scala index 267cb46f46f2..8d776dc6c0c5 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiPartyManagementService.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/admin/ApiPartyManagementService.scala @@ -5,9 +5,9 @@ package com.daml.platform.apiserver.services.admin import java.time.Duration import java.util.UUID - import akka.stream.Materializer import akka.stream.scaladsl.Source +import com.daml.error.{DamlContextualizedErrorLogger, ContextualizedErrorLogger} import com.daml.ledger.api.domain.{LedgerOffset, PartyEntry} import com.daml.ledger.api.v1.admin.party_management_service.PartyManagementServiceGrpc.PartyManagementService import com.daml.ledger.api.v1.admin.party_management_service._ @@ -45,6 +45,8 @@ private[apiserver] final class ApiPartyManagementService private ( with GrpcApiService { private implicit val logger: ContextualizedLogger = ContextualizedLogger.get(this.getClass) + private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, loggingContext, None) private val synchronousResponse = new SynchronousResponse( new SynchronousResponseStrategy(transactionService, writeService, partyManagementService), @@ -178,6 +180,9 @@ private[apiserver] object ApiPartyManagementService { PartyEntry, PartyEntry.AllocationAccepted, ] { + private val logger = ContextualizedLogger.get(getClass) + private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, loggingContext, None) override def currentLedgerEnd(): Future[Option[LedgerOffset.Absolute]] = ledgerEndService.currentLedgerEnd().map(Some(_)) diff --git a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/transaction/ApiTransactionService.scala b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/transaction/ApiTransactionService.scala index 6d38168360d8..a7ee8cd9577c 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/transaction/ApiTransactionService.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/apiserver/services/transaction/ApiTransactionService.scala @@ -6,7 +6,7 @@ package com.daml.platform.apiserver.services.transaction import akka.NotUsed import akka.stream.Materializer import akka.stream.scaladsl.Source -import com.daml.error.DamlErrorCodeLoggingContext +import com.daml.error.{DamlContextualizedErrorLogger, ErrorCodesVersionSwitcher} import com.daml.error.definitions.LedgerApiErrors import com.daml.grpc.adapter.ExecutionSequencerFactory import com.daml.ledger.api.domain.{ @@ -31,12 +31,10 @@ import com.daml.logging.LoggingContext.withEnrichedLoggingContext import com.daml.logging.entries.LoggingEntries import com.daml.logging.{ContextualizedLogger, LoggingContext} import com.daml.metrics.Metrics -import com.daml.platform.apiserver.ErrorCodesVersionSwitcher import com.daml.platform.apiserver.services.transaction.ApiTransactionService._ import com.daml.platform.apiserver.services.{StreamMetrics, logging} import com.daml.platform.server.api.services.domain.TransactionService import com.daml.platform.server.api.services.grpc.GrpcTransactionService -import com.daml.platform.server.api.validation.ErrorFactories import io.grpc._ import scalaz.syntax.tag._ @@ -72,10 +70,10 @@ private[apiserver] object ApiTransactionService { private[apiserver] final class ApiTransactionService private ( transactionsService: IndexTransactionsService, metrics: Metrics, - errorsVersionsSwitcher: ErrorCodesVersionSwitcher, + errorCodesVersionSwitcher: ErrorCodesVersionSwitcher, )(implicit executionContext: ExecutionContext, loggingContext: LoggingContext) - extends TransactionService - with ErrorFactories { + extends TransactionService { + private val logger: ContextualizedLogger = ContextualizedLogger.get(this.getClass) override def getLedgerEnd(ledgerId: String): Future[LedgerOffset.Absolute] = @@ -145,12 +143,12 @@ private[apiserver] final class ApiTransactionService private ( .getOrElse { val msg = s"invalid eventId: ${request.eventId}" Future.failed( - errorsVersionsSwitcher.choose( + errorCodesVersionSwitcher.choose( v1 = Status.NOT_FOUND .withDescription(msg) .asRuntimeException(), v2 = LedgerApiErrors.CommandValidation.InvalidArgument - .Reject(msg)(new DamlErrorCodeLoggingContext(logger, loggingContext, None)) + .Reject(msg)(new DamlContextualizedErrorLogger(logger, loggingContext, None)) .asGrpcError, ) ) diff --git a/ledger/participant-integration-api/src/main/scala/platform/index/LedgerBackedIndexService.scala b/ledger/participant-integration-api/src/main/scala/platform/index/LedgerBackedIndexService.scala index 687057344633..6439948a93f2 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/index/LedgerBackedIndexService.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/index/LedgerBackedIndexService.scala @@ -4,11 +4,11 @@ package com.daml.platform.index import java.time.Instant - import akka.NotUsed import akka.stream.scaladsl.Source import com.daml.daml_lf_dev.DamlLf.Archive import com.daml.dec.{DirectExecutionContext => DEC} +import com.daml.error.DamlContextualizedErrorLogger import com.daml.ledger.api.domain import com.daml.ledger.api.domain.ConfigurationEntry.Accepted import com.daml.ledger.api.domain.{ @@ -39,7 +39,7 @@ import com.daml.lf.data.Ref.{Identifier, PackageId, Party} import com.daml.lf.language.Ast import com.daml.lf.transaction.GlobalKey import com.daml.lf.value.Value.{ContractId, VersionedContractInstance} -import com.daml.logging.LoggingContext +import com.daml.logging.{ContextualizedLogger, LoggingContext} import com.daml.platform.ApiOffset import com.daml.platform.ApiOffset.ApiOffsetConverter import com.daml.platform.server.api.validation.ErrorFactories @@ -54,6 +54,7 @@ private[platform] final class LedgerBackedIndexService( ledger: ReadOnlyLedger, participantId: Ref.ParticipantId, ) extends IndexService { + private val logger = ContextualizedLogger.get(getClass) override def getLedgerId()(implicit loggingContext: LoggingContext): Future[LedgerId] = Future.successful(ledger.ledgerId) @@ -142,9 +143,10 @@ private[platform] final class LedgerBackedIndexService( Source.empty case Some(end) if begin > end => Source.failed( + // TODO error codes: Replace with LedgerApiErrors.ReadErrors.RequestedOffsetAfterLedgerEnd ErrorFactories.invalidArgument(None)( s"End offset ${end.toApiString} is before Begin offset ${begin.toApiString}." - ) + )(new DamlContextualizedErrorLogger(logger, loggingContext, None)) ) case endOpt: Option[Offset] => f(Some(begin), endOpt) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/appendonlydao/events/QueryNonPruned.scala b/ledger/participant-integration-api/src/main/scala/platform/store/appendonlydao/events/QueryNonPruned.scala index 251a18016794..7d8633fd3038 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/appendonlydao/events/QueryNonPruned.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/appendonlydao/events/QueryNonPruned.scala @@ -3,8 +3,9 @@ package com.daml.platform.store.appendonlydao.events -import java.sql.Connection +import com.daml.error.NoLogging +import java.sql.Connection import com.daml.ledger.offset.Offset import com.daml.platform.server.api.validation.ErrorFactories import com.daml.platform.store.backend.ParameterStorageBackend @@ -45,7 +46,11 @@ case class QueryNonPrunedImpl(storageBackend: ParameterStorageBackend) extends Q result case Some(pruningOffsetUpToInclusive) => - throw ErrorFactories.participantPrunedDataAccessed(error(pruningOffsetUpToInclusive)) + // TODO error codes: Do not throw + // TODO error codes: Enable logging + throw ErrorFactories.participantPrunedDataAccessed(error(pruningOffsetUpToInclusive))( + NoLogging + ) } } } diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/backend/postgresql/PostgresStorageBackend.scala b/ledger/participant-integration-api/src/main/scala/platform/store/backend/postgresql/PostgresStorageBackend.scala index 373dc952f566..0fb1ce24b05f 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/backend/postgresql/PostgresStorageBackend.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/backend/postgresql/PostgresStorageBackend.scala @@ -7,6 +7,7 @@ import java.sql.Connection import java.time.Instant import anorm.SQL import anorm.SqlParser.{get, int} +import com.daml.error.NoLogging import com.daml.ledger.offset.Offset import com.daml.lf.data.Ref import com.daml.logging.{ContextualizedLogger, LoggingContext} @@ -20,12 +21,12 @@ import com.daml.platform.store.backend.common.{ ConfigurationStorageBackendTemplate, ContractStorageBackendTemplate, DataSourceStorageBackendTemplate, - IntegrityStorageBackendTemplate, DeduplicationStorageBackendTemplate, EventStorageBackendTemplate, EventStrategy, IngestionStorageBackendTemplate, InitHookDataSourceProxy, + IntegrityStorageBackendTemplate, PackageStorageBackendTemplate, ParameterStorageBackendTemplate, PartyStorageBackendTemplate, @@ -145,9 +146,10 @@ private[backend] object PostgresStorageBackend """ .as(int("result").singleOpt)(connection) .foreach(_ => + // TODO error codes: Use specialized error and enable logging throw ErrorFactories.invalidArgument(None)( "Pruning offset for all divulged contracts needs to be after the migration offset" - ) + )(NoLogging) ) } diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/dao/events/QueryNonPruned.scala b/ledger/participant-integration-api/src/main/scala/platform/store/dao/events/QueryNonPruned.scala index 69d7a5232402..62c6026727c4 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/dao/events/QueryNonPruned.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/dao/events/QueryNonPruned.scala @@ -4,8 +4,8 @@ package com.daml.platform.store.dao.events import java.sql.Connection - import anorm.SQL +import com.daml.error.NoLogging import com.daml.ledger.offset.Offset import com.daml.platform.server.api.validation.ErrorFactories import com.daml.platform.store.Conversions.offset @@ -38,7 +38,9 @@ object QueryNonPruned { Either.cond( minOffsetExclusive >= pruningOffsetUpToInclusive, result, - ErrorFactories.participantPrunedDataAccessed(error(pruningOffsetUpToInclusive)), + ErrorFactories.participantPrunedDataAccessed(error(pruningOffsetUpToInclusive))( + NoLogging + ), ) ) } diff --git a/ledger/participant-integration-api/src/test/suite/scala/platform/apiserver/services/ApiSubmissionServiceSpec.scala b/ledger/participant-integration-api/src/test/suite/scala/platform/apiserver/services/ApiSubmissionServiceSpec.scala index 1ff1d329ab34..6ff685a6b757 100644 --- a/ledger/participant-integration-api/src/test/suite/scala/platform/apiserver/services/ApiSubmissionServiceSpec.scala +++ b/ledger/participant-integration-api/src/test/suite/scala/platform/apiserver/services/ApiSubmissionServiceSpec.scala @@ -4,7 +4,7 @@ package com.daml.platform.apiserver.services import com.codahale.metrics.MetricRegistry -import com.daml.error.ErrorCause +import com.daml.error.{ErrorCause, ErrorCodesVersionSwitcher} import com.daml.ledger.api.domain.{CommandId, Commands, LedgerId, PartyDetails, SubmissionId} import com.daml.ledger.api.messages.command.submission.SubmitRequest import com.daml.ledger.api.testing.utils.AkkaBeforeAndAfterAll @@ -34,7 +34,7 @@ import com.daml.metrics.Metrics import com.daml.platform.apiserver.configuration.LedgerConfigurationSubscription import com.daml.platform.apiserver.execution.CommandExecutor import com.daml.platform.apiserver.services.ApiSubmissionServiceSpec._ -import com.daml.platform.apiserver.{ErrorCodesVersionSwitcher, SeedService} +import com.daml.platform.apiserver.SeedService import com.daml.telemetry.{NoOpTelemetryContext, TelemetryContext} import com.google.rpc.status.{Status => RpcStatus} import io.grpc.Status diff --git a/ledger/sandbox-common/BUILD.bazel b/ledger/sandbox-common/BUILD.bazel index 1d656994b2fa..df825bcec09a 100644 --- a/ledger/sandbox-common/BUILD.bazel +++ b/ledger/sandbox-common/BUILD.bazel @@ -34,6 +34,7 @@ da_scala_library( "//ledger-service/jwt", "//ledger/caching", "//ledger/cli-opts", + "//ledger/error", "//ledger/ledger-api-auth", "//ledger/ledger-api-common", "//ledger/ledger-api-domain", diff --git a/ledger/sandbox-common/src/main/scala/platform/sandbox/services/SandboxResetService.scala b/ledger/sandbox-common/src/main/scala/platform/sandbox/services/SandboxResetService.scala index 3ca8fe3ff2a0..3133b1a98bb8 100644 --- a/ledger/sandbox-common/src/main/scala/platform/sandbox/services/SandboxResetService.scala +++ b/ledger/sandbox-common/src/main/scala/platform/sandbox/services/SandboxResetService.scala @@ -4,8 +4,8 @@ package com.daml.platform.sandbox.services import java.util.concurrent.atomic.AtomicBoolean - import com.daml.dec.{DirectExecutionContext => DE} +import com.daml.error.DamlContextualizedErrorLogger import com.daml.ledger.api.auth.Authorizer import com.daml.ledger.api.domain.LedgerId import com.daml.ledger.api.v1.testing.reset_service.{ResetRequest, ResetServiceGrpc} @@ -27,6 +27,8 @@ class SandboxResetService( with ServerInterceptor { private val logger = ContextualizedLogger.get(this.getClass) + private implicit val contextualizedErrorLogger: DamlContextualizedErrorLogger = + new DamlContextualizedErrorLogger(logger, loggingContext, None) private val resetInitialized = new AtomicBoolean(false) diff --git a/navigator/backend/src/main/scala/com/digitalasset/navigator/model/converter/LedgerApiV1.scala b/navigator/backend/src/main/scala/com/digitalasset/navigator/model/converter/LedgerApiV1.scala index 3ae908db6aed..0d15bfad7419 100644 --- a/navigator/backend/src/main/scala/com/digitalasset/navigator/model/converter/LedgerApiV1.scala +++ b/navigator/backend/src/main/scala/com/digitalasset/navigator/model/converter/LedgerApiV1.scala @@ -11,7 +11,7 @@ import com.daml.lf.iface import com.daml.lf.value.{Value => V} import com.daml.ledger.api.{v1 => V1} import com.daml.ledger.api.refinements.ApiTypes -import com.daml.ledger.api.validation.ValueValidator.{validateRecord, validateValue} +import com.daml.ledger.api.validation.NoLoggingValueValidator.{validateRecord, validateValue} import com.daml.navigator.{model => Model} import com.daml.navigator.model.{IdentifierApiConversions, IdentifierDamlConversions} import com.daml.platform.participant.util.LfEngineToApi.{lfValueToApiRecord, lfValueToApiValue} diff --git a/triggers/runner/src/main/scala/com/digitalasset/daml/lf/engine/trigger/Converter.scala b/triggers/runner/src/main/scala/com/digitalasset/daml/lf/engine/trigger/Converter.scala index 23f5b4b9171a..d29dddca618d 100644 --- a/triggers/runner/src/main/scala/com/digitalasset/daml/lf/engine/trigger/Converter.scala +++ b/triggers/runner/src/main/scala/com/digitalasset/daml/lf/engine/trigger/Converter.scala @@ -24,7 +24,7 @@ import com.daml.ledger.api.v1.completion.Completion import com.daml.ledger.api.v1.event.{ArchivedEvent, CreatedEvent, Event} import com.daml.ledger.api.v1.transaction.Transaction import com.daml.ledger.api.v1.value -import com.daml.ledger.api.validation.ValueValidator +import com.daml.ledger.api.validation.NoLoggingValueValidator import com.daml.platform.participant.util.LfEngineToApi.{ lfValueToApiRecord, lfValueToApiValue, @@ -162,7 +162,7 @@ object Converter { ), ) for { - createArguments <- ValueValidator + createArguments <- NoLoggingValueValidator .validateRecord(created.getCreateArguments) .left .map(_.getMessage)