From cf3ac011ca60a226d783e87676e3125f26c15303 Mon Sep 17 00:00:00 2001 From: tudor-da Date: Thu, 25 Nov 2021 12:42:29 +0100 Subject: [PATCH] [Self-service error codes] Do not return error code id and definite_answer in metadata for security sensitive errors (#11828) * [Self-service error codes] Do not return error code id in metadata CHANGELOG_BEGIN CHANGELOG_END * Do not propagate definite_answer * Additionally, remove redundant context info from error codes * Fix tests * Create IndexDbException as a specialization * Used to globally define error codes returned by the persistence layer that are logging --- .../main/scala/com/daml/error/ErrorCode.scala | 26 +-- .../daml/error/definitions/IndexErrors.scala | 44 +++-- .../error/definitions/LedgerApiErrors.scala | 109 +++++----- .../com/daml/error/utils/ErrorDetails.scala | 20 +- .../scala/com/daml/error/ErrorCodeSpec.scala | 30 ++- .../utils/testpackage/SeriousError.scala | 8 +- .../testpackage/subpackage/MildErrors.scala | 6 +- .../auth/AuthorizationInterceptorSpec.scala | 5 +- .../api/validation/ErrorFactories.scala | 18 +- .../api/validation/ErrorFactoriesSpec.scala | 186 +++++++++++------- .../platform/index/ReadOnlySqlLedger.scala | 11 +- .../platform/store/ConversionsSpec.scala | 2 +- .../sandbox/stores/ledger/RejectionSpec.scala | 4 +- .../stores/ledger/sql/SqlLedgerSpec.scala | 2 +- 14 files changed, 277 insertions(+), 194 deletions(-) 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 46cb4d7a7f..55d04c3041 100644 --- a/ledger/error/src/main/scala/com/daml/error/ErrorCode.scala +++ b/ledger/error/src/main/scala/com/daml/error/ErrorCode.scala @@ -67,18 +67,20 @@ abstract class ErrorCode(val id: String, val category: ErrorCategory)(implicit getStatusInfo(err) // Provide error id and context via ErrorInfo - val errInfoBld = com.google.rpc.ErrorInfo.newBuilder().setReason(id) - if (!code.category.securitySensitive) { - contextMap.foreach { case (k, v) => errInfoBld.putMetadata(k, v) } - } + val maybeErrInfo = + if (!code.category.securitySensitive) { + val errInfoBld = com.google.rpc.ErrorInfo.newBuilder() + contextMap.foreach { case (k, v) => errInfoBld.putMetadata(k, v) } + errInfoBld.setReason(id) - // TODO error codes: Resolve dependency and use constant - // val definiteAnswerKey = com.daml.ledger.grpc.GrpcStatuses.DefiniteAnswerKey - val definiteAnswerKey = "definite_answer" - err.definiteAnswerO.foreach { definiteAnswer => - errInfoBld.putMetadata(definiteAnswerKey, definiteAnswer.toString) - } - val errInfo = com.google.protobuf.Any.pack(errInfoBld.build()) + // TODO error codes: Resolve dependency and use constant + // val definiteAnswerKey = com.daml.ledger.grpc.GrpcStatuses.DefiniteAnswerKey + val definiteAnswerKey = "definite_answer" + err.definiteAnswerO.foreach { definiteAnswer => + errInfoBld.putMetadata(definiteAnswerKey, definiteAnswer.toString) + } + Some(com.google.protobuf.Any.pack(errInfoBld.build())) + } else None // Build retry info val retryInfo = err.retryable.map { ri => @@ -124,7 +126,7 @@ abstract class ErrorCode(val id: String, val category: ErrorCategory)(implicit .setCode(codeGrpc.value()) .setMessage(message) - (Seq(errInfo) ++ retryInfo.toList ++ requestInfo.toList ++ resourceInfo) + (maybeErrInfo.toList ++ retryInfo.toList ++ requestInfo.toList ++ resourceInfo) .foldLeft(statusBuilder) { case (acc, item) => acc.addDetails(item) } diff --git a/ledger/error/src/main/scala/com/daml/error/definitions/IndexErrors.scala b/ledger/error/src/main/scala/com/daml/error/definitions/IndexErrors.scala index 5ad5d5131a..ca2e939d29 100644 --- a/ledger/error/src/main/scala/com/daml/error/definitions/IndexErrors.scala +++ b/ledger/error/src/main/scala/com/daml/error/definitions/IndexErrors.scala @@ -3,10 +3,9 @@ package com.daml.error.definitions +import com.daml.error.ErrorCode.LoggingApiException import com.daml.error._ import com.daml.error.definitions.ErrorGroups.ParticipantErrorGroup.IndexErrorGroup -import com.daml.error.utils.ErrorDetails -import io.grpc.StatusRuntimeException @Explanation("Errors raised by the Participant Index persistence layer.") object IndexErrors extends IndexErrorGroup { @@ -19,11 +18,10 @@ object IndexErrors extends IndexErrorGroup { extends ErrorCode( id = "INDEX_DB_SQL_TRANSIENT_ERROR", ErrorCategory.TransientServerFailure, - ) - with HasUnapply { + ) { case class Reject(throwable: Throwable)(implicit val loggingContext: ContextualizedErrorLogger - ) extends LoggingTransactionErrorImpl( + ) extends DbError( cause = s"Processing the request failed due to a transient database error: ${throwable.getMessage}", throwableO = Some(throwable), @@ -38,11 +36,10 @@ object IndexErrors extends IndexErrorGroup { extends ErrorCode( id = "INDEX_DB_SQL_NON_TRANSIENT_ERROR", ErrorCategory.SystemInternalAssumptionViolated, - ) - with HasUnapply { + ) { case class Reject(throwable: Throwable)(implicit val loggingContext: ContextualizedErrorLogger - ) extends LoggingTransactionErrorImpl( + ) extends DbError( cause = s"Processing the request failed due to a non-transient database error: ${throwable.getMessage}", throwableO = Some(throwable), @@ -57,21 +54,34 @@ object IndexErrors extends IndexErrorGroup { extends ErrorCode( id = "INDEX_DB_INVALID_RESULT_SET", ErrorCategory.SystemInternalAssumptionViolated, - ) - with HasUnapply { + ) { case class Reject(message: String)(implicit val loggingContext: ContextualizedErrorLogger - ) extends LoggingTransactionErrorImpl( + ) extends DbError( cause = message ) } } - trait HasUnapply { - this: ErrorCode => - // TODO error codes: Create a generic unapply for ErrorCode that returns the ErrorCode instance - // and match against that one. - def unapply(exception: StatusRuntimeException): Option[Unit] = - if (ErrorDetails.isErrorCode(exception)(errorCode = this)) Some(()) else None + // Decorator that returns a specialized StatusRuntimeException (IndexDbException) + // that can be used for precise matching of persistence exceptions (e.g. for index initialization failures that need retrying). + // Without this specialization, internal errors just appear as StatusRuntimeExceptions (see INDEX_DB_SQL_NON_TRANSIENT_ERROR) + // without any marker, impeding us to assert whether they are emitted by the persistence layer or not. + abstract class DbError( + override val cause: String, + override val throwableO: Option[Throwable] = None, + )(implicit + code: ErrorCode, + loggingContext: ContextualizedErrorLogger, + ) extends LoggingTransactionErrorImpl(cause, throwableO) { + override def asGrpcErrorFromContext(implicit + contextualizedErrorLogger: ContextualizedErrorLogger + ): IndexDbException = { + val err = super.asGrpcErrorFromContext(contextualizedErrorLogger) + IndexDbException(err.getStatus, err.getTrailers) + } } + + case class IndexDbException(status: io.grpc.Status, metadata: io.grpc.Metadata) + extends LoggingApiException(status, metadata) } 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 f685715503..1347471fbe 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 @@ -54,10 +54,11 @@ object LedgerApiErrors extends LedgerApiErrorGroup { id = "REQUEST_TIME_OUT", ErrorCategory.DeadlineExceededRequestStateUnknown, ) { - case class Reject(message: String, override val definiteAnswer: Boolean)(implicit + case class Reject(_message: String, _definiteAnswer: Boolean)(implicit loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( - cause = message + cause = _message, + definiteAnswer = _definiteAnswer, ) } @@ -275,17 +276,23 @@ object LedgerApiErrors extends LedgerApiErrorGroup { id = "SERVICE_NOT_RUNNING", ErrorCategory.TransientServerFailure, ) { - case class Reject(serviceName: String)(implicit + case class Reject(_serviceName: String)(implicit loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( - cause = s"$serviceName has been shut down." - ) + cause = s"${_serviceName} has been shut down." + ) { + override def context: Map[String, String] = + super.context ++ Map("service_name" -> _serviceName) + } - case class ServiceReset(serviceName: String)(implicit + case class ServiceReset(_serviceName: String)(implicit loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( - cause = s"$serviceName is currently being reset." - ) + cause = s"${_serviceName} is currently being reset." + ) { + override def context: Map[String, String] = + super.context ++ Map("service_name" -> _serviceName) + } } @Explanation("Authentication errors.") @@ -355,14 +362,14 @@ object LedgerApiErrors extends LedgerApiErrorGroup { id = "PACKAGE_NOT_FOUND", ErrorCategory.InvalidGivenCurrentSystemStateResourceMissing, ) { - case class Reject(packageId: String)(implicit + case class Reject(_packageId: String)(implicit loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = "Could not find package." ) { override def resources: Seq[(ErrorResource, String)] = { - super.resources :+ ((ErrorResource.DalfPackage, packageId)) + super.resources :+ ((ErrorResource.DalfPackage, _packageId)) } } @@ -388,10 +395,11 @@ object LedgerApiErrors extends LedgerApiErrorGroup { ErrorCategory.InvalidGivenCurrentSystemStateResourceMissing, ) { - case class Reject(transactionId: String)(implicit loggingContext: ContextualizedErrorLogger) - extends LoggingTransactionErrorImpl(cause = "Transaction not found, or not visible.") { + case class Reject(_transactionId: String)(implicit + loggingContext: ContextualizedErrorLogger + ) extends LoggingTransactionErrorImpl(cause = "Transaction not found, or not visible.") { override def resources: Seq[(ErrorResource, String)] = Seq( - (ErrorResource.TransactionId, transactionId) + (ErrorResource.TransactionId, _transactionId) ) } } @@ -412,10 +420,10 @@ object LedgerApiErrors extends LedgerApiErrorGroup { cause = "The ledger configuration could not be retrieved." ) - case class RejectWithMessage(message: String)(implicit + case class RejectWithMessage(_message: String)(implicit loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( - cause = s"The ledger configuration could not be retrieved: $message." + cause = s"The ledger configuration could not be retrieved: ${_message}." ) } } @@ -427,11 +435,9 @@ object LedgerApiErrors extends LedgerApiErrorGroup { id = "PARTICIPANT_PRUNED_DATA_ACCESSED", ErrorCategory.InvalidGivenCurrentSystemStateOther, ) { - case class Reject(message: String)(implicit + case class Reject(override val cause: String)(implicit loggingContext: ContextualizedErrorLogger - ) extends LoggingTransactionErrorImpl( - cause = message - ) + ) extends LoggingTransactionErrorImpl(cause = cause) } @Explanation( @@ -443,10 +449,11 @@ object LedgerApiErrors extends LedgerApiErrorGroup { id = "OFFSET_AFTER_LEDGER_END", ErrorCategory.InvalidGivenCurrentSystemStateSeekAfterEnd, ) { - case class Reject(offsetType: String, requestedOffset: String, ledgerEnd: String)(implicit + case class Reject(_offsetType: String, _requestedOffset: String, _ledgerEnd: String)(implicit loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( - cause = s"$offsetType offset ($requestedOffset) is after ledger end ($ledgerEnd)" + cause = + s"${_offsetType} offset (${_requestedOffset}) is after ledger end (${_ledgerEnd})" ) } @@ -459,9 +466,9 @@ object LedgerApiErrors extends LedgerApiErrorGroup { id = "OFFSET_OUT_OF_RANGE", ErrorCategory.InvalidGivenCurrentSystemStateOther, ) { - case class Reject(message: String)(implicit + case class Reject(_message: String)(implicit loggingContext: ContextualizedErrorLogger - ) extends LoggingTransactionErrorImpl(cause = message) + ) extends LoggingTransactionErrorImpl(cause = _message) } @Explanation( @@ -488,11 +495,14 @@ object LedgerApiErrors extends LedgerApiErrorGroup { @Resolution("Inspect the reason given and correct your application.") object MissingField extends ErrorCode(id = "MISSING_FIELD", ErrorCategory.InvalidIndependentOfSystemState) { - case class Reject(missingField: String)(implicit + case class Reject(_missingField: String)(implicit loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( - cause = s"The submitted command is missing a mandatory field: $missingField" - ) + cause = s"The submitted command is missing a mandatory field: ${_missingField}" + ) { + override def context: Map[String, String] = + super.context ++ Map("field_name" -> _missingField) + } } @Explanation( @@ -550,14 +560,14 @@ object LedgerApiErrors extends LedgerApiErrorGroup { ErrorCategory.InvalidIndependentOfSystemState, ) { case class Error( - fieldName: String, - offsetValue: String, - message: String, + _fieldName: String, + _offsetValue: String, + _message: String, )(implicit override val loggingContext: ContextualizedErrorLogger ) extends BaseError.Impl( cause = - s"Offset in ${fieldName} not specified in hexadecimal: ${offsetValue}: ${message}" + s"Offset in ${_fieldName} not specified in hexadecimal: ${_offsetValue}: ${_message}" ) } } @@ -633,10 +643,10 @@ object LedgerApiErrors extends LedgerApiErrorGroup { id = "CONFIGURATION_ENTRY_REJECTED", ErrorCategory.InvalidGivenCurrentSystemStateOther, ) { - case class Reject(message: String)(implicit + case class Reject(_message: String)(implicit loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( - cause = message + cause = _message ) } @@ -647,10 +657,10 @@ object LedgerApiErrors extends LedgerApiErrorGroup { id = "PACKAGE_UPLOAD_REJECTED", ErrorCategory.InvalidGivenCurrentSystemStateOther, ) { - case class Reject(message: String)(implicit + case class Reject(_message: String)(implicit loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( - cause = message + cause = _message ) } } @@ -673,15 +683,16 @@ object LedgerApiErrors extends LedgerApiErrorGroup { ) { case class Reject( - override val definiteAnswer: Boolean = false, - existingCommandSubmissionId: Option[String], + _definiteAnswer: Boolean = false, + _existingCommandSubmissionId: Option[String], )(implicit loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( - cause = "A command with the given command id has already been successfully processed" + cause = "A command with the given command id has already been successfully processed", + definiteAnswer = _definiteAnswer, ) { override def context: Map[String, String] = - super.context ++ existingCommandSubmissionId.map("existing_submission_id" -> _).toList + super.context ++ _existingCommandSubmissionId.map("existing_submission_id" -> _).toList } } @@ -731,13 +742,13 @@ object LedgerApiErrors extends LedgerApiErrorGroup { ErrorCategory.InvalidGivenCurrentSystemStateResourceMissing, ) { - case class MultipleContractsNotFound(notFoundContractIds: Set[String])(implicit + case class MultipleContractsNotFound(_notFoundContractIds: Set[String])(implicit loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( - cause = s"Unknown contracts: ${notFoundContractIds.mkString("[", ", ", "]")}" + cause = s"Unknown contracts: ${_notFoundContractIds.mkString("[", ", ", "]")}" ) { override def resources: Seq[(ErrorResource, String)] = Seq( - (ErrorResource.ContractId, notFoundContractIds.mkString("[", ", ", "]")) + (ErrorResource.ContractId, _notFoundContractIds.mkString("[", ", ", "]")) ) } @@ -808,19 +819,17 @@ object LedgerApiErrors extends LedgerApiErrorGroup { ErrorCategory.InvalidGivenCurrentSystemStateOther, // It may succeed at a later time ) { case class RejectEnriched( - message: String, + override val cause: String, ledger_time: Instant, ledger_time_lower_bound: Instant, ledger_time_upper_bound: Instant, )(implicit loggingContext: ContextualizedErrorLogger) - extends LoggingTransactionErrorImpl( - cause = s"Invalid ledger time: $message" - ) + extends LoggingTransactionErrorImpl(cause = cause) case class RejectSimple( - details: String + override val cause: String )(implicit loggingContext: ContextualizedErrorLogger) - extends LoggingTransactionErrorImpl(cause = s"Invalid ledger time: $details") + extends LoggingTransactionErrorImpl(cause = cause) } } @@ -860,12 +869,12 @@ object LedgerApiErrors extends LedgerApiErrorGroup { id = "PARTY_NOT_KNOWN_ON_LEDGER", ErrorCategory.InvalidGivenCurrentSystemStateResourceMissing, ) { - case class Reject(parties: Set[String])(implicit + case class Reject(_parties: Set[String])(implicit loggingContext: ContextualizedErrorLogger - ) extends LoggingTransactionErrorImpl(cause = s"Parties not known on ledger: ${parties + ) extends LoggingTransactionErrorImpl(cause = s"Parties not known on ledger: ${_parties .mkString("[", ",", "]")}") { override def resources: Seq[(ErrorResource, String)] = - parties.map((ErrorResource.Party, _)).toSeq + _parties.map((ErrorResource.Party, _)).toSeq } @deprecated diff --git a/ledger/error/src/main/scala/com/daml/error/utils/ErrorDetails.scala b/ledger/error/src/main/scala/com/daml/error/utils/ErrorDetails.scala index e607369c6c..d15c615326 100644 --- a/ledger/error/src/main/scala/com/daml/error/utils/ErrorDetails.scala +++ b/ledger/error/src/main/scala/com/daml/error/utils/ErrorDetails.scala @@ -3,11 +3,8 @@ package com.daml.error.utils -import com.daml.error.ErrorCode import com.google.protobuf import com.google.rpc.{ErrorInfo, RequestInfo, ResourceInfo, RetryInfo} -import io.grpc.StatusRuntimeException -import io.grpc.protobuf.StatusProto import scala.jdk.CollectionConverters._ @@ -15,7 +12,8 @@ object ErrorDetails { sealed trait ErrorDetail extends Product with Serializable final case class ResourceInfoDetail(name: String, typ: String) extends ErrorDetail - final case class ErrorInfoDetail(reason: String) extends ErrorDetail + final case class ErrorInfoDetail(reason: String, metadata: Map[String, String]) + extends ErrorDetail final case class RetryInfoDetail(retryDelayInSeconds: Long) extends ErrorDetail final case class RequestInfoDetail(requestId: String) extends ErrorDetail @@ -26,7 +24,7 @@ object ErrorDetails { case any if any.is(classOf[ErrorInfo]) => val v = any.unpack(classOf[ErrorInfo]) - ErrorInfoDetail(v.getReason) + ErrorInfoDetail(v.getReason, v.getMetadataMap.asScala.toMap) case any if any.is(classOf[RetryInfo]) => val v = any.unpack(classOf[RetryInfo]) @@ -38,16 +36,4 @@ object ErrorDetails { case any => throw new IllegalStateException(s"Could not unpack value of: |$any|") } - - def isErrorCode(exception: StatusRuntimeException)(errorCode: ErrorCode): Boolean = { - val rpcStatus = - StatusProto.fromStatusAndTrailers(exception.getStatus, exception.getTrailers) - - ErrorDetails - .from(rpcStatus.getDetailsList.asScala.toSeq) - .exists { - case ErrorInfoDetail(reason) => reason == errorCode.id - case _ => false - } - } } 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 47b38727f8..75d80db0eb 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 @@ -60,7 +60,8 @@ class ErrorCodeSpec extends AnyFlatSpec with Matchers with BeforeAndAfter { } s"$className.asGrpcErrorFromContext" should "output a GRPC error with correct status, message and metadata" in { - val error = NotSoSeriousError.Error("some error cause") + val contextMetadata = Map("some key" -> "some value", "another key" -> "another value") + val error = NotSoSeriousError.Error("some error cause", contextMetadata) val correlationId = "12345678" val actualGrpcError = error.asGrpcErrorFromContext(errorLoggingContext(Some(correlationId))) @@ -78,13 +79,38 @@ class ErrorCodeSpec extends AnyFlatSpec with Matchers with BeforeAndAfter { actualGrpcError.getMessage shouldBe expectedErrorMessage errorDetails should contain theSameElementsAs Seq( - ErrorDetails.ErrorInfoDetail(NotSoSeriousError.id), + ErrorDetails.ErrorInfoDetail( + NotSoSeriousError.id, + Map("category" -> "1") ++ contextMetadata ++ Map("definite_answer" -> "true"), + ), ErrorDetails.RetryInfoDetail(TransientServerFailure.retryable.get.duration.toSeconds), ErrorDetails.RequestInfoDetail(correlationId), ErrorDetails.ResourceInfoDetail(error.resources.head._1.asString, error.resources.head._2), ) } + s"$className.asGrpcErrorFromContext" should "not propagate security sensitive information in gRPC statuses" in { + val error = + SeriousError.Error("some cause", Map("some sensitive key" -> "some sensitive value")) + val correlationId = "12345678" + + val actualGrpcError = error.asGrpcErrorFromContext(errorLoggingContext(Some(correlationId))) + val expectedErrorMessage = + s"An error occurred. Please contact the operator and inquire about the request $correlationId" + + val actualStatus = actualGrpcError.getStatus + val actualTrailers = actualGrpcError.getTrailers + val actualRpcStatus = StatusProto.fromStatusAndTrailers(actualStatus, actualTrailers) + + val errorDetails = + ErrorDetails.from(actualRpcStatus.getDetailsList.asScala.toSeq) + + actualStatus.getCode shouldBe io.grpc.Status.Code.INTERNAL + actualGrpcError.getStatus.getDescription shouldBe expectedErrorMessage + + errorDetails should contain theSameElementsAs Seq(ErrorDetails.RequestInfoDetail(correlationId)) + } + private def logSeriousError( cause: String = "the error argument", extra: Map[String, String] = Map.empty, diff --git a/ledger/error/src/test/utils/scala/com/daml/error/utils/testpackage/SeriousError.scala b/ledger/error/src/test/utils/scala/com/daml/error/utils/testpackage/SeriousError.scala index 36932f8bac..cbe872efb9 100644 --- a/ledger/error/src/test/utils/scala/com/daml/error/utils/testpackage/SeriousError.scala +++ b/ledger/error/src/test/utils/scala/com/daml/error/utils/testpackage/SeriousError.scala @@ -13,7 +13,13 @@ case object SeriousError extends ErrorCode("BLUE_SCREEN", ErrorCategory.SystemInternalAssumptionViolated)( ErrorClass.root() ) { - case class Error(cause: String)(implicit val loggingContext: LoggingContext) extends BaseError { + case class Error( + cause: String, + override val context: Map[String, String] = Map.empty, + override val definiteAnswerO: Option[Boolean] = Some(true), + )(implicit + val loggingContext: LoggingContext + ) extends BaseError { /** The error code, usually passed in as implicit where the error class is defined */ override def code: ErrorCode = SeriousError.code diff --git a/ledger/error/src/test/utils/scala/com/daml/error/utils/testpackage/subpackage/MildErrors.scala b/ledger/error/src/test/utils/scala/com/daml/error/utils/testpackage/subpackage/MildErrors.scala index b405bcec63..0ce2e73070 100644 --- a/ledger/error/src/test/utils/scala/com/daml/error/utils/testpackage/subpackage/MildErrors.scala +++ b/ledger/error/src/test/utils/scala/com/daml/error/utils/testpackage/subpackage/MildErrors.scala @@ -19,7 +19,11 @@ object MildErrors "TEST_ROUTINE_FAILURE_PLEASE_IGNORE", ErrorCategory.TransientServerFailure, ) { - case class Error(someErrArg: String)(implicit val loggingContext: LoggingContext) + case class Error( + someErrArg: String, + override val context: Map[String, String], + override val definiteAnswerO: Option[Boolean] = Some(true), + )(implicit val loggingContext: LoggingContext) extends BaseError { override def code: ErrorCode = NotSoSeriousError.code diff --git a/ledger/ledger-api-auth/src/test/suite/scala/com/digitalasset/ledger/api/auth/AuthorizationInterceptorSpec.scala b/ledger/ledger-api-auth/src/test/suite/scala/com/digitalasset/ledger/api/auth/AuthorizationInterceptorSpec.scala index 1a3af6a9b6..0d7ed8a228 100644 --- a/ledger/ledger-api-auth/src/test/suite/scala/com/digitalasset/ledger/api/auth/AuthorizationInterceptorSpec.scala +++ b/ledger/ledger-api-auth/src/test/suite/scala/com/digitalasset/ledger/api/auth/AuthorizationInterceptorSpec.scala @@ -5,7 +5,6 @@ package com.daml.ledger.api.auth import com.daml.error.ErrorCodesVersionSwitcher import com.daml.ledger.api.auth.interceptor.AuthorizationInterceptor -import com.google.rpc.ErrorInfo import io.grpc.protobuf.StatusProto import io.grpc.{Metadata, ServerCall, Status} import org.mockito.captor.ArgCaptor @@ -42,9 +41,7 @@ class AuthorizationInterceptorSpec actualStatus.getDescription shouldBe "An error occurred. Please contact the operator and inquire about the request " val actualRpcStatus = StatusProto.fromStatusAndTrailers(actualStatus, actualMetadata) - actualRpcStatus.getDetailsList.size() shouldBe 1 - val errorInfo = actualRpcStatus.getDetailsList.get(0).unpack(classOf[ErrorInfo]) - errorInfo.getReason shouldBe "INTERNAL_AUTHORIZATION_ERROR" + actualRpcStatus.getDetailsList.size() shouldBe 0 } } 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 3130d70ff2..67a0590bc4 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 @@ -76,7 +76,7 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch v2 = LedgerApiErrors.RequestTimeOut .Reject( "Timed out while awaiting for a completion corresponding to a command submission.", - definiteAnswer = false, + _definiteAnswer = false, ) .asGrpcStatusFromContext, ) @@ -149,7 +149,7 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch errorCodesVersionSwitcher.choose( v1 = io.grpc.Status.NOT_FOUND.asRuntimeException(), v2 = LedgerApiErrors.RequestValidation.NotFound.Package - .Reject(packageId = packageId) + .Reject(_packageId = packageId) .asGrpcError, ) @@ -184,7 +184,7 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch exception }, v2 = LedgerApiErrors.ConsistencyErrors.DuplicateCommand - .Reject(existingCommandSubmissionId = existingSubmissionId) + .Reject(_existingCommandSubmissionId = existingSubmissionId) .asGrpcError, ) @@ -309,9 +309,9 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch v1 = invalidArgumentV1(definiteAnswer, message), v2 = LedgerApiErrors.RequestValidation.NonHexOffset .Error( - fieldName = fieldName, - offsetValue = offsetValue, - message = message, + _fieldName = fieldName, + _offsetValue = offsetValue, + _message = message, ) .asGrpcError, ) @@ -385,11 +385,7 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch errorCodesVersionSwitcher.choose( v1 = aborted(message, definiteAnswer), v2 = LedgerApiErrors.RequestTimeOut - .Reject( - message, - // TODO error codes: How to handle None definiteAnswer? - definiteAnswer.getOrElse(false), - ) + .Reject(message, definiteAnswer.getOrElse(false)) .asGrpcError, ) } 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 c57df38ac2..b2a53cb1ee 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 @@ -50,16 +50,18 @@ class ErrorFactoriesSpec val failureReason = "some db transient failure" val someSqlTransientException = new SQLTransientException(failureReason) assertV2Error( - SelfServiceErrorCodeFactories - .sqlTransientException(someSqlTransientException) + SelfServiceErrorCodeFactories.sqlTransientException(someSqlTransientException) )( expectedCode = Code.UNAVAILABLE, expectedMessage = s"INDEX_DB_SQL_TRANSIENT_ERROR(1,$truncatedCorrelationId): Processing the request failed due to a transient database error: $failureReason", expectedDetails = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("INDEX_DB_SQL_TRANSIENT_ERROR"), expectedCorrelationIdRequestInfo, ErrorDetails.RetryInfoDetail(1), + ErrorDetails.ErrorInfoDetail( + "INDEX_DB_SQL_TRANSIENT_ERROR", + Map("category" -> "1", "definite_answer" -> "false"), + ), ), ) } @@ -73,19 +75,12 @@ class ErrorFactoriesSpec expectedCode = Code.INTERNAL, expectedMessage = s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId", - expectedDetails = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("INDEX_DB_SQL_NON_TRANSIENT_ERROR"), - expectedCorrelationIdRequestInfo, - ), + expectedDetails = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo), ) } "TrackerErrors" should { - val errorDetails = com.google.protobuf.Any.pack[ErrorInfo]( - ErrorInfo - .newBuilder() - .build() - ) + val errorDetails = com.google.protobuf.Any.pack[ErrorInfo](ErrorInfo.newBuilder().build()) "return failedToEnqueueCommandSubmission" in { val t = new Exception("message123") @@ -100,10 +95,7 @@ class ErrorFactoriesSpec v2_code = Code.INTERNAL, v2_message = s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId", - v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("LEDGER_API_INTERNAL_ERROR"), - expectedCorrelationIdRequestInfo, - ), + v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo), ) } @@ -118,7 +110,14 @@ class ErrorFactoriesSpec v2_message = s"PARTICIPANT_BACKPRESSURE(2,$truncatedCorrelationId): The participant is overloaded: Some buffer is full", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("PARTICIPANT_BACKPRESSURE"), + ErrorDetails.ErrorInfoDetail( + "PARTICIPANT_BACKPRESSURE", + Map( + "category" -> "2", + "definite_answer" -> "false", + "reason" -> "Some buffer is full", + ), + ), expectedCorrelationIdRequestInfo, ErrorDetails.RetryInfoDetail(1), ), @@ -138,7 +137,14 @@ class ErrorFactoriesSpec v2_message = s"SERVICE_NOT_RUNNING(1,$truncatedCorrelationId): Some service has been shut down.", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("SERVICE_NOT_RUNNING"), + ErrorDetails.ErrorInfoDetail( + "SERVICE_NOT_RUNNING", + Map( + "category" -> "1", + "definite_answer" -> "false", + "service_name" -> "Some service", + ), + ), expectedCorrelationIdRequestInfo, ErrorDetails.RetryInfoDetail(1), ), @@ -158,7 +164,10 @@ class ErrorFactoriesSpec v2_message = s"REQUEST_TIME_OUT(3,$truncatedCorrelationId): Timed out while awaiting for a completion corresponding to a command submission.", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("REQUEST_TIME_OUT"), + ErrorDetails.ErrorInfoDetail( + "REQUEST_TIME_OUT", + Map("category" -> "3", "definite_answer" -> "false"), + ), expectedCorrelationIdRequestInfo, ErrorDetails.RetryInfoDetail(1), ), @@ -176,10 +185,7 @@ class ErrorFactoriesSpec v2_code = Code.INTERNAL, v2_message = s"An error occurred. Please contact the operator and inquire about the request cor-id-12345679", - v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("LEDGER_API_INTERNAL_ERROR"), - expectedCorrelationIdRequestInfo, - ), + v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo), ) } @@ -194,7 +200,10 @@ class ErrorFactoriesSpec v2_code = Code.NOT_FOUND, v2_message = s"PACKAGE_NOT_FOUND(11,$truncatedCorrelationId): Could not find package.", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("PACKAGE_NOT_FOUND"), + ErrorDetails.ErrorInfoDetail( + "PACKAGE_NOT_FOUND", + Map("category" -> "11", "definite_answer" -> "false"), + ), expectedCorrelationIdRequestInfo, ErrorDetails.ResourceInfoDetail("PACKAGE", "packageId123"), ), @@ -209,10 +218,7 @@ class ErrorFactoriesSpec v2_code = Code.INTERNAL, v2_message = s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId", - v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("LEDGER_API_INTERNAL_ERROR"), - expectedCorrelationIdRequestInfo, - ), + v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo), ) } @@ -224,7 +230,10 @@ class ErrorFactoriesSpec v2_code = Code.FAILED_PRECONDITION, v2_message = s"CONFIGURATION_ENTRY_REJECTED(9,$truncatedCorrelationId): message123", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("CONFIGURATION_ENTRY_REJECTED"), + ErrorDetails.ErrorInfoDetail( + "CONFIGURATION_ENTRY_REJECTED", + Map("category" -> "9", "definite_answer" -> "false"), + ), expectedCorrelationIdRequestInfo, ), ) @@ -239,7 +248,10 @@ class ErrorFactoriesSpec v2_message = s"TRANSACTION_NOT_FOUND(11,$truncatedCorrelationId): Transaction not found, or not visible.", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("TRANSACTION_NOT_FOUND"), + ErrorDetails.ErrorInfoDetail( + "TRANSACTION_NOT_FOUND", + Map("category" -> "11", "definite_answer" -> "false"), + ), expectedCorrelationIdRequestInfo, ErrorDetails.ResourceInfoDetail("TRANSACTION_ID", "tId"), ), @@ -255,7 +267,10 @@ class ErrorFactoriesSpec v2_message = s"DUPLICATE_COMMAND(10,$truncatedCorrelationId): A command with the given command id has already been successfully processed", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("DUPLICATE_COMMAND"), + ErrorDetails.ErrorInfoDetail( + "DUPLICATE_COMMAND", + Map("category" -> "10", "definite_answer" -> "false"), + ), expectedCorrelationIdRequestInfo, ), ) @@ -269,10 +284,7 @@ class ErrorFactoriesSpec v2_code = Code.PERMISSION_DENIED, v2_message = s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId", - v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("PERMISSION_DENIED"), - expectedCorrelationIdRequestInfo, - ), + v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo), ) } @@ -286,7 +298,10 @@ class ErrorFactoriesSpec v2_code = Code.DEADLINE_EXCEEDED, v2_message = s"REQUEST_TIME_OUT(3,$truncatedCorrelationId): message123", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("REQUEST_TIME_OUT"), + ErrorDetails.ErrorInfoDetail( + "REQUEST_TIME_OUT", + Map("category" -> "3", "definite_answer" -> "false"), + ), expectedCorrelationIdRequestInfo, ErrorDetails.RetryInfoDetail(1), ), @@ -308,7 +323,7 @@ class ErrorFactoriesSpec v2_message = s"NON_HEXADECIMAL_OFFSET(8,$truncatedCorrelationId): Offset in fieldName123 not specified in hexadecimal: offsetValue123: message123", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("NON_HEXADECIMAL_OFFSET"), + ErrorDetails.ErrorInfoDetail("NON_HEXADECIMAL_OFFSET", Map("category" -> "8")), expectedCorrelationIdRequestInfo, ), ) @@ -323,7 +338,10 @@ class ErrorFactoriesSpec v2_code = Code.OUT_OF_RANGE, v2_message = s"OFFSET_AFTER_LEDGER_END(12,$truncatedCorrelationId): $expectedMessage", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("OFFSET_AFTER_LEDGER_END"), + ErrorDetails.ErrorInfoDetail( + "OFFSET_AFTER_LEDGER_END", + Map("category" -> "12", "definite_answer" -> "false"), + ), expectedCorrelationIdRequestInfo, ), ) @@ -337,7 +355,10 @@ class ErrorFactoriesSpec v2_code = Code.FAILED_PRECONDITION, v2_message = s"OFFSET_OUT_OF_RANGE(9,$truncatedCorrelationId): message123", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("OFFSET_OUT_OF_RANGE"), + ErrorDetails.ErrorInfoDetail( + "OFFSET_OUT_OF_RANGE", + Map("category" -> "9", "definite_answer" -> "false"), + ), expectedCorrelationIdRequestInfo, ), ) @@ -351,10 +372,7 @@ class ErrorFactoriesSpec v2_code = Code.UNAUTHENTICATED, v2_message = s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId", - v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("UNAUTHENTICATED"), - expectedCorrelationIdRequestInfo, - ), + v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo), ) } @@ -368,10 +386,7 @@ class ErrorFactoriesSpec v2_code = Code.INTERNAL, v2_message = s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId", - v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("INTERNAL_AUTHORIZATION_ERROR"), - expectedCorrelationIdRequestInfo, - ), + v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo), ) } @@ -393,7 +408,10 @@ class ErrorFactoriesSpec v2_message = s"LEDGER_CONFIGURATION_NOT_FOUND(11,$truncatedCorrelationId): The ledger configuration could not be retrieved.", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("LEDGER_CONFIGURATION_NOT_FOUND"), + ErrorDetails.ErrorInfoDetail( + "LEDGER_CONFIGURATION_NOT_FOUND", + Map("category" -> "11", "definite_answer" -> "false"), + ), expectedCorrelationIdRequestInfo, ), ) @@ -419,8 +437,9 @@ class ErrorFactoriesSpec "return an invalid deduplication period error" in { val errorDetailMessage = "message" val field = "field" + val maxDeduplicationDuration = Duration.ofSeconds(5) assertVersionedError( - _.invalidDeduplicationDuration(field, errorDetailMessage, None, Duration.ofSeconds(5)) + _.invalidDeduplicationDuration(field, errorDetailMessage, None, maxDeduplicationDuration) )( v1_code = Code.INVALID_ARGUMENT, v1_message = s"Invalid field $field: $errorDetailMessage", @@ -429,7 +448,14 @@ class ErrorFactoriesSpec v2_message = s"INVALID_DEDUPLICATION_PERIOD(9,$truncatedCorrelationId): The submitted command had an invalid deduplication period: $errorDetailMessage", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("INVALID_DEDUPLICATION_PERIOD"), + ErrorDetails.ErrorInfoDetail( + "INVALID_DEDUPLICATION_PERIOD", + Map( + "category" -> "9", + "definite_answer" -> "false", + "max_deduplication_duration" -> maxDeduplicationDuration.toString, + ), + ), expectedCorrelationIdRequestInfo, ), ) @@ -442,16 +468,20 @@ class ErrorFactoriesSpec (Some(false), Seq(definiteAnswers(false))), ) + val fieldName = "my field" forEvery(testCases) { (definiteAnswer, expectedDetails) => - assertVersionedError(_.invalidField("my field", "my message", definiteAnswer))( + assertVersionedError(_.invalidField(fieldName, "my message", definiteAnswer))( v1_code = Code.INVALID_ARGUMENT, - v1_message = "Invalid field my field: my message", + v1_message = "Invalid field " + fieldName + ": my message", v1_details = expectedDetails, v2_code = Code.INVALID_ARGUMENT, v2_message = - s"INVALID_FIELD(8,$truncatedCorrelationId): The submitted command has a field with invalid value: Invalid field my field: my message", + s"INVALID_FIELD(8,$truncatedCorrelationId): The submitted command has a field with invalid value: Invalid field $fieldName: my message", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("INVALID_FIELD"), + ErrorDetails.ErrorInfoDetail( + "INVALID_FIELD", + Map("category" -> "8", "definite_answer" -> "false"), + ), expectedCorrelationIdRequestInfo, ), ) @@ -476,7 +506,10 @@ class ErrorFactoriesSpec v2_message = s"LEDGER_ID_MISMATCH(11,$truncatedCorrelationId): Ledger ID 'received' not found. Actual Ledger ID is 'expected'.", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("LEDGER_ID_MISMATCH"), + ErrorDetails.ErrorInfoDetail( + "LEDGER_ID_MISMATCH", + Map("category" -> "11", "definite_answer" -> "true"), + ), expectedCorrelationIdRequestInfo, ), ) @@ -499,7 +532,10 @@ class ErrorFactoriesSpec v2_code = Code.FAILED_PRECONDITION, v2_message = s"PARTICIPANT_PRUNED_DATA_ACCESSED(9,$truncatedCorrelationId): my message", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("PARTICIPANT_PRUNED_DATA_ACCESSED"), + ErrorDetails.ErrorInfoDetail( + "PARTICIPANT_PRUNED_DATA_ACCESSED", + Map("category" -> "9", "definite_answer" -> "false"), + ), expectedCorrelationIdRequestInfo, ), ) @@ -513,10 +549,7 @@ class ErrorFactoriesSpec v2_code = Code.INTERNAL, v2_message = s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId", - v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("LEDGER_API_INTERNAL_ERROR"), - expectedCorrelationIdRequestInfo, - ), + v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo), ) } @@ -537,7 +570,10 @@ class ErrorFactoriesSpec v2_message = s"SERVICE_NOT_RUNNING(1,$truncatedCorrelationId): $serviceName has been shut down.", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("SERVICE_NOT_RUNNING"), + ErrorDetails.ErrorInfoDetail( + "SERVICE_NOT_RUNNING", + Map("category" -> "1", "definite_answer" -> "false", "service_name" -> serviceName), + ), expectedCorrelationIdRequestInfo, ErrorDetails.RetryInfoDetail(1), ), @@ -557,7 +593,10 @@ class ErrorFactoriesSpec v2_message = s"SERVICE_NOT_RUNNING(1,$truncatedCorrelationId): $serviceName is currently being reset.", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("SERVICE_NOT_RUNNING"), + ErrorDetails.ErrorInfoDetail( + "SERVICE_NOT_RUNNING", + Map("category" -> "1", "definite_answer" -> "false", "service_name" -> serviceName), + ), expectedCorrelationIdRequestInfo, ErrorDetails.RetryInfoDetail(1), ), @@ -565,6 +604,8 @@ class ErrorFactoriesSpec } "return a missingField error" in { + val fieldName = "my field" + val testCases = Table( ("definite answer", "expected details"), (None, Seq.empty), @@ -572,15 +613,18 @@ class ErrorFactoriesSpec ) forEvery(testCases) { (definiteAnswer, expectedDetails) => - assertVersionedError(_.missingField("my field", definiteAnswer))( + assertVersionedError(_.missingField(fieldName, definiteAnswer))( v1_code = Code.INVALID_ARGUMENT, - v1_message = "Missing field: my field", + v1_message = "Missing field: " + fieldName, v1_details = expectedDetails, v2_code = Code.INVALID_ARGUMENT, v2_message = - s"MISSING_FIELD(8,$truncatedCorrelationId): The submitted command is missing a mandatory field: my field", + s"MISSING_FIELD(8,$truncatedCorrelationId): The submitted command is missing a mandatory field: $fieldName", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("MISSING_FIELD"), + ErrorDetails.ErrorInfoDetail( + "MISSING_FIELD", + Map("category" -> "8", "definite_answer" -> "false", "field_name" -> fieldName), + ), expectedCorrelationIdRequestInfo, ), ) @@ -603,7 +647,10 @@ class ErrorFactoriesSpec v2_message = s"INVALID_ARGUMENT(8,$truncatedCorrelationId): The submitted command has invalid arguments: my message", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("INVALID_ARGUMENT"), + ErrorDetails.ErrorInfoDetail( + "INVALID_ARGUMENT", + Map("category" -> "8", "definite_answer" -> "false"), + ), expectedCorrelationIdRequestInfo, ), ) @@ -626,7 +673,10 @@ class ErrorFactoriesSpec v2_message = s"INVALID_ARGUMENT(8,$truncatedCorrelationId): The submitted command has invalid arguments: my message", v2_details = Seq[ErrorDetails.ErrorDetail]( - ErrorDetails.ErrorInfoDetail("INVALID_ARGUMENT"), + ErrorDetails.ErrorInfoDetail( + "INVALID_ARGUMENT", + Map("category" -> "8", "definite_answer" -> "false"), + ), expectedCorrelationIdRequestInfo, ), ) diff --git a/ledger/participant-integration-api/src/main/scala/platform/index/ReadOnlySqlLedger.scala b/ledger/participant-integration-api/src/main/scala/platform/index/ReadOnlySqlLedger.scala index c284320891..f2ab1187a6 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/index/ReadOnlySqlLedger.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/index/ReadOnlySqlLedger.scala @@ -7,7 +7,7 @@ import akka.Done import akka.actor.Cancellable import akka.stream._ import akka.stream.scaladsl.{Keep, Sink, Source} -import com.daml.error.definitions.IndexErrors +import com.daml.error.definitions.IndexErrors.IndexDbException import com.daml.ledger.api.domain.LedgerId import com.daml.ledger.api.health.HealthStatus import com.daml.ledger.offset.Offset @@ -147,12 +147,11 @@ private[platform] object ReadOnlySqlLedger { executionContext: ExecutionContext, loggingContext: LoggingContext, ): Future[LedgerId] = { + // If the index database is not yet fully initialized, + // querying for the ledger ID will throw different errors, + // depending on the database, and how far the initialization is. val isRetryable: PartialFunction[Throwable, Boolean] = { - // If the index database is not yet fully initialized, - // querying for the ledger ID will throw different errors, - // depending on the database, and how far the initialization is. - case IndexErrors.DatabaseErrors.SqlTransientError(_) => true - case IndexErrors.DatabaseErrors.SqlNonTransientError(_) => true + case _: IndexDbException => true case _: LedgerIdNotFoundException => true case _: MismatchException.LedgerId => false case _ => false diff --git a/ledger/participant-integration-api/src/test/suite/scala/platform/store/ConversionsSpec.scala b/ledger/participant-integration-api/src/test/suite/scala/platform/store/ConversionsSpec.scala index 82a34f7333..52fa64792f 100644 --- a/ledger/participant-integration-api/src/test/suite/scala/platform/store/ConversionsSpec.scala +++ b/ledger/participant-integration-api/src/test/suite/scala/platform/store/ConversionsSpec.scala @@ -132,7 +132,7 @@ class ConversionsSpec extends AsyncWordSpec with Matchers { v1expectedCode = Status.Code.ABORTED.value(), v1expectedMessage = "Invalid ledger time: Too late.", v2expectedCode = Status.Code.FAILED_PRECONDITION.value(), - v2expectedMessage = "INVALID_LEDGER_TIME(9,0): Invalid ledger time: Too late.", + v2expectedMessage = "INVALID_LEDGER_TIME(9,0): Too late.", ) } } diff --git a/ledger/sandbox-classic/src/test/lib/scala/platform/sandbox/stores/ledger/RejectionSpec.scala b/ledger/sandbox-classic/src/test/lib/scala/platform/sandbox/stores/ledger/RejectionSpec.scala index c2567d4bd9..948df24857 100644 --- a/ledger/sandbox-classic/src/test/lib/scala/platform/sandbox/stores/ledger/RejectionSpec.scala +++ b/ledger/sandbox-classic/src/test/lib/scala/platform/sandbox/stores/ledger/RejectionSpec.scala @@ -59,7 +59,6 @@ class RejectionSpec extends AnyWordSpec with Matchers { errorInfo shouldBe com.google.rpc.error_details.ErrorInfo( reason = "LEDGER_CONFIGURATION_NOT_FOUND", metadata = Map( - "message" -> "Cannot validate ledger time", "category" -> "11", "definite_answer" -> "false", ), @@ -104,7 +103,7 @@ class RejectionSpec extends AnyWordSpec with Matchers { Rejection.InvalidLedgerTime(outOfRange).toStateRejectionReason(errorFactoriesV2) actualRejectionReason.code shouldBe Status.Code.FAILED_PRECONDITION.value() - actualRejectionReason.message shouldBe "INVALID_LEDGER_TIME(9,12345678): Invalid ledger time: Ledger time 2021-07-20T09:30:00Z outside of range [2021-07-20T09:00:00Z, 2021-07-20T09:10:00Z]" + actualRejectionReason.message shouldBe "INVALID_LEDGER_TIME(9,12345678): Ledger time 2021-07-20T09:30:00Z outside of range [2021-07-20T09:00:00Z, 2021-07-20T09:10:00Z]" val (errorInfo, requestInfo) = extractDetails(actualRejectionReason.status.details) @@ -115,7 +114,6 @@ class RejectionSpec extends AnyWordSpec with Matchers { "ledger_time_lower_bound" -> "2021-07-20T09:00:00Z", "ledger_time_upper_bound" -> "2021-07-20T09:10:00Z", "category" -> "9", - "message" -> "Ledger time 2021-07-20T09:30:00Z outside of range [2021-07-20T09:00:00Z, 2021-07-20T09:10:00Z]", "definite_answer" -> "false", ), ) diff --git a/ledger/sandbox-classic/src/test/suite/scala/platform/sandbox/stores/ledger/sql/SqlLedgerSpec.scala b/ledger/sandbox-classic/src/test/suite/scala/platform/sandbox/stores/ledger/sql/SqlLedgerSpec.scala index 8a43ebe9c0..ea3f94189b 100644 --- a/ledger/sandbox-classic/src/test/suite/scala/platform/sandbox/stores/ledger/sql/SqlLedgerSpec.scala +++ b/ledger/sandbox-classic/src/test/suite/scala/platform/sandbox/stores/ledger/sql/SqlLedgerSpec.scala @@ -383,7 +383,7 @@ final class SqlLedgerSpec case Seq(Completion(`commandId1`, Some(status), _, _, _, _, _)) => status.code should be(Status.Code.FAILED_PRECONDITION.value) status.message should be( - s"INVALID_LEDGER_TIME(9,$submissionId): Invalid ledger time: Ledger time 2021-09-01T18:05:00Z outside of range [2021-09-01T17:59:50Z, 2021-09-01T18:00:30Z]" + s"INVALID_LEDGER_TIME(9,$submissionId): Ledger time 2021-09-01T18:05:00Z outside of range [2021-09-01T17:59:50Z, 2021-09-01T18:00:30Z]" ) } }