[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
This commit is contained in:
tudor-da 2021-11-25 12:42:29 +01:00 committed by GitHub
parent 026b92a8b1
commit cf3ac011ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 277 additions and 194 deletions

View File

@ -67,18 +67,20 @@ abstract class ErrorCode(val id: String, val category: ErrorCategory)(implicit
getStatusInfo(err) getStatusInfo(err)
// Provide error id and context via ErrorInfo // Provide error id and context via ErrorInfo
val errInfoBld = com.google.rpc.ErrorInfo.newBuilder().setReason(id) val maybeErrInfo =
if (!code.category.securitySensitive) { if (!code.category.securitySensitive) {
contextMap.foreach { case (k, v) => errInfoBld.putMetadata(k, v) } 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 // TODO error codes: Resolve dependency and use constant
// val definiteAnswerKey = com.daml.ledger.grpc.GrpcStatuses.DefiniteAnswerKey // val definiteAnswerKey = com.daml.ledger.grpc.GrpcStatuses.DefiniteAnswerKey
val definiteAnswerKey = "definite_answer" val definiteAnswerKey = "definite_answer"
err.definiteAnswerO.foreach { definiteAnswer => err.definiteAnswerO.foreach { definiteAnswer =>
errInfoBld.putMetadata(definiteAnswerKey, definiteAnswer.toString) errInfoBld.putMetadata(definiteAnswerKey, definiteAnswer.toString)
} }
val errInfo = com.google.protobuf.Any.pack(errInfoBld.build()) Some(com.google.protobuf.Any.pack(errInfoBld.build()))
} else None
// Build retry info // Build retry info
val retryInfo = err.retryable.map { ri => val retryInfo = err.retryable.map { ri =>
@ -124,7 +126,7 @@ abstract class ErrorCode(val id: String, val category: ErrorCategory)(implicit
.setCode(codeGrpc.value()) .setCode(codeGrpc.value())
.setMessage(message) .setMessage(message)
(Seq(errInfo) ++ retryInfo.toList ++ requestInfo.toList ++ resourceInfo) (maybeErrInfo.toList ++ retryInfo.toList ++ requestInfo.toList ++ resourceInfo)
.foldLeft(statusBuilder) { case (acc, item) => .foldLeft(statusBuilder) { case (acc, item) =>
acc.addDetails(item) acc.addDetails(item)
} }

View File

@ -3,10 +3,9 @@
package com.daml.error.definitions package com.daml.error.definitions
import com.daml.error.ErrorCode.LoggingApiException
import com.daml.error._ import com.daml.error._
import com.daml.error.definitions.ErrorGroups.ParticipantErrorGroup.IndexErrorGroup 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.") @Explanation("Errors raised by the Participant Index persistence layer.")
object IndexErrors extends IndexErrorGroup { object IndexErrors extends IndexErrorGroup {
@ -19,11 +18,10 @@ object IndexErrors extends IndexErrorGroup {
extends ErrorCode( extends ErrorCode(
id = "INDEX_DB_SQL_TRANSIENT_ERROR", id = "INDEX_DB_SQL_TRANSIENT_ERROR",
ErrorCategory.TransientServerFailure, ErrorCategory.TransientServerFailure,
) ) {
with HasUnapply {
case class Reject(throwable: Throwable)(implicit case class Reject(throwable: Throwable)(implicit
val loggingContext: ContextualizedErrorLogger val loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl( ) extends DbError(
cause = cause =
s"Processing the request failed due to a transient database error: ${throwable.getMessage}", s"Processing the request failed due to a transient database error: ${throwable.getMessage}",
throwableO = Some(throwable), throwableO = Some(throwable),
@ -38,11 +36,10 @@ object IndexErrors extends IndexErrorGroup {
extends ErrorCode( extends ErrorCode(
id = "INDEX_DB_SQL_NON_TRANSIENT_ERROR", id = "INDEX_DB_SQL_NON_TRANSIENT_ERROR",
ErrorCategory.SystemInternalAssumptionViolated, ErrorCategory.SystemInternalAssumptionViolated,
) ) {
with HasUnapply {
case class Reject(throwable: Throwable)(implicit case class Reject(throwable: Throwable)(implicit
val loggingContext: ContextualizedErrorLogger val loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl( ) extends DbError(
cause = cause =
s"Processing the request failed due to a non-transient database error: ${throwable.getMessage}", s"Processing the request failed due to a non-transient database error: ${throwable.getMessage}",
throwableO = Some(throwable), throwableO = Some(throwable),
@ -57,21 +54,34 @@ object IndexErrors extends IndexErrorGroup {
extends ErrorCode( extends ErrorCode(
id = "INDEX_DB_INVALID_RESULT_SET", id = "INDEX_DB_INVALID_RESULT_SET",
ErrorCategory.SystemInternalAssumptionViolated, ErrorCategory.SystemInternalAssumptionViolated,
) ) {
with HasUnapply {
case class Reject(message: String)(implicit case class Reject(message: String)(implicit
val loggingContext: ContextualizedErrorLogger val loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl( ) extends DbError(
cause = message cause = message
) )
} }
} }
trait HasUnapply { // Decorator that returns a specialized StatusRuntimeException (IndexDbException)
this: ErrorCode => // that can be used for precise matching of persistence exceptions (e.g. for index initialization failures that need retrying).
// TODO error codes: Create a generic unapply for ErrorCode that returns the ErrorCode instance // Without this specialization, internal errors just appear as StatusRuntimeExceptions (see INDEX_DB_SQL_NON_TRANSIENT_ERROR)
// and match against that one. // without any marker, impeding us to assert whether they are emitted by the persistence layer or not.
def unapply(exception: StatusRuntimeException): Option[Unit] = abstract class DbError(
if (ErrorDetails.isErrorCode(exception)(errorCode = this)) Some(()) else None 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)
} }

View File

@ -54,10 +54,11 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
id = "REQUEST_TIME_OUT", id = "REQUEST_TIME_OUT",
ErrorCategory.DeadlineExceededRequestStateUnknown, ErrorCategory.DeadlineExceededRequestStateUnknown,
) { ) {
case class Reject(message: String, override val definiteAnswer: Boolean)(implicit case class Reject(_message: String, _definiteAnswer: Boolean)(implicit
loggingContext: ContextualizedErrorLogger loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl( ) extends LoggingTransactionErrorImpl(
cause = message cause = _message,
definiteAnswer = _definiteAnswer,
) )
} }
@ -275,17 +276,23 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
id = "SERVICE_NOT_RUNNING", id = "SERVICE_NOT_RUNNING",
ErrorCategory.TransientServerFailure, ErrorCategory.TransientServerFailure,
) { ) {
case class Reject(serviceName: String)(implicit case class Reject(_serviceName: String)(implicit
loggingContext: ContextualizedErrorLogger loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl( ) 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 loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl( ) 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.") @Explanation("Authentication errors.")
@ -355,14 +362,14 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
id = "PACKAGE_NOT_FOUND", id = "PACKAGE_NOT_FOUND",
ErrorCategory.InvalidGivenCurrentSystemStateResourceMissing, ErrorCategory.InvalidGivenCurrentSystemStateResourceMissing,
) { ) {
case class Reject(packageId: String)(implicit case class Reject(_packageId: String)(implicit
loggingContext: ContextualizedErrorLogger loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl( ) extends LoggingTransactionErrorImpl(
cause = "Could not find package." cause = "Could not find package."
) { ) {
override def resources: Seq[(ErrorResource, String)] = { 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, ErrorCategory.InvalidGivenCurrentSystemStateResourceMissing,
) { ) {
case class Reject(transactionId: String)(implicit loggingContext: ContextualizedErrorLogger) case class Reject(_transactionId: String)(implicit
extends LoggingTransactionErrorImpl(cause = "Transaction not found, or not visible.") { loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl(cause = "Transaction not found, or not visible.") {
override def resources: Seq[(ErrorResource, String)] = Seq( 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." cause = "The ledger configuration could not be retrieved."
) )
case class RejectWithMessage(message: String)(implicit case class RejectWithMessage(_message: String)(implicit
loggingContext: ContextualizedErrorLogger loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl( ) 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", id = "PARTICIPANT_PRUNED_DATA_ACCESSED",
ErrorCategory.InvalidGivenCurrentSystemStateOther, ErrorCategory.InvalidGivenCurrentSystemStateOther,
) { ) {
case class Reject(message: String)(implicit case class Reject(override val cause: String)(implicit
loggingContext: ContextualizedErrorLogger loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl( ) extends LoggingTransactionErrorImpl(cause = cause)
cause = message
)
} }
@Explanation( @Explanation(
@ -443,10 +449,11 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
id = "OFFSET_AFTER_LEDGER_END", id = "OFFSET_AFTER_LEDGER_END",
ErrorCategory.InvalidGivenCurrentSystemStateSeekAfterEnd, ErrorCategory.InvalidGivenCurrentSystemStateSeekAfterEnd,
) { ) {
case class Reject(offsetType: String, requestedOffset: String, ledgerEnd: String)(implicit case class Reject(_offsetType: String, _requestedOffset: String, _ledgerEnd: String)(implicit
loggingContext: ContextualizedErrorLogger loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl( ) 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", id = "OFFSET_OUT_OF_RANGE",
ErrorCategory.InvalidGivenCurrentSystemStateOther, ErrorCategory.InvalidGivenCurrentSystemStateOther,
) { ) {
case class Reject(message: String)(implicit case class Reject(_message: String)(implicit
loggingContext: ContextualizedErrorLogger loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl(cause = message) ) extends LoggingTransactionErrorImpl(cause = _message)
} }
@Explanation( @Explanation(
@ -488,11 +495,14 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
@Resolution("Inspect the reason given and correct your application.") @Resolution("Inspect the reason given and correct your application.")
object MissingField object MissingField
extends ErrorCode(id = "MISSING_FIELD", ErrorCategory.InvalidIndependentOfSystemState) { extends ErrorCode(id = "MISSING_FIELD", ErrorCategory.InvalidIndependentOfSystemState) {
case class Reject(missingField: String)(implicit case class Reject(_missingField: String)(implicit
loggingContext: ContextualizedErrorLogger loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl( ) 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( @Explanation(
@ -550,14 +560,14 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
ErrorCategory.InvalidIndependentOfSystemState, ErrorCategory.InvalidIndependentOfSystemState,
) { ) {
case class Error( case class Error(
fieldName: String, _fieldName: String,
offsetValue: String, _offsetValue: String,
message: String, _message: String,
)(implicit )(implicit
override val loggingContext: ContextualizedErrorLogger override val loggingContext: ContextualizedErrorLogger
) extends BaseError.Impl( ) extends BaseError.Impl(
cause = 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", id = "CONFIGURATION_ENTRY_REJECTED",
ErrorCategory.InvalidGivenCurrentSystemStateOther, ErrorCategory.InvalidGivenCurrentSystemStateOther,
) { ) {
case class Reject(message: String)(implicit case class Reject(_message: String)(implicit
loggingContext: ContextualizedErrorLogger loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl( ) extends LoggingTransactionErrorImpl(
cause = message cause = _message
) )
} }
@ -647,10 +657,10 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
id = "PACKAGE_UPLOAD_REJECTED", id = "PACKAGE_UPLOAD_REJECTED",
ErrorCategory.InvalidGivenCurrentSystemStateOther, ErrorCategory.InvalidGivenCurrentSystemStateOther,
) { ) {
case class Reject(message: String)(implicit case class Reject(_message: String)(implicit
loggingContext: ContextualizedErrorLogger loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl( ) extends LoggingTransactionErrorImpl(
cause = message cause = _message
) )
} }
} }
@ -673,15 +683,16 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
) { ) {
case class Reject( case class Reject(
override val definiteAnswer: Boolean = false, _definiteAnswer: Boolean = false,
existingCommandSubmissionId: Option[String], _existingCommandSubmissionId: Option[String],
)(implicit )(implicit
loggingContext: ContextualizedErrorLogger loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl( ) 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] = 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, ErrorCategory.InvalidGivenCurrentSystemStateResourceMissing,
) { ) {
case class MultipleContractsNotFound(notFoundContractIds: Set[String])(implicit case class MultipleContractsNotFound(_notFoundContractIds: Set[String])(implicit
loggingContext: ContextualizedErrorLogger loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl( ) extends LoggingTransactionErrorImpl(
cause = s"Unknown contracts: ${notFoundContractIds.mkString("[", ", ", "]")}" cause = s"Unknown contracts: ${_notFoundContractIds.mkString("[", ", ", "]")}"
) { ) {
override def resources: Seq[(ErrorResource, String)] = Seq( 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 ErrorCategory.InvalidGivenCurrentSystemStateOther, // It may succeed at a later time
) { ) {
case class RejectEnriched( case class RejectEnriched(
message: String, override val cause: String,
ledger_time: Instant, ledger_time: Instant,
ledger_time_lower_bound: Instant, ledger_time_lower_bound: Instant,
ledger_time_upper_bound: Instant, ledger_time_upper_bound: Instant,
)(implicit loggingContext: ContextualizedErrorLogger) )(implicit loggingContext: ContextualizedErrorLogger)
extends LoggingTransactionErrorImpl( extends LoggingTransactionErrorImpl(cause = cause)
cause = s"Invalid ledger time: $message"
)
case class RejectSimple( case class RejectSimple(
details: String override val cause: String
)(implicit loggingContext: ContextualizedErrorLogger) )(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", id = "PARTY_NOT_KNOWN_ON_LEDGER",
ErrorCategory.InvalidGivenCurrentSystemStateResourceMissing, ErrorCategory.InvalidGivenCurrentSystemStateResourceMissing,
) { ) {
case class Reject(parties: Set[String])(implicit case class Reject(_parties: Set[String])(implicit
loggingContext: ContextualizedErrorLogger loggingContext: ContextualizedErrorLogger
) extends LoggingTransactionErrorImpl(cause = s"Parties not known on ledger: ${parties ) extends LoggingTransactionErrorImpl(cause = s"Parties not known on ledger: ${_parties
.mkString("[", ",", "]")}") { .mkString("[", ",", "]")}") {
override def resources: Seq[(ErrorResource, String)] = override def resources: Seq[(ErrorResource, String)] =
parties.map((ErrorResource.Party, _)).toSeq _parties.map((ErrorResource.Party, _)).toSeq
} }
@deprecated @deprecated

View File

@ -3,11 +3,8 @@
package com.daml.error.utils package com.daml.error.utils
import com.daml.error.ErrorCode
import com.google.protobuf import com.google.protobuf
import com.google.rpc.{ErrorInfo, RequestInfo, ResourceInfo, RetryInfo} import com.google.rpc.{ErrorInfo, RequestInfo, ResourceInfo, RetryInfo}
import io.grpc.StatusRuntimeException
import io.grpc.protobuf.StatusProto
import scala.jdk.CollectionConverters._ import scala.jdk.CollectionConverters._
@ -15,7 +12,8 @@ object ErrorDetails {
sealed trait ErrorDetail extends Product with Serializable sealed trait ErrorDetail extends Product with Serializable
final case class ResourceInfoDetail(name: String, typ: String) extends ErrorDetail 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 RetryInfoDetail(retryDelayInSeconds: Long) extends ErrorDetail
final case class RequestInfoDetail(requestId: String) extends ErrorDetail final case class RequestInfoDetail(requestId: String) extends ErrorDetail
@ -26,7 +24,7 @@ object ErrorDetails {
case any if any.is(classOf[ErrorInfo]) => case any if any.is(classOf[ErrorInfo]) =>
val v = any.unpack(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]) => case any if any.is(classOf[RetryInfo]) =>
val v = any.unpack(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|") 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
}
}
} }

View File

@ -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 { 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 correlationId = "12345678"
val actualGrpcError = error.asGrpcErrorFromContext(errorLoggingContext(Some(correlationId))) val actualGrpcError = error.asGrpcErrorFromContext(errorLoggingContext(Some(correlationId)))
@ -78,13 +79,38 @@ class ErrorCodeSpec extends AnyFlatSpec with Matchers with BeforeAndAfter {
actualGrpcError.getMessage shouldBe expectedErrorMessage actualGrpcError.getMessage shouldBe expectedErrorMessage
errorDetails should contain theSameElementsAs Seq( 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.RetryInfoDetail(TransientServerFailure.retryable.get.duration.toSeconds),
ErrorDetails.RequestInfoDetail(correlationId), ErrorDetails.RequestInfoDetail(correlationId),
ErrorDetails.ResourceInfoDetail(error.resources.head._1.asString, error.resources.head._2), 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( private def logSeriousError(
cause: String = "the error argument", cause: String = "the error argument",
extra: Map[String, String] = Map.empty, extra: Map[String, String] = Map.empty,

View File

@ -13,7 +13,13 @@ case object SeriousError
extends ErrorCode("BLUE_SCREEN", ErrorCategory.SystemInternalAssumptionViolated)( extends ErrorCode("BLUE_SCREEN", ErrorCategory.SystemInternalAssumptionViolated)(
ErrorClass.root() 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 */ /** The error code, usually passed in as implicit where the error class is defined */
override def code: ErrorCode = SeriousError.code override def code: ErrorCode = SeriousError.code

View File

@ -19,7 +19,11 @@ object MildErrors
"TEST_ROUTINE_FAILURE_PLEASE_IGNORE", "TEST_ROUTINE_FAILURE_PLEASE_IGNORE",
ErrorCategory.TransientServerFailure, 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 { extends BaseError {
override def code: ErrorCode = NotSoSeriousError.code override def code: ErrorCode = NotSoSeriousError.code

View File

@ -5,7 +5,6 @@ package com.daml.ledger.api.auth
import com.daml.error.ErrorCodesVersionSwitcher import com.daml.error.ErrorCodesVersionSwitcher
import com.daml.ledger.api.auth.interceptor.AuthorizationInterceptor import com.daml.ledger.api.auth.interceptor.AuthorizationInterceptor
import com.google.rpc.ErrorInfo
import io.grpc.protobuf.StatusProto import io.grpc.protobuf.StatusProto
import io.grpc.{Metadata, ServerCall, Status} import io.grpc.{Metadata, ServerCall, Status}
import org.mockito.captor.ArgCaptor 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 <no-correlation-id>" actualStatus.getDescription shouldBe "An error occurred. Please contact the operator and inquire about the request <no-correlation-id>"
val actualRpcStatus = StatusProto.fromStatusAndTrailers(actualStatus, actualMetadata) val actualRpcStatus = StatusProto.fromStatusAndTrailers(actualStatus, actualMetadata)
actualRpcStatus.getDetailsList.size() shouldBe 1 actualRpcStatus.getDetailsList.size() shouldBe 0
val errorInfo = actualRpcStatus.getDetailsList.get(0).unpack(classOf[ErrorInfo])
errorInfo.getReason shouldBe "INTERNAL_AUTHORIZATION_ERROR"
} }
} }

View File

@ -76,7 +76,7 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch
v2 = LedgerApiErrors.RequestTimeOut v2 = LedgerApiErrors.RequestTimeOut
.Reject( .Reject(
"Timed out while awaiting for a completion corresponding to a command submission.", "Timed out while awaiting for a completion corresponding to a command submission.",
definiteAnswer = false, _definiteAnswer = false,
) )
.asGrpcStatusFromContext, .asGrpcStatusFromContext,
) )
@ -149,7 +149,7 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch
errorCodesVersionSwitcher.choose( errorCodesVersionSwitcher.choose(
v1 = io.grpc.Status.NOT_FOUND.asRuntimeException(), v1 = io.grpc.Status.NOT_FOUND.asRuntimeException(),
v2 = LedgerApiErrors.RequestValidation.NotFound.Package v2 = LedgerApiErrors.RequestValidation.NotFound.Package
.Reject(packageId = packageId) .Reject(_packageId = packageId)
.asGrpcError, .asGrpcError,
) )
@ -184,7 +184,7 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch
exception exception
}, },
v2 = LedgerApiErrors.ConsistencyErrors.DuplicateCommand v2 = LedgerApiErrors.ConsistencyErrors.DuplicateCommand
.Reject(existingCommandSubmissionId = existingSubmissionId) .Reject(_existingCommandSubmissionId = existingSubmissionId)
.asGrpcError, .asGrpcError,
) )
@ -309,9 +309,9 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch
v1 = invalidArgumentV1(definiteAnswer, message), v1 = invalidArgumentV1(definiteAnswer, message),
v2 = LedgerApiErrors.RequestValidation.NonHexOffset v2 = LedgerApiErrors.RequestValidation.NonHexOffset
.Error( .Error(
fieldName = fieldName, _fieldName = fieldName,
offsetValue = offsetValue, _offsetValue = offsetValue,
message = message, _message = message,
) )
.asGrpcError, .asGrpcError,
) )
@ -385,11 +385,7 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch
errorCodesVersionSwitcher.choose( errorCodesVersionSwitcher.choose(
v1 = aborted(message, definiteAnswer), v1 = aborted(message, definiteAnswer),
v2 = LedgerApiErrors.RequestTimeOut v2 = LedgerApiErrors.RequestTimeOut
.Reject( .Reject(message, definiteAnswer.getOrElse(false))
message,
// TODO error codes: How to handle None definiteAnswer?
definiteAnswer.getOrElse(false),
)
.asGrpcError, .asGrpcError,
) )
} }

View File

@ -50,16 +50,18 @@ class ErrorFactoriesSpec
val failureReason = "some db transient failure" val failureReason = "some db transient failure"
val someSqlTransientException = new SQLTransientException(failureReason) val someSqlTransientException = new SQLTransientException(failureReason)
assertV2Error( assertV2Error(
SelfServiceErrorCodeFactories SelfServiceErrorCodeFactories.sqlTransientException(someSqlTransientException)
.sqlTransientException(someSqlTransientException)
)( )(
expectedCode = Code.UNAVAILABLE, expectedCode = Code.UNAVAILABLE,
expectedMessage = expectedMessage =
s"INDEX_DB_SQL_TRANSIENT_ERROR(1,$truncatedCorrelationId): Processing the request failed due to a transient database error: $failureReason", s"INDEX_DB_SQL_TRANSIENT_ERROR(1,$truncatedCorrelationId): Processing the request failed due to a transient database error: $failureReason",
expectedDetails = Seq[ErrorDetails.ErrorDetail]( expectedDetails = Seq[ErrorDetails.ErrorDetail](
ErrorDetails.ErrorInfoDetail("INDEX_DB_SQL_TRANSIENT_ERROR"),
expectedCorrelationIdRequestInfo, expectedCorrelationIdRequestInfo,
ErrorDetails.RetryInfoDetail(1), 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, expectedCode = Code.INTERNAL,
expectedMessage = expectedMessage =
s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId", s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId",
expectedDetails = Seq[ErrorDetails.ErrorDetail]( expectedDetails = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo),
ErrorDetails.ErrorInfoDetail("INDEX_DB_SQL_NON_TRANSIENT_ERROR"),
expectedCorrelationIdRequestInfo,
),
) )
} }
"TrackerErrors" should { "TrackerErrors" should {
val errorDetails = com.google.protobuf.Any.pack[ErrorInfo]( val errorDetails = com.google.protobuf.Any.pack[ErrorInfo](ErrorInfo.newBuilder().build())
ErrorInfo
.newBuilder()
.build()
)
"return failedToEnqueueCommandSubmission" in { "return failedToEnqueueCommandSubmission" in {
val t = new Exception("message123") val t = new Exception("message123")
@ -100,10 +95,7 @@ class ErrorFactoriesSpec
v2_code = Code.INTERNAL, v2_code = Code.INTERNAL,
v2_message = v2_message =
s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId", s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo),
ErrorDetails.ErrorInfoDetail("LEDGER_API_INTERNAL_ERROR"),
expectedCorrelationIdRequestInfo,
),
) )
} }
@ -118,7 +110,14 @@ class ErrorFactoriesSpec
v2_message = v2_message =
s"PARTICIPANT_BACKPRESSURE(2,$truncatedCorrelationId): The participant is overloaded: Some buffer is full", s"PARTICIPANT_BACKPRESSURE(2,$truncatedCorrelationId): The participant is overloaded: Some buffer is full",
v2_details = Seq[ErrorDetails.ErrorDetail]( 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, expectedCorrelationIdRequestInfo,
ErrorDetails.RetryInfoDetail(1), ErrorDetails.RetryInfoDetail(1),
), ),
@ -138,7 +137,14 @@ class ErrorFactoriesSpec
v2_message = v2_message =
s"SERVICE_NOT_RUNNING(1,$truncatedCorrelationId): Some service has been shut down.", s"SERVICE_NOT_RUNNING(1,$truncatedCorrelationId): Some service has been shut down.",
v2_details = Seq[ErrorDetails.ErrorDetail]( 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, expectedCorrelationIdRequestInfo,
ErrorDetails.RetryInfoDetail(1), ErrorDetails.RetryInfoDetail(1),
), ),
@ -158,7 +164,10 @@ class ErrorFactoriesSpec
v2_message = v2_message =
s"REQUEST_TIME_OUT(3,$truncatedCorrelationId): Timed out while awaiting for a completion corresponding to a command submission.", s"REQUEST_TIME_OUT(3,$truncatedCorrelationId): Timed out while awaiting for a completion corresponding to a command submission.",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](
ErrorDetails.ErrorInfoDetail("REQUEST_TIME_OUT"), ErrorDetails.ErrorInfoDetail(
"REQUEST_TIME_OUT",
Map("category" -> "3", "definite_answer" -> "false"),
),
expectedCorrelationIdRequestInfo, expectedCorrelationIdRequestInfo,
ErrorDetails.RetryInfoDetail(1), ErrorDetails.RetryInfoDetail(1),
), ),
@ -176,10 +185,7 @@ class ErrorFactoriesSpec
v2_code = Code.INTERNAL, v2_code = Code.INTERNAL,
v2_message = v2_message =
s"An error occurred. Please contact the operator and inquire about the request cor-id-12345679", s"An error occurred. Please contact the operator and inquire about the request cor-id-12345679",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo),
ErrorDetails.ErrorInfoDetail("LEDGER_API_INTERNAL_ERROR"),
expectedCorrelationIdRequestInfo,
),
) )
} }
@ -194,7 +200,10 @@ class ErrorFactoriesSpec
v2_code = Code.NOT_FOUND, v2_code = Code.NOT_FOUND,
v2_message = s"PACKAGE_NOT_FOUND(11,$truncatedCorrelationId): Could not find package.", v2_message = s"PACKAGE_NOT_FOUND(11,$truncatedCorrelationId): Could not find package.",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](
ErrorDetails.ErrorInfoDetail("PACKAGE_NOT_FOUND"), ErrorDetails.ErrorInfoDetail(
"PACKAGE_NOT_FOUND",
Map("category" -> "11", "definite_answer" -> "false"),
),
expectedCorrelationIdRequestInfo, expectedCorrelationIdRequestInfo,
ErrorDetails.ResourceInfoDetail("PACKAGE", "packageId123"), ErrorDetails.ResourceInfoDetail("PACKAGE", "packageId123"),
), ),
@ -209,10 +218,7 @@ class ErrorFactoriesSpec
v2_code = Code.INTERNAL, v2_code = Code.INTERNAL,
v2_message = v2_message =
s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId", s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo),
ErrorDetails.ErrorInfoDetail("LEDGER_API_INTERNAL_ERROR"),
expectedCorrelationIdRequestInfo,
),
) )
} }
@ -224,7 +230,10 @@ class ErrorFactoriesSpec
v2_code = Code.FAILED_PRECONDITION, v2_code = Code.FAILED_PRECONDITION,
v2_message = s"CONFIGURATION_ENTRY_REJECTED(9,$truncatedCorrelationId): message123", v2_message = s"CONFIGURATION_ENTRY_REJECTED(9,$truncatedCorrelationId): message123",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](
ErrorDetails.ErrorInfoDetail("CONFIGURATION_ENTRY_REJECTED"), ErrorDetails.ErrorInfoDetail(
"CONFIGURATION_ENTRY_REJECTED",
Map("category" -> "9", "definite_answer" -> "false"),
),
expectedCorrelationIdRequestInfo, expectedCorrelationIdRequestInfo,
), ),
) )
@ -239,7 +248,10 @@ class ErrorFactoriesSpec
v2_message = v2_message =
s"TRANSACTION_NOT_FOUND(11,$truncatedCorrelationId): Transaction not found, or not visible.", s"TRANSACTION_NOT_FOUND(11,$truncatedCorrelationId): Transaction not found, or not visible.",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](
ErrorDetails.ErrorInfoDetail("TRANSACTION_NOT_FOUND"), ErrorDetails.ErrorInfoDetail(
"TRANSACTION_NOT_FOUND",
Map("category" -> "11", "definite_answer" -> "false"),
),
expectedCorrelationIdRequestInfo, expectedCorrelationIdRequestInfo,
ErrorDetails.ResourceInfoDetail("TRANSACTION_ID", "tId"), ErrorDetails.ResourceInfoDetail("TRANSACTION_ID", "tId"),
), ),
@ -255,7 +267,10 @@ class ErrorFactoriesSpec
v2_message = v2_message =
s"DUPLICATE_COMMAND(10,$truncatedCorrelationId): A command with the given command id has already been successfully processed", s"DUPLICATE_COMMAND(10,$truncatedCorrelationId): A command with the given command id has already been successfully processed",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](
ErrorDetails.ErrorInfoDetail("DUPLICATE_COMMAND"), ErrorDetails.ErrorInfoDetail(
"DUPLICATE_COMMAND",
Map("category" -> "10", "definite_answer" -> "false"),
),
expectedCorrelationIdRequestInfo, expectedCorrelationIdRequestInfo,
), ),
) )
@ -269,10 +284,7 @@ class ErrorFactoriesSpec
v2_code = Code.PERMISSION_DENIED, v2_code = Code.PERMISSION_DENIED,
v2_message = v2_message =
s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId", s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo),
ErrorDetails.ErrorInfoDetail("PERMISSION_DENIED"),
expectedCorrelationIdRequestInfo,
),
) )
} }
@ -286,7 +298,10 @@ class ErrorFactoriesSpec
v2_code = Code.DEADLINE_EXCEEDED, v2_code = Code.DEADLINE_EXCEEDED,
v2_message = s"REQUEST_TIME_OUT(3,$truncatedCorrelationId): message123", v2_message = s"REQUEST_TIME_OUT(3,$truncatedCorrelationId): message123",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](
ErrorDetails.ErrorInfoDetail("REQUEST_TIME_OUT"), ErrorDetails.ErrorInfoDetail(
"REQUEST_TIME_OUT",
Map("category" -> "3", "definite_answer" -> "false"),
),
expectedCorrelationIdRequestInfo, expectedCorrelationIdRequestInfo,
ErrorDetails.RetryInfoDetail(1), ErrorDetails.RetryInfoDetail(1),
), ),
@ -308,7 +323,7 @@ class ErrorFactoriesSpec
v2_message = v2_message =
s"NON_HEXADECIMAL_OFFSET(8,$truncatedCorrelationId): Offset in fieldName123 not specified in hexadecimal: offsetValue123: message123", s"NON_HEXADECIMAL_OFFSET(8,$truncatedCorrelationId): Offset in fieldName123 not specified in hexadecimal: offsetValue123: message123",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](
ErrorDetails.ErrorInfoDetail("NON_HEXADECIMAL_OFFSET"), ErrorDetails.ErrorInfoDetail("NON_HEXADECIMAL_OFFSET", Map("category" -> "8")),
expectedCorrelationIdRequestInfo, expectedCorrelationIdRequestInfo,
), ),
) )
@ -323,7 +338,10 @@ class ErrorFactoriesSpec
v2_code = Code.OUT_OF_RANGE, v2_code = Code.OUT_OF_RANGE,
v2_message = s"OFFSET_AFTER_LEDGER_END(12,$truncatedCorrelationId): $expectedMessage", v2_message = s"OFFSET_AFTER_LEDGER_END(12,$truncatedCorrelationId): $expectedMessage",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](
ErrorDetails.ErrorInfoDetail("OFFSET_AFTER_LEDGER_END"), ErrorDetails.ErrorInfoDetail(
"OFFSET_AFTER_LEDGER_END",
Map("category" -> "12", "definite_answer" -> "false"),
),
expectedCorrelationIdRequestInfo, expectedCorrelationIdRequestInfo,
), ),
) )
@ -337,7 +355,10 @@ class ErrorFactoriesSpec
v2_code = Code.FAILED_PRECONDITION, v2_code = Code.FAILED_PRECONDITION,
v2_message = s"OFFSET_OUT_OF_RANGE(9,$truncatedCorrelationId): message123", v2_message = s"OFFSET_OUT_OF_RANGE(9,$truncatedCorrelationId): message123",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](
ErrorDetails.ErrorInfoDetail("OFFSET_OUT_OF_RANGE"), ErrorDetails.ErrorInfoDetail(
"OFFSET_OUT_OF_RANGE",
Map("category" -> "9", "definite_answer" -> "false"),
),
expectedCorrelationIdRequestInfo, expectedCorrelationIdRequestInfo,
), ),
) )
@ -351,10 +372,7 @@ class ErrorFactoriesSpec
v2_code = Code.UNAUTHENTICATED, v2_code = Code.UNAUTHENTICATED,
v2_message = v2_message =
s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId", s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo),
ErrorDetails.ErrorInfoDetail("UNAUTHENTICATED"),
expectedCorrelationIdRequestInfo,
),
) )
} }
@ -368,10 +386,7 @@ class ErrorFactoriesSpec
v2_code = Code.INTERNAL, v2_code = Code.INTERNAL,
v2_message = v2_message =
s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId", s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo),
ErrorDetails.ErrorInfoDetail("INTERNAL_AUTHORIZATION_ERROR"),
expectedCorrelationIdRequestInfo,
),
) )
} }
@ -393,7 +408,10 @@ class ErrorFactoriesSpec
v2_message = v2_message =
s"LEDGER_CONFIGURATION_NOT_FOUND(11,$truncatedCorrelationId): The ledger configuration could not be retrieved.", s"LEDGER_CONFIGURATION_NOT_FOUND(11,$truncatedCorrelationId): The ledger configuration could not be retrieved.",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](
ErrorDetails.ErrorInfoDetail("LEDGER_CONFIGURATION_NOT_FOUND"), ErrorDetails.ErrorInfoDetail(
"LEDGER_CONFIGURATION_NOT_FOUND",
Map("category" -> "11", "definite_answer" -> "false"),
),
expectedCorrelationIdRequestInfo, expectedCorrelationIdRequestInfo,
), ),
) )
@ -419,8 +437,9 @@ class ErrorFactoriesSpec
"return an invalid deduplication period error" in { "return an invalid deduplication period error" in {
val errorDetailMessage = "message" val errorDetailMessage = "message"
val field = "field" val field = "field"
val maxDeduplicationDuration = Duration.ofSeconds(5)
assertVersionedError( assertVersionedError(
_.invalidDeduplicationDuration(field, errorDetailMessage, None, Duration.ofSeconds(5)) _.invalidDeduplicationDuration(field, errorDetailMessage, None, maxDeduplicationDuration)
)( )(
v1_code = Code.INVALID_ARGUMENT, v1_code = Code.INVALID_ARGUMENT,
v1_message = s"Invalid field $field: $errorDetailMessage", v1_message = s"Invalid field $field: $errorDetailMessage",
@ -429,7 +448,14 @@ class ErrorFactoriesSpec
v2_message = v2_message =
s"INVALID_DEDUPLICATION_PERIOD(9,$truncatedCorrelationId): The submitted command had an invalid deduplication period: $errorDetailMessage", s"INVALID_DEDUPLICATION_PERIOD(9,$truncatedCorrelationId): The submitted command had an invalid deduplication period: $errorDetailMessage",
v2_details = Seq[ErrorDetails.ErrorDetail]( 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, expectedCorrelationIdRequestInfo,
), ),
) )
@ -442,16 +468,20 @@ class ErrorFactoriesSpec
(Some(false), Seq(definiteAnswers(false))), (Some(false), Seq(definiteAnswers(false))),
) )
val fieldName = "my field"
forEvery(testCases) { (definiteAnswer, expectedDetails) => forEvery(testCases) { (definiteAnswer, expectedDetails) =>
assertVersionedError(_.invalidField("my field", "my message", definiteAnswer))( assertVersionedError(_.invalidField(fieldName, "my message", definiteAnswer))(
v1_code = Code.INVALID_ARGUMENT, v1_code = Code.INVALID_ARGUMENT,
v1_message = "Invalid field my field: my message", v1_message = "Invalid field " + fieldName + ": my message",
v1_details = expectedDetails, v1_details = expectedDetails,
v2_code = Code.INVALID_ARGUMENT, v2_code = Code.INVALID_ARGUMENT,
v2_message = 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]( v2_details = Seq[ErrorDetails.ErrorDetail](
ErrorDetails.ErrorInfoDetail("INVALID_FIELD"), ErrorDetails.ErrorInfoDetail(
"INVALID_FIELD",
Map("category" -> "8", "definite_answer" -> "false"),
),
expectedCorrelationIdRequestInfo, expectedCorrelationIdRequestInfo,
), ),
) )
@ -476,7 +506,10 @@ class ErrorFactoriesSpec
v2_message = v2_message =
s"LEDGER_ID_MISMATCH(11,$truncatedCorrelationId): Ledger ID 'received' not found. Actual Ledger ID is 'expected'.", s"LEDGER_ID_MISMATCH(11,$truncatedCorrelationId): Ledger ID 'received' not found. Actual Ledger ID is 'expected'.",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](
ErrorDetails.ErrorInfoDetail("LEDGER_ID_MISMATCH"), ErrorDetails.ErrorInfoDetail(
"LEDGER_ID_MISMATCH",
Map("category" -> "11", "definite_answer" -> "true"),
),
expectedCorrelationIdRequestInfo, expectedCorrelationIdRequestInfo,
), ),
) )
@ -499,7 +532,10 @@ class ErrorFactoriesSpec
v2_code = Code.FAILED_PRECONDITION, v2_code = Code.FAILED_PRECONDITION,
v2_message = s"PARTICIPANT_PRUNED_DATA_ACCESSED(9,$truncatedCorrelationId): my message", v2_message = s"PARTICIPANT_PRUNED_DATA_ACCESSED(9,$truncatedCorrelationId): my message",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](
ErrorDetails.ErrorInfoDetail("PARTICIPANT_PRUNED_DATA_ACCESSED"), ErrorDetails.ErrorInfoDetail(
"PARTICIPANT_PRUNED_DATA_ACCESSED",
Map("category" -> "9", "definite_answer" -> "false"),
),
expectedCorrelationIdRequestInfo, expectedCorrelationIdRequestInfo,
), ),
) )
@ -513,10 +549,7 @@ class ErrorFactoriesSpec
v2_code = Code.INTERNAL, v2_code = Code.INTERNAL,
v2_message = v2_message =
s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId", s"An error occurred. Please contact the operator and inquire about the request $originalCorrelationId",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](expectedCorrelationIdRequestInfo),
ErrorDetails.ErrorInfoDetail("LEDGER_API_INTERNAL_ERROR"),
expectedCorrelationIdRequestInfo,
),
) )
} }
@ -537,7 +570,10 @@ class ErrorFactoriesSpec
v2_message = v2_message =
s"SERVICE_NOT_RUNNING(1,$truncatedCorrelationId): $serviceName has been shut down.", s"SERVICE_NOT_RUNNING(1,$truncatedCorrelationId): $serviceName has been shut down.",
v2_details = Seq[ErrorDetails.ErrorDetail]( 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, expectedCorrelationIdRequestInfo,
ErrorDetails.RetryInfoDetail(1), ErrorDetails.RetryInfoDetail(1),
), ),
@ -557,7 +593,10 @@ class ErrorFactoriesSpec
v2_message = v2_message =
s"SERVICE_NOT_RUNNING(1,$truncatedCorrelationId): $serviceName is currently being reset.", s"SERVICE_NOT_RUNNING(1,$truncatedCorrelationId): $serviceName is currently being reset.",
v2_details = Seq[ErrorDetails.ErrorDetail]( 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, expectedCorrelationIdRequestInfo,
ErrorDetails.RetryInfoDetail(1), ErrorDetails.RetryInfoDetail(1),
), ),
@ -565,6 +604,8 @@ class ErrorFactoriesSpec
} }
"return a missingField error" in { "return a missingField error" in {
val fieldName = "my field"
val testCases = Table( val testCases = Table(
("definite answer", "expected details"), ("definite answer", "expected details"),
(None, Seq.empty), (None, Seq.empty),
@ -572,15 +613,18 @@ class ErrorFactoriesSpec
) )
forEvery(testCases) { (definiteAnswer, expectedDetails) => forEvery(testCases) { (definiteAnswer, expectedDetails) =>
assertVersionedError(_.missingField("my field", definiteAnswer))( assertVersionedError(_.missingField(fieldName, definiteAnswer))(
v1_code = Code.INVALID_ARGUMENT, v1_code = Code.INVALID_ARGUMENT,
v1_message = "Missing field: my field", v1_message = "Missing field: " + fieldName,
v1_details = expectedDetails, v1_details = expectedDetails,
v2_code = Code.INVALID_ARGUMENT, v2_code = Code.INVALID_ARGUMENT,
v2_message = 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]( v2_details = Seq[ErrorDetails.ErrorDetail](
ErrorDetails.ErrorInfoDetail("MISSING_FIELD"), ErrorDetails.ErrorInfoDetail(
"MISSING_FIELD",
Map("category" -> "8", "definite_answer" -> "false", "field_name" -> fieldName),
),
expectedCorrelationIdRequestInfo, expectedCorrelationIdRequestInfo,
), ),
) )
@ -603,7 +647,10 @@ class ErrorFactoriesSpec
v2_message = v2_message =
s"INVALID_ARGUMENT(8,$truncatedCorrelationId): The submitted command has invalid arguments: my message", s"INVALID_ARGUMENT(8,$truncatedCorrelationId): The submitted command has invalid arguments: my message",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](
ErrorDetails.ErrorInfoDetail("INVALID_ARGUMENT"), ErrorDetails.ErrorInfoDetail(
"INVALID_ARGUMENT",
Map("category" -> "8", "definite_answer" -> "false"),
),
expectedCorrelationIdRequestInfo, expectedCorrelationIdRequestInfo,
), ),
) )
@ -626,7 +673,10 @@ class ErrorFactoriesSpec
v2_message = v2_message =
s"INVALID_ARGUMENT(8,$truncatedCorrelationId): The submitted command has invalid arguments: my message", s"INVALID_ARGUMENT(8,$truncatedCorrelationId): The submitted command has invalid arguments: my message",
v2_details = Seq[ErrorDetails.ErrorDetail]( v2_details = Seq[ErrorDetails.ErrorDetail](
ErrorDetails.ErrorInfoDetail("INVALID_ARGUMENT"), ErrorDetails.ErrorInfoDetail(
"INVALID_ARGUMENT",
Map("category" -> "8", "definite_answer" -> "false"),
),
expectedCorrelationIdRequestInfo, expectedCorrelationIdRequestInfo,
), ),
) )

View File

@ -7,7 +7,7 @@ import akka.Done
import akka.actor.Cancellable import akka.actor.Cancellable
import akka.stream._ import akka.stream._
import akka.stream.scaladsl.{Keep, Sink, Source} 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.domain.LedgerId
import com.daml.ledger.api.health.HealthStatus import com.daml.ledger.api.health.HealthStatus
import com.daml.ledger.offset.Offset import com.daml.ledger.offset.Offset
@ -147,12 +147,11 @@ private[platform] object ReadOnlySqlLedger {
executionContext: ExecutionContext, executionContext: ExecutionContext,
loggingContext: LoggingContext, loggingContext: LoggingContext,
): Future[LedgerId] = { ): 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] = { val isRetryable: PartialFunction[Throwable, Boolean] = {
// If the index database is not yet fully initialized, case _: IndexDbException => true
// 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 _: LedgerIdNotFoundException => true case _: LedgerIdNotFoundException => true
case _: MismatchException.LedgerId => false case _: MismatchException.LedgerId => false
case _ => false case _ => false

View File

@ -132,7 +132,7 @@ class ConversionsSpec extends AsyncWordSpec with Matchers {
v1expectedCode = Status.Code.ABORTED.value(), v1expectedCode = Status.Code.ABORTED.value(),
v1expectedMessage = "Invalid ledger time: Too late.", v1expectedMessage = "Invalid ledger time: Too late.",
v2expectedCode = Status.Code.FAILED_PRECONDITION.value(), 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.",
) )
} }
} }

View File

@ -59,7 +59,6 @@ class RejectionSpec extends AnyWordSpec with Matchers {
errorInfo shouldBe com.google.rpc.error_details.ErrorInfo( errorInfo shouldBe com.google.rpc.error_details.ErrorInfo(
reason = "LEDGER_CONFIGURATION_NOT_FOUND", reason = "LEDGER_CONFIGURATION_NOT_FOUND",
metadata = Map( metadata = Map(
"message" -> "Cannot validate ledger time",
"category" -> "11", "category" -> "11",
"definite_answer" -> "false", "definite_answer" -> "false",
), ),
@ -104,7 +103,7 @@ class RejectionSpec extends AnyWordSpec with Matchers {
Rejection.InvalidLedgerTime(outOfRange).toStateRejectionReason(errorFactoriesV2) Rejection.InvalidLedgerTime(outOfRange).toStateRejectionReason(errorFactoriesV2)
actualRejectionReason.code shouldBe Status.Code.FAILED_PRECONDITION.value() 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) 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_lower_bound" -> "2021-07-20T09:00:00Z",
"ledger_time_upper_bound" -> "2021-07-20T09:10:00Z", "ledger_time_upper_bound" -> "2021-07-20T09:10:00Z",
"category" -> "9", "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", "definite_answer" -> "false",
), ),
) )

View File

@ -383,7 +383,7 @@ final class SqlLedgerSpec
case Seq(Completion(`commandId1`, Some(status), _, _, _, _, _)) => case Seq(Completion(`commandId1`, Some(status), _, _, _, _, _)) =>
status.code should be(Status.Code.FAILED_PRECONDITION.value) status.code should be(Status.Code.FAILED_PRECONDITION.value)
status.message should be( 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]"
) )
} }
} }