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 aa4bfeb8567c..c8ba647fbae1 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 @@ -3,6 +3,8 @@ package com.daml.error.definitions +import java.time.Duration + import com.daml.error._ import com.daml.error.definitions.ErrorGroups.ParticipantErrorGroup.TransactionErrorGroup.LedgerApiErrorGroup import com.daml.lf.data.Ref @@ -295,11 +297,14 @@ object LedgerApiErrors extends LedgerApiErrorGroup { id = "INVALID_DEDUPLICATION_PERIOD", ErrorCategory.InvalidGivenCurrentSystemStateOther, ) { - case class Reject(_reason: String)(implicit + case class Reject(_reason: String, _maxDeduplicationDuration: Duration)(implicit loggingContext: ContextualizedErrorLogger ) extends LoggingTransactionErrorImpl( cause = s"The submitted command had an invalid deduplication period: ${_reason}" - ) + ) { + override def context: Map[String, String] = + super.context + ("max_deduplication_duration" -> _maxDeduplicationDuration.toString) + } } } diff --git a/ledger/ledger-api-common/BUILD.bazel b/ledger/ledger-api-common/BUILD.bazel index f59120241aed..73d6d2c3e9e9 100644 --- a/ledger/ledger-api-common/BUILD.bazel +++ b/ledger/ledger-api-common/BUILD.bazel @@ -79,6 +79,8 @@ da_scala_library( "//language-support/scala/bindings", "//ledger/ledger-api-domain", "//libs-scala/concurrent", + "//libs-scala/grpc-utils", + "@maven//:com_google_api_grpc_proto_google_common_protos", "@maven//:org_scalatest_scalatest_compatible", ], ) 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 169fd59cea2d..5f4f9cda3f08 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 @@ -4,6 +4,7 @@ package com.daml.platform.server.api.validation import java.sql.{SQLNonTransientException, SQLTransientException} +import java.time.Duration import com.daml.error.ErrorCode.ApiException import com.daml.error.definitions.{IndexErrors, LedgerApiErrors} @@ -231,11 +232,12 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch fieldName: String, message: String, definiteAnswer: Option[Boolean], + maxDeduplicationDuration: Duration, )(implicit contextualizedErrorLogger: ContextualizedErrorLogger): StatusRuntimeException = errorCodesVersionSwitcher.choose( legacyInvalidField(fieldName, message, definiteAnswer), LedgerApiErrors.CommandValidation.InvalidDeduplicationPeriodField - .Reject(message) + .Reject(message, maxDeduplicationDuration) .asGrpcError, ) diff --git a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/FieldValidations.scala b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/FieldValidations.scala index 20a50b060db1..b4578a0e7cde 100644 --- a/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/FieldValidations.scala +++ b/ledger/ledger-api-common/src/main/scala/com/digitalasset/platform/server/api/validation/FieldValidations.scala @@ -162,6 +162,7 @@ class FieldValidations private (errorFactories: ErrorFactories) { fieldName, exceedsMaxDurationMessage, definiteAnswer = Some(false), + maxDeduplicationTime, ) ) else Right(duration) diff --git a/ledger/ledger-api-common/src/test/lib/scala/com/digitalasset/ledger/api/validation/ValidatorTestUtils.scala b/ledger/ledger-api-common/src/test/lib/scala/com/digitalasset/ledger/api/validation/ValidatorTestUtils.scala index 537bd0754ef8..c1eb646914e4 100644 --- a/ledger/ledger-api-common/src/test/lib/scala/com/digitalasset/ledger/api/validation/ValidatorTestUtils.scala +++ b/ledger/ledger-api-common/src/test/lib/scala/com/digitalasset/ledger/api/validation/ValidatorTestUtils.scala @@ -3,9 +3,11 @@ package com.daml.ledger.api.validation +import com.daml.grpc.GrpcStatus import com.daml.ledger.api.domain import com.daml.ledger.api.messages.transaction import com.daml.lf.data.Ref +import com.google.rpc.error_details import io.grpc.Status.Code import io.grpc.StatusRuntimeException import org.scalatest._ @@ -40,16 +42,19 @@ trait ValidatorTestUtils extends Matchers with Inside with OptionValues { self: expectedDescriptionV1: String, expectedCodeV2: Code, expectedDescriptionV2: String, + metadataV2: Map[String, String] = Map.empty, ): Assertion = { requestMustFailWith( request = testedRequest(testedFactory(false)), code = expectedCodeV1, description = expectedDescriptionV1, + metadata = Map.empty, ) requestMustFailWith( request = testedRequest(testedFactory(true)), code = expectedCodeV2, description = expectedDescriptionV2, + metadataV2, ) } @@ -85,24 +90,32 @@ trait ValidatorTestUtils extends Matchers with Inside with OptionValues { self: request: Future[_], code: Code, description: String, + metadata: Map[String, String], ): Future[Assertion] = { val f = request.map(Right(_)).recover { case ex: StatusRuntimeException => Left(ex) } - f.map(inside(_)(isError(code, description))) + f.map(inside(_)(isError(code, description, metadata))) } protected def requestMustFailWith( request: Either[StatusRuntimeException, _], code: Code, description: String, + metadata: Map[String, String], ): Assertion = { - inside(request)(isError(code, description)) + inside(request)(isError(code, description, metadata)) } protected def isError( expectedCode: Code, expectedDescription: String, + metadata: Map[String, String], ): PartialFunction[Either[StatusRuntimeException, _], Assertion] = { case Left(err) => err.getStatus should have(Symbol("code")(expectedCode)) err.getStatus should have(Symbol("description")(expectedDescription)) + GrpcStatus + .toProto(err.getStatus, err.getTrailers) + .details + .flatMap(_.unpack[error_details.ErrorInfo].metadata) + .toMap should contain allElementsOf metadata } } diff --git a/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/SubmitRequestValidatorTest.scala b/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/SubmitRequestValidatorTest.scala index 14637ebf6ed6..d97abcb9ec80 100644 --- a/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/SubmitRequestValidatorTest.scala +++ b/ledger/ledger-api-common/src/test/suite/scala/com/digitalasset/ledger/api/validation/SubmitRequestValidatorTest.scala @@ -418,6 +418,9 @@ class SubmitRequestValidatorTest expectedCodeV2 = FAILED_PRECONDITION, expectedDescriptionV2 = s"INVALID_DEDUPLICATION_PERIOD(9,0): The submitted command had an invalid deduplication period: The given deduplication duration of ${java.time.Duration .ofSeconds(durationSecondsExceedingMax)} exceeds the maximum deduplication time of ${internal.maxDeduplicationDuration}", + metadataV2 = Map( + "max_deduplication_duration" -> internal.maxDeduplicationDuration.toString + ), ) } }