Port error code changes (#11113)

* Port changes from Canton PR #7365

CHANGELOG_BEGIN
CHANGELOG_END

* Ported ErrorResource changes

* Ported constant use change

* Fix compilation issues
* Missing dependency on ledger-grpc
* Compilation issue SI-4440 in RejectionGenerators

* Re-review
This commit is contained in:
tudor-da 2021-10-05 18:33:24 +02:00 committed by GitHub
parent 9fd8182bbb
commit 31db15d555
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 113 additions and 35 deletions

View File

@ -21,18 +21,18 @@ object ErrorResource {
def fromString(str: String): Option[ErrorResource] = all.find(_.asString == str)
object ContractId extends ErrorResource {
def asString: String = "contract-id"
def asString: String = "CONTRACT_ID"
}
object ContractKey extends ErrorResource {
def asString: String = "contract-key"
def asString: String = "CONTRACT_KEY"
}
object DalfPackage extends ErrorResource {
def asString: String = "lf-package"
def asString: String = "PACKAGE"
}
object LedgerId extends ErrorResource {
def asString: String = "ledger-id"
def asString: String = "LEDGER_ID"
}
object CommandId extends ErrorResource {
def asString: String = "command-id"
def asString: String = "COMMAND_ID"
}
}

View File

@ -52,6 +52,7 @@ compile_deps = [
"//ledger/ledger-api-domain",
"//ledger/ledger-api-health",
"//ledger/ledger-configuration",
"//ledger/ledger-grpc",
"//ledger/ledger-offset",
"//ledger/ledger-resources",
"//ledger/metrics",

View File

@ -273,8 +273,27 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
object GenericInterpretationError
extends ErrorCode(
id = "DAML_INTERPRETATION_ERROR",
// TODO error codes: this is a bad error message and needs to be fixed by also adjusting the expected ledger-api conformance tests
ErrorCategory.InvalidIndependentOfSystemState, // (is INVALID_ARGUMENT, should be PRECONDITION_FAILED)
ErrorCategory.InvalidGivenCurrentSystemStateOther,
) {
case class Error(override val cause: String)(implicit
loggingContext: LoggingContext,
logger: ContextualizedLogger,
correlationId: CorrelationId,
) extends LoggingTransactionErrorImpl(
cause = cause
)
}
@Explanation(
"""This error occurs if the Daml transaction failed during interpretation due to an invalid argument."""
)
@Resolution("This error type occurs if there is an application error.")
object InvalidArgumentInterpretationError
extends ErrorCode(
id = "DAML_INTERPRETER_INVALID_ARGUMENT",
ErrorCategory.InvalidIndependentOfSystemState,
) {
case class Error(override val cause: String)(implicit
@ -292,10 +311,9 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
)
@Resolution("This error indicates an application error.")
object ContractNotActive
// Is INVALID_ARGUMENT, SHOULD BE NOT_EXISTS
extends ErrorCode(
id = "CONTRACT_NOT_ACTIVE",
ErrorCategory.InvalidIndependentOfSystemState,
ErrorCategory.InvalidGivenCurrentSystemStateResourceMissing,
) {
case class Reject(
@ -351,8 +369,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
object ContractNotFound
extends ErrorCode(
id = "CONTRACT_NOT_FOUND",
// TODO error codes: this is a bad error message and needs to be fixed by also adjusting the expected ledger-api conformance tests
ErrorCategory.IsAbortShouldBePrecondition, // IS ABORTED, should be NOT_EXISTS
ErrorCategory.InvalidGivenCurrentSystemStateResourceMissing,
) {
case class Reject(
@ -381,8 +398,7 @@ object LedgerApiErrors extends LedgerApiErrorGroup {
object ContractKeyNotFound
extends ErrorCode(
id = "CONTRACT_KEY_NOT_FOUND",
// TODO error codes: this is a bad error message and needs to be fixed by also adjusting the expected ledger-api conformance tests
ErrorCategory.InvalidIndependentOfSystemState, // IS INVALID_ARGUMENT, should be NOT_EXISTS
ErrorCategory.InvalidGivenCurrentSystemStateResourceMissing,
) {
case class Reject(

View File

@ -3,26 +3,56 @@
package com.daml.platform.apiserver.error
import com.daml.error.BaseError
import com.daml.error.{BaseError, ErrorCode}
import com.daml.ledger.participant.state
import com.daml.lf.engine.Error.{Interpretation, Package, Preprocessing, Validation}
import com.daml.lf.engine.{Error => LfError}
import com.daml.lf.interpretation.{Error => LfInterpretationError}
import com.daml.logging.{ContextualizedLogger, LoggingContext}
import com.daml.platform.apiserver.error.RejectionGenerators.ErrorCauseExport
import com.daml.platform.store.ErrorCause
import io.grpc.Status.Code
import io.grpc.StatusRuntimeException
import io.grpc.protobuf.StatusProto
import scala.util.{Failure, Success, Try}
object RejectionGenerators {
class RejectionGenerators(conformanceMode: Boolean) {
private val adjustErrors = Map(
LedgerApiErrors.InterpreterErrors.LookupErrors.ContractKeyNotFound -> Code.INVALID_ARGUMENT,
LedgerApiErrors.InterpreterErrors.ContractNotActive -> Code.INVALID_ARGUMENT,
LedgerApiErrors.InterpreterErrors.LookupErrors.ContractNotFound -> Code.ABORTED,
LedgerApiErrors.InterpreterErrors.LookupErrors.ContractKeyNotFound -> Code.INVALID_ARGUMENT,
LedgerApiErrors.InterpreterErrors.GenericInterpretationError -> Code.INVALID_ARGUMENT,
)
private def enforceConformance(ex: StatusRuntimeException): StatusRuntimeException =
if (!conformanceMode) ex
else {
adjustErrors
.find { case (k, _) =>
ex.getStatus.getDescription.startsWith(k.id + "(")
}
.fold(ex) { case (_, newGrpcCode) =>
val parsed = StatusProto.fromThrowable(ex)
// rewrite status to use "conformance" code
val bld = com.google.rpc.Status
.newBuilder()
.setCode(newGrpcCode.value())
.setMessage(parsed.getMessage)
.addAllDetails(parsed.getDetailsList)
val newEx = StatusProto.toStatusRuntimeException(bld.build())
// strip stack trace from exception
new ErrorCode.ApiException(newEx.getStatus, newEx.getTrailers)
}
}
def toGrpc(reject: BaseError)(implicit
logger: ContextualizedLogger,
loggingContext: LoggingContext,
correlationId: CorrelationId,
): StatusRuntimeException =
reject.asGrpcErrorFromContext(correlationId.id, logger)(loggingContext)
enforceConformance(reject.asGrpcErrorFromContext(correlationId.id, logger)(loggingContext))
def duplicateCommand(implicit
logger: ContextualizedLogger,
@ -66,6 +96,7 @@ object RejectionGenerators {
def processDamlException(
err: com.daml.lf.interpretation.Error,
renderedMessage: String,
detailMessage: Option[String],
): BaseError = {
// detailMessage is only suitable for server side debugging but not for the user, so don't pass except on internal errors
@ -85,25 +116,41 @@ object RejectionGenerators {
case LfInterpretationError.DuplicateContractKey(key) =>
LedgerApiErrors.InterpreterErrors.DuplicateContractKey.Reject(renderedMessage, key)
case _: LfInterpretationError.UnhandledException =>
LedgerApiErrors.InterpreterErrors.GenericInterpretationError.Error(renderedMessage)
LedgerApiErrors.InterpreterErrors.GenericInterpretationError.Error(
renderedMessage + detailMessage.fold("")(x => ". Details: " + x)
)
case _: LfInterpretationError.UserError =>
LedgerApiErrors.InterpreterErrors.GenericInterpretationError.Error(renderedMessage)
case _: LfInterpretationError.TemplatePreconditionViolated =>
LedgerApiErrors.InterpreterErrors.GenericInterpretationError.Error(renderedMessage)
case _: LfInterpretationError.CreateEmptyContractKeyMaintainers =>
LedgerApiErrors.InterpreterErrors.GenericInterpretationError.Error(renderedMessage)
LedgerApiErrors.InterpreterErrors.InvalidArgumentInterpretationError.Error(
renderedMessage
)
case _: LfInterpretationError.FetchEmptyContractKeyMaintainers =>
LedgerApiErrors.InterpreterErrors.GenericInterpretationError.Error(renderedMessage)
LedgerApiErrors.InterpreterErrors.InvalidArgumentInterpretationError.Error(
renderedMessage
)
case _: LfInterpretationError.WronglyTypedContract =>
LedgerApiErrors.InterpreterErrors.GenericInterpretationError.Error(renderedMessage)
LedgerApiErrors.InterpreterErrors.InvalidArgumentInterpretationError.Error(
renderedMessage
)
case LfInterpretationError.NonComparableValues =>
LedgerApiErrors.InterpreterErrors.GenericInterpretationError.Error(renderedMessage)
LedgerApiErrors.InterpreterErrors.InvalidArgumentInterpretationError.Error(
renderedMessage
)
case _: LfInterpretationError.ContractIdInContractKey =>
LedgerApiErrors.InterpreterErrors.GenericInterpretationError.Error(renderedMessage)
LedgerApiErrors.InterpreterErrors.InvalidArgumentInterpretationError.Error(
renderedMessage
)
case LfInterpretationError.ValueExceedsMaxNesting =>
LedgerApiErrors.InterpreterErrors.GenericInterpretationError.Error(renderedMessage)
LedgerApiErrors.InterpreterErrors.InvalidArgumentInterpretationError.Error(
renderedMessage
)
case _: LfInterpretationError.ContractIdComparability =>
LedgerApiErrors.InterpreterErrors.GenericInterpretationError.Error(renderedMessage)
LedgerApiErrors.InterpreterErrors.InvalidArgumentInterpretationError.Error(
renderedMessage
)
}
}
@ -114,7 +161,8 @@ object RejectionGenerators {
err match {
case Interpretation.Internal(location, message) =>
LedgerApiErrors.InternalError.Interpretation(location, message, detailMessage)
case m @ Interpretation.DamlException(error) => processDamlException(error, m.message)
case m @ Interpretation.DamlException(error) =>
processDamlException(error, m.message, detailMessage)
}
def processLfError(error: LfError) = {
@ -178,7 +226,9 @@ object RejectionGenerators {
reject
}
}
}
object RejectionGenerators {
sealed trait ErrorCauseExport
object ErrorCauseExport {
final case class DamlLf(error: LfError) extends ErrorCauseExport

View File

@ -11,9 +11,20 @@ import com.daml.ledger.participant.state.v2.Update.CommandRejected.{
}
import com.daml.logging.{ContextualizedLogger, LoggingContext}
import com.google.rpc.status.{Status => RpcStatus}
import io.grpc.StatusRuntimeException
import io.grpc.{Status, StatusRuntimeException}
trait TransactionError extends BaseError {
@Deprecated
def createRejectionDeprecated(
rewrite: Map[ErrorCode, Status.Code]
)(implicit
logger: ContextualizedLogger,
loggingContext: LoggingContext,
correlationId: Option[String],
): RejectionReasonTemplate = {
FinalReason(_rpcStatus(rewrite.get(this.code), correlationId))
}
def createRejection(
correlationId: Option[String]
)(implicit
@ -30,6 +41,12 @@ trait TransactionError extends BaseError {
def rpcStatus(
correlationId: Option[String]
)(implicit logger: ContextualizedLogger, loggingContext: LoggingContext): RpcStatus =
_rpcStatus(None, correlationId)
def _rpcStatus(
overrideCode: Option[Status.Code],
correlationId: Option[String],
)(implicit logger: ContextualizedLogger, loggingContext: LoggingContext): RpcStatus = {
// yes, this is a horrible duplication of ErrorCode.asGrpcError. why? because
@ -38,16 +55,10 @@ trait TransactionError extends BaseError {
// objects. however, the sync-api uses the scala variant whereas we have to return StatusRuntimeExceptions.
// therefore, we have to compose the status code a second time here ...
// the ideal fix would be to extend scalapb accordingly ...
val ErrorCode.StatusInfo(codeGrpc, messageWithoutContext, contextMap, _) =
val ErrorCode.StatusInfo(codeGrpc, message, contextMap, _) =
code.getStatusInfo(this, correlationId, logger)(loggingContext)
// TODO error codes: avoid appending the context to the description. right now, we need to do that as the ledger api server is throwing away any error details
val message =
if (code.category.securitySensitive) messageWithoutContext
else messageWithoutContext + "; " + code.formatContextAsString(contextMap)
val definiteAnswerKey =
"definite_answer" // TODO error codes: Can we use a constant from some upstream class?
val definiteAnswerKey = com.daml.ledger.grpc.GrpcStatuses.DefiniteAnswerKey
val metadata = if (code.category.securitySensitive) Map.empty[String, String] else contextMap
val errorInfo = com.google.rpc.error_details.ErrorInfo(
@ -82,7 +93,7 @@ trait TransactionError extends BaseError {
) ++ retryInfoO.toList ++ requestInfoO.toList ++ resourceInfos
com.google.rpc.status.Status(
codeGrpc.value(),
overrideCode.getOrElse(codeGrpc).value(),
message,
details,
)