mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-20 01:07:18 +03:00
Remove pre-1.18 error codes [DPP-773] (#12841)
* Remove legacy error codes CHANGELOG_BEGIN CHANGELOG_END * Remove ValidatorFixture * Removed redundant helper function * Remove redundant object * Rebased. run-full-compat: true * Rebased. run-full-compat: true
This commit is contained in:
parent
d97f9815b2
commit
dfd38186fe
@ -75,7 +75,6 @@ da_scala_library(
|
||||
"//language-support/java/bindings:bindings-java",
|
||||
"//ledger-api/grpc-definitions:ledger_api_proto_scala",
|
||||
"//ledger-api/rs-grpc-bridge",
|
||||
"//ledger/error",
|
||||
"//ledger/ledger-api-auth",
|
||||
"//ledger/ledger-api-common",
|
||||
"//ledger/participant-state-index",
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
package com.daml.ledger.rxjava.grpc.helpers
|
||||
|
||||
import com.daml.error.ErrorCodesVersionSwitcher
|
||||
import java.net.{InetSocketAddress, SocketAddress}
|
||||
import java.time.{Clock, Duration}
|
||||
import java.util.concurrent.TimeUnit
|
||||
@ -54,7 +53,6 @@ final class LedgerServices(val ledgerId: String) {
|
||||
() => Clock.systemUTC().instant(),
|
||||
ledgerId,
|
||||
participantId,
|
||||
new ErrorCodesVersionSwitcher(enableSelfServiceErrorCodes = true),
|
||||
new InMemoryUserManagementStore(),
|
||||
executionContext,
|
||||
userRightsCheckIntervalInSeconds = 1,
|
||||
@ -102,7 +100,6 @@ final class LedgerServices(val ledgerId: String) {
|
||||
authService,
|
||||
Some(new InMemoryUserManagementStore()),
|
||||
executionContext,
|
||||
new ErrorCodesVersionSwitcher(enableSelfServiceErrorCodes = true),
|
||||
)
|
||||
services
|
||||
.foldLeft(newServerBuilder())(_ addService _)
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
package com.daml.ledger
|
||||
|
||||
import com.daml.error.ErrorCodesVersionSwitcher
|
||||
import java.time.Clock
|
||||
import java.util.UUID
|
||||
|
||||
@ -35,7 +34,6 @@ package object rxjava {
|
||||
() => Clock.systemUTC().instant(),
|
||||
"testLedgerId",
|
||||
"testParticipantId",
|
||||
new ErrorCodesVersionSwitcher(enableSelfServiceErrorCodes = true),
|
||||
new InMemoryUserManagementStore(),
|
||||
ExecutionContext.parasitic,
|
||||
userRightsCheckIntervalInSeconds = 1,
|
||||
|
@ -1,16 +0,0 @@
|
||||
// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.error
|
||||
import io.grpc.StatusRuntimeException
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
class ErrorCodesVersionSwitcher(enableSelfServiceErrorCodes: Boolean)
|
||||
extends ValueSwitch(enableSelfServiceErrorCodes) {
|
||||
|
||||
def chooseAsFailedFuture[T](
|
||||
v1: => StatusRuntimeException,
|
||||
v2: => StatusRuntimeException,
|
||||
): Future[T] = Future.failed(choose(v1 = v1, v2 = v2))
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.error
|
||||
|
||||
/** A mechanism to switch between the legacy error codes (v1) and the new self-service error codes (v2).
|
||||
* This class is intended to facilitate transition to self-service error codes.
|
||||
* Once the previous error codes are removed, this class and its specializations should be dropped as well.
|
||||
*/
|
||||
class ValueSwitch(enableSelfServiceErrorCodes: Boolean) {
|
||||
def choose[X](
|
||||
v1: => X,
|
||||
v2: => X,
|
||||
): X =
|
||||
if (enableSelfServiceErrorCodes) {
|
||||
v2
|
||||
} else {
|
||||
v1
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.daml.error
|
||||
|
||||
import io.grpc.{Status, StatusRuntimeException}
|
||||
import org.scalatest.flatspec.AnyFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
|
||||
class ValueSwitchSpec extends AnyFlatSpec with Matchers {
|
||||
|
||||
behavior of classOf[ValueSwitch].getSimpleName
|
||||
|
||||
it should "use self-service (v2) error codes" in {
|
||||
// given
|
||||
val tested = new ValueSwitch(enableSelfServiceErrorCodes = true)
|
||||
|
||||
// when
|
||||
val actual =
|
||||
tested.choose(
|
||||
v1 = fail("This argument should be evaluated lazily!"),
|
||||
v2 = aStatusRuntimeException,
|
||||
)
|
||||
|
||||
// then
|
||||
actual shouldBe aStatusRuntimeException
|
||||
}
|
||||
|
||||
it should "use legacy (v1) error codes" in {
|
||||
// given
|
||||
val tested = new ValueSwitch(enableSelfServiceErrorCodes = false)
|
||||
|
||||
// when
|
||||
val actual =
|
||||
tested.choose(
|
||||
v1 = aStatusRuntimeException,
|
||||
v2 = fail("This argument should be evaluated lazily!"),
|
||||
)
|
||||
|
||||
// then
|
||||
actual shouldBe aStatusRuntimeException
|
||||
}
|
||||
|
||||
private lazy val aStatusRuntimeException = new StatusRuntimeException(Status.INTERNAL)
|
||||
}
|
@ -76,7 +76,6 @@ object Main {
|
||||
keyValueSource,
|
||||
metrics,
|
||||
failOnUnexpectedEvent = false,
|
||||
enableSelfServiceErrorCodes = true,
|
||||
)
|
||||
|
||||
// Note: this method is doing quite a lot of work to transform a sequence of write sets
|
||||
|
@ -7,11 +7,7 @@ import java.time.Instant
|
||||
|
||||
import akka.actor.Scheduler
|
||||
import com.daml.error.definitions.LedgerApiErrors
|
||||
import com.daml.error.{
|
||||
ContextualizedErrorLogger,
|
||||
DamlContextualizedErrorLogger,
|
||||
ErrorCodesVersionSwitcher,
|
||||
}
|
||||
import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger}
|
||||
import com.daml.ledger.api.auth.interceptor.AuthorizationInterceptor
|
||||
import com.daml.ledger.api.v1.transaction_filter.TransactionFilter
|
||||
import com.daml.ledger.participant.state.index.v2.UserManagementStore
|
||||
@ -31,14 +27,13 @@ final class Authorizer(
|
||||
now: () => Instant,
|
||||
ledgerId: String,
|
||||
participantId: String,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
userManagementStore: UserManagementStore,
|
||||
ec: ExecutionContext,
|
||||
userRightsCheckIntervalInSeconds: Int,
|
||||
akkaScheduler: Scheduler,
|
||||
)(implicit loggingContext: LoggingContext) {
|
||||
private val logger = ContextualizedLogger.get(this.getClass)
|
||||
private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
private val errorFactories = ErrorFactories()
|
||||
private implicit val errorLogger: ContextualizedErrorLogger =
|
||||
new DamlContextualizedErrorLogger(logger, loggingContext, None)
|
||||
|
||||
@ -209,7 +204,7 @@ final class Authorizer(
|
||||
case Some(applicationId) if applicationId.nonEmpty => Right(applicationId)
|
||||
case _ =>
|
||||
Left(
|
||||
errorFactories.invalidArgument(None)(
|
||||
errorFactories.invalidArgument(
|
||||
"Cannot default application_id field because claims do not specify an application-id or user-id. Is authentication turned on?"
|
||||
)
|
||||
)
|
||||
@ -330,7 +325,6 @@ object Authorizer {
|
||||
now: () => Instant,
|
||||
ledgerId: String,
|
||||
participantId: String,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
userManagementStore: UserManagementStore,
|
||||
ec: ExecutionContext,
|
||||
userRightsCheckIntervalInSeconds: Int,
|
||||
@ -341,7 +335,6 @@ object Authorizer {
|
||||
now = now,
|
||||
ledgerId = ledgerId,
|
||||
participantId = participantId,
|
||||
errorCodesVersionSwitcher = errorCodesVersionSwitcher,
|
||||
userManagementStore = userManagementStore,
|
||||
ec = ec,
|
||||
userRightsCheckIntervalInSeconds = userRightsCheckIntervalInSeconds,
|
||||
|
@ -4,7 +4,7 @@
|
||||
package com.daml.ledger.api.auth.interceptor
|
||||
|
||||
import com.daml.error.definitions.LedgerApiErrors
|
||||
import com.daml.error.{DamlContextualizedErrorLogger, ErrorCodesVersionSwitcher}
|
||||
import com.daml.error.DamlContextualizedErrorLogger
|
||||
import com.daml.ledger.api.auth._
|
||||
import com.daml.ledger.api.domain.UserRight
|
||||
import com.daml.ledger.participant.state.index.v2.UserManagementStore
|
||||
@ -26,12 +26,11 @@ final class AuthorizationInterceptor(
|
||||
authService: AuthService,
|
||||
userManagementStoreO: Option[UserManagementStore],
|
||||
implicit val ec: ExecutionContext,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
)(implicit loggingContext: LoggingContext)
|
||||
extends ServerInterceptor {
|
||||
private val logger = ContextualizedLogger.get(getClass)
|
||||
private val errorLogger = new DamlContextualizedErrorLogger(logger, loggingContext, None)
|
||||
private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
private val errorFactories = ErrorFactories()
|
||||
|
||||
override def interceptCall[ReqT, RespT](
|
||||
call: ServerCall[ReqT, RespT],
|
||||
@ -127,7 +126,7 @@ final class AuthorizationInterceptor(
|
||||
Ref.UserId.fromString(userIdStr) match {
|
||||
case Left(err) =>
|
||||
Future.failed(
|
||||
errorFactories.invalidArgument(None)(s"token $err")(errorLogger)
|
||||
errorFactories.invalidArgument(s"token $err")(errorLogger)
|
||||
)
|
||||
case Right(userId) =>
|
||||
Future.successful(userId)
|
||||
@ -155,10 +154,9 @@ object AuthorizationInterceptor {
|
||||
authService: AuthService,
|
||||
userManagementStoreO: Option[UserManagementStore],
|
||||
ec: ExecutionContext,
|
||||
errorCodesStatusSwitcher: ErrorCodesVersionSwitcher,
|
||||
): AuthorizationInterceptor =
|
||||
LoggingContext.newLoggingContext { implicit loggingContext: LoggingContext =>
|
||||
new AuthorizationInterceptor(authService, userManagementStoreO, ec, errorCodesStatusSwitcher)
|
||||
new AuthorizationInterceptor(authService, userManagementStoreO, ec)
|
||||
}
|
||||
|
||||
def convertUserRightsToClaims(userRights: Set[UserRight]): Seq[Claim] = {
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
package com.daml.ledger.api.auth
|
||||
|
||||
import com.daml.error.ErrorCodesVersionSwitcher
|
||||
import com.daml.ledger.api.auth.interceptor.AuthorizationInterceptor
|
||||
import io.grpc.protobuf.StatusProto
|
||||
import io.grpc.{Metadata, ServerCall, Status}
|
||||
@ -29,16 +28,8 @@ class AuthorizationInterceptorSpec
|
||||
|
||||
behavior of s"$className.interceptCall"
|
||||
|
||||
it should "close the ServerCall with a V1 status code on decoding failure" in {
|
||||
testServerCloseError(usesSelfServiceErrorCodes = false) { case (actualStatus, actualMetadata) =>
|
||||
actualStatus.getCode shouldBe Status.Code.INTERNAL
|
||||
actualStatus.getDescription shouldBe "Failed to get claims from request metadata"
|
||||
actualMetadata.keys() shouldBe empty
|
||||
}
|
||||
}
|
||||
|
||||
it should "close the ServerCall with a V2 status code on decoding failure" in {
|
||||
testServerCloseError(usesSelfServiceErrorCodes = true) { case (actualStatus, actualMetadata) =>
|
||||
testServerCloseError { case (actualStatus, actualMetadata) =>
|
||||
actualStatus.getCode shouldBe Status.Code.INTERNAL
|
||||
actualStatus.getDescription shouldBe "An error occurred. Please contact the operator and inquire about the request <no-correlation-id>"
|
||||
|
||||
@ -47,9 +38,7 @@ class AuthorizationInterceptorSpec
|
||||
}
|
||||
}
|
||||
|
||||
private def testServerCloseError(
|
||||
usesSelfServiceErrorCodes: Boolean
|
||||
)(assertRpcStatus: (Status, Metadata) => Assertion) = {
|
||||
private def testServerCloseError(assertRpcStatus: (Status, Metadata) => Assertion) = {
|
||||
val authService = mock[AuthService]
|
||||
val userManagementService = mock[UserManagementStore]
|
||||
val serverCall = mock[ServerCall[Nothing, Nothing]]
|
||||
@ -64,13 +53,11 @@ class AuthorizationInterceptorSpec
|
||||
()
|
||||
}
|
||||
|
||||
val errorCodesStatusSwitcher = new ErrorCodesVersionSwitcher(usesSelfServiceErrorCodes)
|
||||
val authorizationInterceptor =
|
||||
AuthorizationInterceptor(
|
||||
authService,
|
||||
Some(userManagementService),
|
||||
global,
|
||||
errorCodesStatusSwitcher,
|
||||
)
|
||||
|
||||
val statusCaptor = ArgCaptor[Status]
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
package com.daml.ledger.api.auth
|
||||
|
||||
import com.daml.error.ErrorCodesVersionSwitcher
|
||||
import com.daml.ledger.api.auth.interceptor.AuthorizationInterceptor
|
||||
import io.grpc.{Status, StatusRuntimeException}
|
||||
import org.scalatest.Assertion
|
||||
@ -36,41 +35,30 @@ class AuthorizerSpec
|
||||
|
||||
it should "authorize if claims are valid" in {
|
||||
contextWithClaims {
|
||||
authorizer(selfServiceErrorCodes = false)
|
||||
authorizer()
|
||||
.authorize(dummyReqRes)(allAuthorized)(dummyRequest)
|
||||
}.map(_ shouldBe expectedSuccessfulResponse)
|
||||
}
|
||||
|
||||
behavior of s"$className.authorize (V1 error codes)"
|
||||
behavior of s"$className.authorize"
|
||||
|
||||
it should "return permission denied on authorization error" in {
|
||||
testPermissionDenied(selfServiceErrorCodes = false)
|
||||
testPermissionDenied()
|
||||
}
|
||||
|
||||
behavior of s"$className.authorize (V2 error codes)"
|
||||
|
||||
it should "return permission denied on authorization error" in {
|
||||
testPermissionDenied(selfServiceErrorCodes = true)
|
||||
}
|
||||
|
||||
private def testPermissionDenied(selfServiceErrorCodes: Boolean) =
|
||||
private def testPermissionDenied() =
|
||||
contextWithClaims {
|
||||
authorizer(selfServiceErrorCodes).authorize(dummyReqRes)(unauthorized)(dummyRequest)
|
||||
authorizer().authorize(dummyReqRes)(unauthorized)(dummyRequest)
|
||||
}
|
||||
.transform(
|
||||
assertExpectedFailure(selfServiceErrorCodes = selfServiceErrorCodes)(
|
||||
Status.PERMISSION_DENIED.getCode
|
||||
)
|
||||
assertExpectedFailure(Status.PERMISSION_DENIED.getCode)
|
||||
)
|
||||
|
||||
private def assertExpectedFailure[T](
|
||||
selfServiceErrorCodes: Boolean
|
||||
)(expectedStatusCode: Status.Code): Try[T] => Try[Assertion] = {
|
||||
expectedStatusCode: Status.Code
|
||||
): Try[T] => Try[Assertion] = {
|
||||
case Failure(ex: StatusRuntimeException) =>
|
||||
ex.getStatus.getCode shouldBe expectedStatusCode
|
||||
if (selfServiceErrorCodes) {
|
||||
ex.getStatus.getDescription shouldBe "An error occurred. Please contact the operator and inquire about the request <no-correlation-id>"
|
||||
}
|
||||
Success(succeed)
|
||||
case ex => fail(s"Expected a failure with StatusRuntimeException but got $ex")
|
||||
}
|
||||
@ -80,11 +68,10 @@ class AuthorizerSpec
|
||||
.withValue(AuthorizationInterceptor.contextKeyClaimSet, ClaimSet.Claims.Wildcard)
|
||||
.call(() => f)
|
||||
|
||||
private def authorizer(selfServiceErrorCodes: Boolean) = Authorizer(
|
||||
private def authorizer() = Authorizer(
|
||||
() => Instant.ofEpochSecond(1337L),
|
||||
"some-ledger-id",
|
||||
"participant-id",
|
||||
new ErrorCodesVersionSwitcher(selfServiceErrorCodes),
|
||||
mock[UserManagementStore],
|
||||
mock[ExecutionContext],
|
||||
userRightsCheckIntervalInSeconds = 1,
|
||||
|
@ -79,134 +79,124 @@ class CompletionResponseTest extends AnyWordSpec with Matchers {
|
||||
implicit val contextualizedErrorLogger: ContextualizedErrorLogger =
|
||||
new DamlContextualizedErrorLogger(logger, loggingContext, None)
|
||||
|
||||
def testIt(useSelfServiceErrorCodes: Boolean) = {
|
||||
val errorFactories = ErrorFactories()
|
||||
|
||||
val errorFactories = ErrorFactories(useSelfServiceErrorCodes)
|
||||
|
||||
s"(self service error codes: $useSelfServiceErrorCodes)" should {
|
||||
"convert queue completion failure" in {
|
||||
val exception =
|
||||
CompletionResponse.toException(
|
||||
QueueCompletionFailure(TimeoutResponse(commandId)),
|
||||
errorFactories,
|
||||
)
|
||||
exception.getStatus.getCode shouldBe {
|
||||
if (useSelfServiceErrorCodes) Code.DEADLINE_EXCEEDED
|
||||
else Code.ABORTED
|
||||
}
|
||||
}
|
||||
|
||||
"convert queue submit failure" in {
|
||||
val status = GrpcStatus.buildStatus(
|
||||
Map.empty,
|
||||
GrpcStatus.toJavaBuilder(grpc.Status.RESOURCE_EXHAUSTED),
|
||||
)
|
||||
val exception =
|
||||
CompletionResponse.toException(
|
||||
QueueSubmitFailure(status),
|
||||
errorFactories,
|
||||
)
|
||||
exception.getStatus.getCode shouldBe Code.RESOURCE_EXHAUSTED
|
||||
}
|
||||
|
||||
"include default metadata for status not ok" in {
|
||||
val exception = CompletionResponse.toException(
|
||||
QueueCompletionFailure(
|
||||
NotOkResponse(
|
||||
Completion(
|
||||
commandId = commandId,
|
||||
status = Some(
|
||||
Status(
|
||||
Code.CANCELLED.value(),
|
||||
details = Seq.empty,
|
||||
)
|
||||
),
|
||||
),
|
||||
None,
|
||||
)
|
||||
),
|
||||
errorFactories,
|
||||
)
|
||||
val status = protobuf.StatusProto.fromThrowable(exception)
|
||||
val packedErrorInfo = status.getDetails(0).unpack(classOf[JavaErrorInfo])
|
||||
packedErrorInfo.getMetadataOrThrow(GrpcStatuses.DefiniteAnswerKey) shouldEqual "false"
|
||||
}
|
||||
|
||||
"include metadata for status not ok" in {
|
||||
val errorInfo = ErrorInfo(
|
||||
metadata = Map(GrpcStatuses.DefiniteAnswerKey -> "true")
|
||||
)
|
||||
val exception = CompletionResponse.toException(
|
||||
QueueCompletionFailure(
|
||||
NotOkResponse(
|
||||
Completion(
|
||||
commandId = commandId,
|
||||
status = Some(
|
||||
Status(
|
||||
Code.CANCELLED.value(),
|
||||
details = Seq(
|
||||
Any.pack(
|
||||
errorInfo
|
||||
)
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
None,
|
||||
)
|
||||
),
|
||||
errorFactories,
|
||||
)
|
||||
val status = protobuf.StatusProto.fromThrowable(exception)
|
||||
val packedErrorInfo = status.getDetails(0).unpack(classOf[JavaErrorInfo])
|
||||
packedErrorInfo.getMetadataOrThrow(GrpcStatuses.DefiniteAnswerKey) shouldEqual "true"
|
||||
}
|
||||
|
||||
"merge metadata for status not ok" in {
|
||||
val errorInfo = ErrorInfo(
|
||||
metadata = Map(GrpcStatuses.DefiniteAnswerKey -> "true")
|
||||
)
|
||||
val requestInfo = RequestInfo(requestId = "aRequestId")
|
||||
val exception = CompletionResponse.toException(
|
||||
QueueCompletionFailure(
|
||||
NotOkResponse(
|
||||
Completion(
|
||||
commandId = commandId,
|
||||
status = Some(
|
||||
Status(
|
||||
Code.INTERNAL.value(),
|
||||
details = Seq(
|
||||
Any.pack(errorInfo),
|
||||
Any.pack(requestInfo),
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
None,
|
||||
)
|
||||
),
|
||||
errorFactories,
|
||||
)
|
||||
|
||||
val status = protobuf.StatusProto.fromThrowable(exception)
|
||||
status.getCode shouldBe Code.INTERNAL.value()
|
||||
val details = status.getDetailsList.asScala
|
||||
details.size shouldBe 2
|
||||
details.exists { detail =>
|
||||
detail.is(classOf[JavaErrorInfo]) && detail
|
||||
.unpack(classOf[JavaErrorInfo])
|
||||
.getMetadataOrThrow(GrpcStatuses.DefiniteAnswerKey) == "true"
|
||||
} shouldEqual true
|
||||
details.exists { detail =>
|
||||
detail.is(classOf[JavaRequestInfo]) && detail
|
||||
.unpack(classOf[JavaRequestInfo])
|
||||
.getRequestId == "aRequestId"
|
||||
} shouldEqual true
|
||||
}
|
||||
}
|
||||
"convert queue completion failure" in {
|
||||
val exception =
|
||||
CompletionResponse.toException(
|
||||
QueueCompletionFailure(TimeoutResponse(commandId)),
|
||||
errorFactories,
|
||||
)
|
||||
exception.getStatus.getCode shouldBe Code.DEADLINE_EXCEEDED
|
||||
}
|
||||
|
||||
"convert queue submit failure" in {
|
||||
val status = GrpcStatus.buildStatus(
|
||||
Map.empty,
|
||||
GrpcStatus.toJavaBuilder(grpc.Status.RESOURCE_EXHAUSTED),
|
||||
)
|
||||
val exception =
|
||||
CompletionResponse.toException(
|
||||
QueueSubmitFailure(status),
|
||||
errorFactories,
|
||||
)
|
||||
exception.getStatus.getCode shouldBe Code.RESOURCE_EXHAUSTED
|
||||
}
|
||||
|
||||
"include default metadata for status not ok" in {
|
||||
val exception = CompletionResponse.toException(
|
||||
QueueCompletionFailure(
|
||||
NotOkResponse(
|
||||
Completion(
|
||||
commandId = commandId,
|
||||
status = Some(
|
||||
Status(
|
||||
Code.CANCELLED.value(),
|
||||
details = Seq.empty,
|
||||
)
|
||||
),
|
||||
),
|
||||
None,
|
||||
)
|
||||
),
|
||||
errorFactories,
|
||||
)
|
||||
val status = protobuf.StatusProto.fromThrowable(exception)
|
||||
val packedErrorInfo = status.getDetails(0).unpack(classOf[JavaErrorInfo])
|
||||
packedErrorInfo.getMetadataOrThrow(GrpcStatuses.DefiniteAnswerKey) shouldEqual "false"
|
||||
}
|
||||
|
||||
"include metadata for status not ok" in {
|
||||
val errorInfo = ErrorInfo(
|
||||
metadata = Map(GrpcStatuses.DefiniteAnswerKey -> "true")
|
||||
)
|
||||
val exception = CompletionResponse.toException(
|
||||
QueueCompletionFailure(
|
||||
NotOkResponse(
|
||||
Completion(
|
||||
commandId = commandId,
|
||||
status = Some(
|
||||
Status(
|
||||
Code.CANCELLED.value(),
|
||||
details = Seq(
|
||||
Any.pack(
|
||||
errorInfo
|
||||
)
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
None,
|
||||
)
|
||||
),
|
||||
errorFactories,
|
||||
)
|
||||
val status = protobuf.StatusProto.fromThrowable(exception)
|
||||
val packedErrorInfo = status.getDetails(0).unpack(classOf[JavaErrorInfo])
|
||||
packedErrorInfo.getMetadataOrThrow(GrpcStatuses.DefiniteAnswerKey) shouldEqual "true"
|
||||
}
|
||||
|
||||
"merge metadata for status not ok" in {
|
||||
val errorInfo = ErrorInfo(
|
||||
metadata = Map(GrpcStatuses.DefiniteAnswerKey -> "true")
|
||||
)
|
||||
val requestInfo = RequestInfo(requestId = "aRequestId")
|
||||
val exception = CompletionResponse.toException(
|
||||
QueueCompletionFailure(
|
||||
NotOkResponse(
|
||||
Completion(
|
||||
commandId = commandId,
|
||||
status = Some(
|
||||
Status(
|
||||
Code.INTERNAL.value(),
|
||||
details = Seq(
|
||||
Any.pack(errorInfo),
|
||||
Any.pack(requestInfo),
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
None,
|
||||
)
|
||||
),
|
||||
errorFactories,
|
||||
)
|
||||
|
||||
val status = protobuf.StatusProto.fromThrowable(exception)
|
||||
status.getCode shouldBe Code.INTERNAL.value()
|
||||
val details = status.getDetailsList.asScala
|
||||
details.size shouldBe 2
|
||||
details.exists { detail =>
|
||||
detail.is(classOf[JavaErrorInfo]) && detail
|
||||
.unpack(classOf[JavaErrorInfo])
|
||||
.getMetadataOrThrow(GrpcStatuses.DefiniteAnswerKey) == "true"
|
||||
} shouldEqual true
|
||||
details.exists { detail =>
|
||||
detail.is(classOf[JavaRequestInfo]) && detail
|
||||
.unpack(classOf[JavaRequestInfo])
|
||||
.getRequestId == "aRequestId"
|
||||
} shouldEqual true
|
||||
}
|
||||
testIt(useSelfServiceErrorCodes = true)
|
||||
testIt(useSelfServiceErrorCodes = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ package com.daml.ledger.api.validation
|
||||
import java.time.{Duration, Instant}
|
||||
|
||||
import com.daml.api.util.{DurationConversion, TimestampConversion}
|
||||
import com.daml.error.{ContextualizedErrorLogger, ErrorCodesVersionSwitcher}
|
||||
import com.daml.error.ContextualizedErrorLogger
|
||||
import com.daml.ledger.api.domain.{LedgerId, optionalLedgerId}
|
||||
import com.daml.ledger.api.v1.commands
|
||||
import com.daml.ledger.api.v1.commands.Command.Command.{
|
||||
@ -28,19 +28,16 @@ import com.daml.platform.server.api.validation.{
|
||||
ErrorFactories,
|
||||
FieldValidations,
|
||||
}
|
||||
import io.grpc.{Status, StatusRuntimeException}
|
||||
import io.grpc.StatusRuntimeException
|
||||
import scalaz.syntax.tag._
|
||||
|
||||
import scala.Ordering.Implicits.infixOrderingOps
|
||||
import scala.collection.immutable
|
||||
import scala.annotation.nowarn
|
||||
|
||||
final class CommandsValidator(
|
||||
ledgerId: LedgerId,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
) {
|
||||
final class CommandsValidator(ledgerId: LedgerId) {
|
||||
|
||||
private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
private val errorFactories = ErrorFactories()
|
||||
private val fieldValidations = FieldValidations(errorFactories)
|
||||
private val valueValidator = new ValueValidator(errorFactories, fieldValidations)
|
||||
private val deduplicationValidator = new DeduplicationPeriodValidator(errorFactories)
|
||||
@ -73,7 +70,7 @@ final class CommandsValidator(
|
||||
.fromInstant(ledgerEffectiveTime)
|
||||
.left
|
||||
.map(_ =>
|
||||
invalidArgument(definiteAnswer = Some(false))(
|
||||
invalidArgument(
|
||||
s"Can not represent command ledger time $ledgerEffectiveTime as a Daml timestamp"
|
||||
)
|
||||
)
|
||||
@ -114,7 +111,7 @@ final class CommandsValidator(
|
||||
case (None, Some(minRel)) => Right(currentTime.plus(DurationConversion.fromProto(minRel)))
|
||||
case (Some(_), Some(_)) =>
|
||||
Left(
|
||||
invalidArgument(definiteAnswer = Some(false))(
|
||||
invalidArgument(
|
||||
"min_ledger_time_abs cannot be specified at the same time as min_ledger_time_rel"
|
||||
)
|
||||
)
|
||||
@ -201,7 +198,7 @@ final class CommandsValidator(
|
||||
choiceArgument = validatedChoiceArgument,
|
||||
)
|
||||
case ProtoEmpty =>
|
||||
Left(missingField("command", definiteAnswer = Some(false)))
|
||||
Left(missingField("command"))
|
||||
}
|
||||
|
||||
private def validateSubmitters(
|
||||
@ -213,7 +210,7 @@ final class CommandsValidator(
|
||||
Either.cond(
|
||||
effectiveActAs.nonEmpty,
|
||||
(),
|
||||
missingField("party or act_as", definiteAnswer = Some(false)),
|
||||
missingField("party or act_as"),
|
||||
)
|
||||
|
||||
val submitters = effectiveSubmitters(commands)
|
||||
@ -238,7 +235,7 @@ final class CommandsValidator(
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): Either[StatusRuntimeException, DeduplicationPeriod] =
|
||||
optMaxDeduplicationDuration.fold[Either[StatusRuntimeException, DeduplicationPeriod]](
|
||||
Left(missingLedgerConfig(Status.Code.UNAVAILABLE)(definiteAnswer = Some(false)))
|
||||
Left(missingLedgerConfig())
|
||||
) { maxDeduplicationDuration =>
|
||||
deduplicationPeriod match {
|
||||
case commands.Commands.DeduplicationPeriod.Empty =>
|
||||
@ -259,7 +256,7 @@ final class CommandsValidator(
|
||||
.fold(
|
||||
_ =>
|
||||
Left(
|
||||
nonHexOffset(None)(
|
||||
nonHexOffset(
|
||||
fieldName = "deduplication_period",
|
||||
offsetValue = offset,
|
||||
message =
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
package com.daml.ledger.api.validation
|
||||
|
||||
import com.daml.error.{ContextualizedErrorLogger, ErrorCodesVersionSwitcher}
|
||||
import com.daml.error.ContextualizedErrorLogger
|
||||
import com.daml.ledger.api.domain.{LedgerId, LedgerOffset, optionalLedgerId}
|
||||
import com.daml.ledger.api.messages.command.completion
|
||||
import com.daml.ledger.api.messages.command.completion.CompletionStreamRequest
|
||||
@ -17,10 +17,9 @@ import io.grpc.StatusRuntimeException
|
||||
class CompletionServiceRequestValidator(
|
||||
ledgerId: LedgerId,
|
||||
partyNameChecker: PartyNameChecker,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
) {
|
||||
|
||||
private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
private val errorFactories = ErrorFactories()
|
||||
private val fieldValidations = FieldValidations(errorFactories)
|
||||
private val partyValidator =
|
||||
new PartyValidator(partyNameChecker, errorFactories, fieldValidations)
|
||||
|
@ -45,7 +45,7 @@ class LedgerOffsetValidator(errorFactories: ErrorFactories) {
|
||||
case LedgerOffset.Value.Boundary(value) =>
|
||||
convertLedgerBoundary(fieldName, value)
|
||||
case LedgerOffset.Value.Empty =>
|
||||
Left(missingField(fieldName + ".(" + boundary + "|value)", None))
|
||||
Left(missingField(fieldName + ".(" + boundary + "|value)"))
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@ class LedgerOffsetValidator(errorFactories: ErrorFactories) {
|
||||
value match {
|
||||
case LedgerBoundary.Unrecognized(invalid) =>
|
||||
Left(
|
||||
invalidArgument(None)(
|
||||
invalidArgument(
|
||||
s"Unknown ledger $boundary value '$invalid' in field $fieldName.$boundary"
|
||||
)
|
||||
)
|
||||
|
@ -31,7 +31,7 @@ class PartyValidator(
|
||||
)(implicit contextualizedErrorLogger: ContextualizedErrorLogger): Result[Set[Party]] = {
|
||||
val unknownParties = partiesInRequest.filterNot(partyNameChecker.isKnownParty)
|
||||
if (unknownParties.nonEmpty)
|
||||
Left(invalidArgument(None)(s"Unknown parties: ${unknownParties.mkString("[", ", ", "]")}"))
|
||||
Left(invalidArgument(s"Unknown parties: ${unknownParties.mkString("[", ", ", "]")}"))
|
||||
else Right(partiesInRequest)
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ class TransactionFilterValidator(errorFactories: ErrorFactories) {
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): Either[StatusRuntimeException, domain.TransactionFilter] = {
|
||||
if (txFilter.filtersByParty.isEmpty) {
|
||||
Left(invalidArgument(None)("filtersByParty cannot be empty"))
|
||||
Left(invalidArgument("filtersByParty cannot be empty"))
|
||||
} else {
|
||||
val convertedFilters =
|
||||
txFilter.filtersByParty.toList.traverse { case (k, v) =>
|
||||
|
@ -188,7 +188,7 @@ class TransactionServiceRequestValidator(
|
||||
)(implicit contextualizedErrorLogger: ContextualizedErrorLogger) =
|
||||
transactionFilter.filtersByParty
|
||||
.collectFirst { case (party, Filters(Some(inclusive))) =>
|
||||
invalidArgument(None)(
|
||||
invalidArgument(
|
||||
s"$party attempted subscription for templates ${inclusive.templateIds.mkString("[", ", ", "]")}. Template filtration is not supported on GetTransactionTrees RPC. To get filtered data, use the GetTransactions RPC."
|
||||
)
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
package com.daml.ledger.api.validation
|
||||
|
||||
import com.daml.error.{ContextualizedErrorLogger, ErrorCodesVersionSwitcher, NoLogging}
|
||||
import com.daml.error.{ContextualizedErrorLogger, NoLogging}
|
||||
import com.daml.ledger.api.domain
|
||||
import com.daml.ledger.api.v1.value.Value.Sum
|
||||
import com.daml.ledger.api.v1.{value => api}
|
||||
@ -54,10 +54,10 @@ class ValueValidator(errorFactories: ErrorFactories, fieldValidations: FieldVali
|
||||
case Sum.ContractId(cId) =>
|
||||
ContractId
|
||||
.fromString(cId)
|
||||
.bimap(invalidArgument(definiteAnswer = Some(false)), Lf.ValueContractId(_))
|
||||
.bimap(invalidArgument, Lf.ValueContractId(_))
|
||||
case Sum.Numeric(value) =>
|
||||
def err =
|
||||
invalidArgument(definiteAnswer = Some(false))(s"""Could not read Numeric string "$value"""")
|
||||
invalidArgument(s"""Could not read Numeric string "$value"""")
|
||||
if (validNumericString.matcher(value).matches())
|
||||
Numeric
|
||||
.fromUnscaledBigDecimal(new java.math.BigDecimal(value))
|
||||
@ -69,20 +69,20 @@ class ValueValidator(errorFactories: ErrorFactories, fieldValidations: FieldVali
|
||||
Ref.Party
|
||||
.fromString(party)
|
||||
.left
|
||||
.map(invalidArgument(definiteAnswer = Some(false)))
|
||||
.map(invalidArgument)
|
||||
.map(Lf.ValueParty)
|
||||
case Sum.Bool(b) => Right(Lf.ValueBool(b))
|
||||
case Sum.Timestamp(micros) =>
|
||||
Time.Timestamp
|
||||
.fromLong(micros)
|
||||
.left
|
||||
.map(invalidArgument(definiteAnswer = Some(false)))
|
||||
.map(invalidArgument)
|
||||
.map(Lf.ValueTimestamp)
|
||||
case Sum.Date(days) =>
|
||||
Time.Date
|
||||
.fromDaysSinceEpoch(days)
|
||||
.left
|
||||
.map(invalidArgument(definiteAnswer = Some(false)))
|
||||
.map(invalidArgument)
|
||||
.map(Lf.ValueDate)
|
||||
case Sum.Text(text) => Right(Lf.ValueText(text))
|
||||
case Sum.Int64(value) => Right(Lf.ValueInt64(value))
|
||||
@ -131,7 +131,7 @@ class ValueValidator(errorFactories: ErrorFactories, fieldValidations: FieldVali
|
||||
map <- SortedLookupList
|
||||
.fromImmArray(entries.toImmArray)
|
||||
.left
|
||||
.map(invalidArgument(definiteAnswer = Some(false)))
|
||||
.map(invalidArgument)
|
||||
} yield Lf.ValueTextMap(map)
|
||||
|
||||
case Sum.GenMap(genMap0) =>
|
||||
@ -149,7 +149,7 @@ class ValueValidator(errorFactories: ErrorFactories, fieldValidations: FieldVali
|
||||
}
|
||||
genMap.map(entries => Lf.ValueGenMap(entries.toImmArray))
|
||||
|
||||
case Sum.Empty => Left(missingField("value", definiteAnswer = Some(false)))
|
||||
case Sum.Empty => Left(missingField("value"))
|
||||
}
|
||||
|
||||
private[validation] def validateOptionalIdentifier(
|
||||
@ -161,13 +161,9 @@ class ValueValidator(errorFactories: ErrorFactories, fieldValidations: FieldVali
|
||||
|
||||
}
|
||||
|
||||
/** Implementation of self-service error codes brings logging-on-creation to
|
||||
* error factories and validators, used in the Ledger API where automatic logging is desired.
|
||||
* For places where the ValueValidator is needed without logging(e.g. daml-script, navigator), use this implementation instead.
|
||||
*/
|
||||
object NoLoggingValueValidator {
|
||||
// TODO error codes: re-check if using legacy error codes here is ok
|
||||
private val errorFactories = ErrorFactories(new ErrorCodesVersionSwitcher(false))
|
||||
private val errorFactories = ErrorFactories()
|
||||
private val valueValidator = new ValueValidator(
|
||||
errorFactories = errorFactories,
|
||||
fieldValidations = FieldValidations(errorFactories),
|
||||
|
@ -5,7 +5,7 @@ package com.daml.platform.server.api.services.grpc
|
||||
|
||||
import java.time.{Duration, Instant}
|
||||
|
||||
import com.daml.error.{DamlContextualizedErrorLogger, ErrorCodesVersionSwitcher}
|
||||
import com.daml.error.DamlContextualizedErrorLogger
|
||||
import com.daml.ledger.api.SubmissionIdGenerator
|
||||
import com.daml.ledger.api.domain.LedgerId
|
||||
import com.daml.ledger.api.v1.command_service.CommandServiceGrpc.CommandService
|
||||
@ -23,7 +23,6 @@ import scala.concurrent.{ExecutionContext, Future}
|
||||
class GrpcCommandService(
|
||||
protected val service: CommandService with AutoCloseable,
|
||||
val ledgerId: LedgerId,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
currentLedgerTime: () => Instant,
|
||||
currentUtcTime: () => Instant,
|
||||
maxDeduplicationTime: () => Option[Duration],
|
||||
@ -36,8 +35,8 @@ class GrpcCommandService(
|
||||
protected implicit val logger: ContextualizedLogger = ContextualizedLogger.get(getClass)
|
||||
|
||||
private[this] val validator = new SubmitAndWaitRequestValidator(
|
||||
new CommandsValidator(ledgerId, errorCodesVersionSwitcher),
|
||||
FieldValidations(ErrorFactories(errorCodesVersionSwitcher)),
|
||||
new CommandsValidator(ledgerId),
|
||||
FieldValidations(ErrorFactories()),
|
||||
)
|
||||
|
||||
override def submitAndWait(request: SubmitAndWaitRequest): Future[Empty] =
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
package com.daml.platform.server.api.services.grpc
|
||||
|
||||
import com.daml.error.{DamlContextualizedErrorLogger, ErrorCodesVersionSwitcher}
|
||||
import com.daml.error.DamlContextualizedErrorLogger
|
||||
import com.daml.ledger.api.SubmissionIdGenerator
|
||||
import com.daml.ledger.api.domain.LedgerId
|
||||
import com.daml.ledger.api.v1.command_submission_service.CommandSubmissionServiceGrpc.{
|
||||
@ -35,7 +35,6 @@ class GrpcCommandSubmissionService(
|
||||
maxDeduplicationTime: () => Option[Duration],
|
||||
submissionIdGenerator: SubmissionIdGenerator,
|
||||
metrics: Metrics,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
)(implicit executionContext: ExecutionContext, loggingContext: LoggingContext)
|
||||
extends ApiCommandSubmissionService
|
||||
with ProxyCloseable
|
||||
@ -43,8 +42,8 @@ class GrpcCommandSubmissionService(
|
||||
|
||||
protected implicit val logger: ContextualizedLogger = ContextualizedLogger.get(getClass)
|
||||
private val validator = new SubmitRequestValidator(
|
||||
new CommandsValidator(ledgerId, errorCodesVersionSwitcher),
|
||||
FieldValidations(ErrorFactories(errorCodesVersionSwitcher)),
|
||||
new CommandsValidator(ledgerId),
|
||||
FieldValidations(ErrorFactories()),
|
||||
)
|
||||
|
||||
override def submit(request: ApiSubmitRequest): Future[Empty] = {
|
||||
|
@ -6,11 +6,7 @@ package com.daml.platform.server.api.services.grpc
|
||||
import akka.NotUsed
|
||||
import akka.stream.Materializer
|
||||
import akka.stream.scaladsl.Source
|
||||
import com.daml.error.{
|
||||
ContextualizedErrorLogger,
|
||||
DamlContextualizedErrorLogger,
|
||||
ErrorCodesVersionSwitcher,
|
||||
}
|
||||
import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger}
|
||||
import com.daml.grpc.adapter.ExecutionSequencerFactory
|
||||
import com.daml.ledger.api.health.HealthChecks
|
||||
import com.daml.logging.{ContextualizedLogger, LoggingContext}
|
||||
@ -32,7 +28,6 @@ import scala.util.{Failure, Success, Try}
|
||||
|
||||
class GrpcHealthService(
|
||||
healthChecks: HealthChecks,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
maximumWatchFrequency: FiniteDuration = 1.second,
|
||||
)(implicit
|
||||
protected val esf: ExecutionSequencerFactory,
|
||||
@ -45,7 +40,7 @@ class GrpcHealthService(
|
||||
private val logger = ContextualizedLogger.get(getClass)
|
||||
private val errorLogger: ContextualizedErrorLogger =
|
||||
new DamlContextualizedErrorLogger(logger, loggingContext, None)
|
||||
private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
private val errorFactories = ErrorFactories()
|
||||
|
||||
import errorFactories.invalidArgumentWasNotFound
|
||||
|
||||
@ -66,9 +61,7 @@ class GrpcHealthService(
|
||||
.collect {
|
||||
case component if !healthChecks.hasComponent(component) =>
|
||||
Failure(
|
||||
invalidArgumentWasNotFound(None)(
|
||||
s"Component $component does not exist."
|
||||
)(errorLogger)
|
||||
invalidArgumentWasNotFound(s"Component $component does not exist.")(errorLogger)
|
||||
)
|
||||
}
|
||||
.getOrElse {
|
||||
|
@ -34,9 +34,7 @@ class DeduplicationPeriodValidator(
|
||||
if (duration.compareTo(maxDeduplicationDuration) > 0)
|
||||
Left(
|
||||
errorFactories.invalidDeduplicationPeriod(
|
||||
fieldName,
|
||||
s"The given deduplication duration of $duration exceeds the maximum deduplication time of $maxDeduplicationDuration",
|
||||
definiteAnswer = Some(false),
|
||||
Some(maxDeduplicationDuration),
|
||||
)
|
||||
)
|
||||
@ -51,7 +49,6 @@ class DeduplicationPeriodValidator(
|
||||
.invalidField(
|
||||
fieldName,
|
||||
"Duration must be positive",
|
||||
definiteAnswer = Some(false),
|
||||
)
|
||||
)
|
||||
else Right(duration)
|
||||
|
@ -6,9 +6,8 @@ package com.daml.platform.server.api.validation
|
||||
import java.sql.{SQLNonTransientException, SQLTransientException}
|
||||
import java.time.{Duration, Instant}
|
||||
|
||||
import com.daml.error.ErrorCode.ApiException
|
||||
import com.daml.error.definitions.{IndexErrors, LedgerApiErrors}
|
||||
import com.daml.error.{ContextualizedErrorLogger, ErrorCodesVersionSwitcher}
|
||||
import com.daml.error.ContextualizedErrorLogger
|
||||
import com.daml.grpc.GrpcStatus
|
||||
import com.daml.ledger.api.domain.LedgerId
|
||||
import com.daml.ledger.grpc.GrpcStatuses
|
||||
@ -16,108 +15,61 @@ import com.daml.ledger.offset.Offset
|
||||
import com.daml.lf.data.Ref.TransactionId
|
||||
import com.daml.lf.transaction.GlobalKey
|
||||
import com.daml.lf.value.Value
|
||||
import com.daml.platform.server.api.validation.ErrorFactories.{
|
||||
addDefiniteAnswerDetails,
|
||||
definiteAnswers,
|
||||
}
|
||||
import com.daml.platform.server.api.validation.ErrorFactories.addDefiniteAnswerDetails
|
||||
import com.daml.platform.server.api.{ApiException => NoStackTraceApiException}
|
||||
import com.google.protobuf.{Any => AnyProto}
|
||||
import com.google.rpc.status.{Status => RpcStatus}
|
||||
import com.google.rpc.{ErrorInfo, Status}
|
||||
import io.grpc.Status.Code
|
||||
import io.grpc.protobuf.StatusProto
|
||||
import io.grpc.{Metadata, StatusRuntimeException}
|
||||
import io.grpc.StatusRuntimeException
|
||||
import scalaz.syntax.tag._
|
||||
|
||||
import scala.annotation.nowarn
|
||||
|
||||
class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitcher) {
|
||||
class ErrorFactories private () {
|
||||
object SubmissionQueueErrors {
|
||||
def failedToEnqueueCommandSubmission(message: String)(t: Throwable)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): Status =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = {
|
||||
val status = io.grpc.Status.ABORTED
|
||||
.withDescription(s"$message: ${t.getClass.getSimpleName}: ${t.getMessage}")
|
||||
.withCause(t)
|
||||
val statusBuilder = GrpcStatus.toJavaBuilder(status)
|
||||
GrpcStatus.buildStatus(Map.empty, statusBuilder)
|
||||
},
|
||||
v2 = LedgerApiErrors.InternalError
|
||||
.Generic(
|
||||
message = s"$message: ${t.getClass.getSimpleName}: ${t.getMessage}",
|
||||
throwableO = Some(t),
|
||||
)
|
||||
.asGrpcStatusFromContext,
|
||||
)
|
||||
LedgerApiErrors.InternalError
|
||||
.Generic(
|
||||
message = s"$message: ${t.getClass.getSimpleName}: ${t.getMessage}",
|
||||
throwableO = Some(t),
|
||||
)
|
||||
.asGrpcStatusFromContext
|
||||
|
||||
def queueClosed(queueName: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): Status =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = {
|
||||
val status = io.grpc.Status.ABORTED.withDescription("Queue closed")
|
||||
val statusBuilder = GrpcStatus.toJavaBuilder(status)
|
||||
GrpcStatus.buildStatus(Map.empty, statusBuilder)
|
||||
},
|
||||
v2 = LedgerApiErrors.ServiceNotRunning
|
||||
.Reject(queueName)
|
||||
.asGrpcStatusFromContext,
|
||||
)
|
||||
LedgerApiErrors.ServiceNotRunning
|
||||
.Reject(queueName)
|
||||
.asGrpcStatusFromContext
|
||||
|
||||
def timedOutOnAwaitingForCommandCompletion()(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): Status =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = {
|
||||
val statusBuilder =
|
||||
GrpcStatus.toJavaBuilder(Code.ABORTED.value(), Some("Timeout"), Iterable.empty)
|
||||
GrpcStatus.buildStatus(Map.empty, statusBuilder)
|
||||
},
|
||||
v2 = LedgerApiErrors.RequestTimeOut
|
||||
.Reject(
|
||||
"Timed out while awaiting for a completion corresponding to a command submission.",
|
||||
_definiteAnswer = false,
|
||||
)
|
||||
.asGrpcStatusFromContext,
|
||||
)
|
||||
LedgerApiErrors.RequestTimeOut
|
||||
.Reject(
|
||||
"Timed out while awaiting for a completion corresponding to a command submission.",
|
||||
_definiteAnswer = false,
|
||||
)
|
||||
.asGrpcStatusFromContext
|
||||
|
||||
def noStatusInCompletionResponse()(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): Status = {
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = {
|
||||
Status
|
||||
.newBuilder()
|
||||
.setCode(Code.INTERNAL.value())
|
||||
.setMessage("Missing status in completion response.")
|
||||
.build()
|
||||
},
|
||||
v2 = LedgerApiErrors.InternalError
|
||||
.Generic(
|
||||
"Missing status in completion response.",
|
||||
throwableO = None,
|
||||
)
|
||||
.asGrpcStatusFromContext,
|
||||
)
|
||||
}
|
||||
): Status =
|
||||
LedgerApiErrors.InternalError
|
||||
.Generic(
|
||||
"Missing status in completion response.",
|
||||
throwableO = None,
|
||||
)
|
||||
.asGrpcStatusFromContext
|
||||
}
|
||||
|
||||
def bufferFull(message: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): Status =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = {
|
||||
val status = io.grpc.Status.RESOURCE_EXHAUSTED
|
||||
.withDescription("Ingress buffer is full")
|
||||
val statusBuilder = GrpcStatus.toJavaBuilder(status)
|
||||
GrpcStatus.buildStatus(Map.empty, statusBuilder)
|
||||
},
|
||||
v2 = LedgerApiErrors.ParticipantBackpressure
|
||||
.Rejection(message)
|
||||
.asGrpcStatusFromContext,
|
||||
)
|
||||
LedgerApiErrors.ParticipantBackpressure
|
||||
.Rejection(message)
|
||||
.asGrpcStatusFromContext
|
||||
|
||||
def sqlTransientException(exception: SQLTransientException)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
@ -132,219 +84,114 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch
|
||||
def transactionNotFound(transactionId: TransactionId)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = grpcError(
|
||||
Status
|
||||
.newBuilder()
|
||||
.setCode(Code.NOT_FOUND.value())
|
||||
.setMessage("Transaction not found, or not visible.")
|
||||
.build()
|
||||
),
|
||||
v2 = LedgerApiErrors.RequestValidation.NotFound.Transaction
|
||||
.Reject(transactionId)
|
||||
.asGrpcError,
|
||||
)
|
||||
LedgerApiErrors.RequestValidation.NotFound.Transaction
|
||||
.Reject(transactionId)
|
||||
.asGrpcError
|
||||
|
||||
def packageNotFound(packageId: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = io.grpc.Status.NOT_FOUND.asRuntimeException(),
|
||||
v2 = LedgerApiErrors.RequestValidation.NotFound.Package
|
||||
.Reject(_packageId = packageId)
|
||||
.asGrpcError,
|
||||
)
|
||||
LedgerApiErrors.RequestValidation.NotFound.Package
|
||||
.Reject(_packageId = packageId)
|
||||
.asGrpcError
|
||||
|
||||
def versionServiceInternalError(message: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = grpcError(
|
||||
Status
|
||||
.newBuilder()
|
||||
.setCode(Code.INTERNAL.value())
|
||||
.setMessage(message)
|
||||
.build()
|
||||
),
|
||||
v2 = LedgerApiErrors.InternalError.VersionService(message).asGrpcError,
|
||||
)
|
||||
LedgerApiErrors.InternalError.VersionService(message).asGrpcError
|
||||
|
||||
def duplicateCommandException(existingSubmissionId: Option[String])(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = {
|
||||
val exception = grpcError(
|
||||
Status
|
||||
.newBuilder()
|
||||
.setCode(Code.ALREADY_EXISTS.value())
|
||||
.setMessage("Duplicate command")
|
||||
.addDetails(definiteAnswers(false))
|
||||
.build()
|
||||
)
|
||||
contextualizedErrorLogger.info(exception.getMessage)
|
||||
exception
|
||||
},
|
||||
v2 = LedgerApiErrors.ConsistencyErrors.DuplicateCommand
|
||||
.Reject(_existingCommandSubmissionId = existingSubmissionId)
|
||||
.asGrpcError,
|
||||
)
|
||||
LedgerApiErrors.ConsistencyErrors.DuplicateCommand
|
||||
.Reject(_existingCommandSubmissionId = existingSubmissionId)
|
||||
.asGrpcError
|
||||
|
||||
/** @param expected Expected ledger id.
|
||||
* @param received Received ledger id.
|
||||
* @param definiteAnswer A flag that says whether it is a definite answer. Provided only in the context of command deduplication.
|
||||
* @return An exception with the [[Code.NOT_FOUND]] status code.
|
||||
*/
|
||||
def ledgerIdMismatch(
|
||||
expected: LedgerId,
|
||||
received: LedgerId,
|
||||
definiteAnswer: Option[Boolean],
|
||||
)(implicit contextualizedErrorLogger: ContextualizedErrorLogger): StatusRuntimeException = {
|
||||
require(!definiteAnswer.contains(true), "Wrong ledger ID can never be a definite answer.")
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = {
|
||||
val statusBuilder = Status
|
||||
.newBuilder()
|
||||
.setCode(Code.NOT_FOUND.value())
|
||||
.setMessage(
|
||||
s"Ledger ID '${received.unwrap}' not found. Actual Ledger ID is '${expected.unwrap}'."
|
||||
)
|
||||
addDefiniteAnswerDetails(definiteAnswer, statusBuilder)
|
||||
grpcError(statusBuilder.build())
|
||||
},
|
||||
v2 = LedgerApiErrors.RequestValidation.LedgerIdMismatch
|
||||
.Reject(
|
||||
s"Ledger ID '${received.unwrap}' not found. Actual Ledger ID is '${expected.unwrap}'."
|
||||
)
|
||||
.asGrpcError,
|
||||
)
|
||||
LedgerApiErrors.RequestValidation.LedgerIdMismatch
|
||||
.Reject(
|
||||
s"Ledger ID '${received.unwrap}' not found. Actual Ledger ID is '${expected.unwrap}'."
|
||||
)
|
||||
.asGrpcError
|
||||
}
|
||||
|
||||
/** @param fieldName A missing field's name.
|
||||
* @param definiteAnswer A flag that says whether it is a definite answer. Provided only in the context of command deduplication.
|
||||
* @return An exception with the [[Code.INVALID_ARGUMENT]] status code.
|
||||
*/
|
||||
def missingField(fieldName: String, definiteAnswer: Option[Boolean])(implicit
|
||||
def missingField(fieldName: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = {
|
||||
val statusBuilder = Status
|
||||
.newBuilder()
|
||||
.setCode(Code.INVALID_ARGUMENT.value())
|
||||
.setMessage(s"Missing field: $fieldName")
|
||||
addDefiniteAnswerDetails(definiteAnswer, statusBuilder)
|
||||
grpcError(statusBuilder.build())
|
||||
},
|
||||
v2 = LedgerApiErrors.RequestValidation.MissingField
|
||||
.Reject(fieldName)
|
||||
.asGrpcError,
|
||||
)
|
||||
LedgerApiErrors.RequestValidation.MissingField
|
||||
.Reject(fieldName)
|
||||
.asGrpcError
|
||||
|
||||
/** @param definiteAnswer A flag that says whether it is a definite answer. Provided only in the context of command deduplication.
|
||||
* @param message A status' message.
|
||||
/** @param message A status' message.
|
||||
* @return An exception with the [[Code.INVALID_ARGUMENT]] status code.
|
||||
*/
|
||||
def invalidArgument(definiteAnswer: Option[Boolean])(message: String)(implicit
|
||||
def invalidArgument(message: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = invalidArgumentV1(definiteAnswer, message),
|
||||
v2 = LedgerApiErrors.RequestValidation.InvalidArgument
|
||||
.Reject(message)
|
||||
.asGrpcError,
|
||||
)
|
||||
LedgerApiErrors.RequestValidation.InvalidArgument
|
||||
.Reject(message)
|
||||
.asGrpcError
|
||||
|
||||
// This error builder covers cases where existing logic handling invalid arguments returned NOT_FOUND.
|
||||
def invalidArgumentWasNotFound(definiteAnswer: Option[Boolean])(message: String)(implicit
|
||||
def invalidArgumentWasNotFound(message: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = {
|
||||
val statusBuilder = Status
|
||||
.newBuilder()
|
||||
.setCode(Code.NOT_FOUND.value())
|
||||
.setMessage(message)
|
||||
addDefiniteAnswerDetails(definiteAnswer, statusBuilder)
|
||||
grpcError(statusBuilder.build())
|
||||
},
|
||||
v2 = LedgerApiErrors.RequestValidation.InvalidArgument
|
||||
.Reject(message)
|
||||
.asGrpcError,
|
||||
)
|
||||
LedgerApiErrors.RequestValidation.InvalidArgument
|
||||
.Reject(message)
|
||||
.asGrpcError
|
||||
|
||||
def offsetOutOfRange(
|
||||
definiteAnswer: Option[Boolean]
|
||||
)(message: String)(implicit
|
||||
def offsetOutOfRange(message: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = invalidArgumentV1(definiteAnswer, message),
|
||||
v2 = LedgerApiErrors.RequestValidation.OffsetOutOfRange
|
||||
.Reject(message)
|
||||
.asGrpcError,
|
||||
)
|
||||
LedgerApiErrors.RequestValidation.OffsetOutOfRange
|
||||
.Reject(message)
|
||||
.asGrpcError
|
||||
|
||||
def offsetAfterLedgerEnd(offsetType: String, requestedOffset: String, ledgerEnd: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException = {
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = grpcError(
|
||||
Status
|
||||
.newBuilder()
|
||||
.setCode(Code.OUT_OF_RANGE.value())
|
||||
.setMessage(s"$offsetType offset ($requestedOffset) is after ledger end ($ledgerEnd)")
|
||||
.build()
|
||||
),
|
||||
v2 = LedgerApiErrors.RequestValidation.OffsetAfterLedgerEnd
|
||||
.Reject(offsetType, requestedOffset, ledgerEnd)
|
||||
.asGrpcError,
|
||||
)
|
||||
}
|
||||
): StatusRuntimeException =
|
||||
LedgerApiErrors.RequestValidation.OffsetAfterLedgerEnd
|
||||
.Reject(offsetType, requestedOffset, ledgerEnd)
|
||||
.asGrpcError
|
||||
|
||||
def nonHexOffset(
|
||||
definiteAnswer: Option[Boolean]
|
||||
)(fieldName: String, offsetValue: String, message: String)(implicit
|
||||
def nonHexOffset(fieldName: String, offsetValue: String, message: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = invalidArgumentV1(definiteAnswer, message),
|
||||
v2 = LedgerApiErrors.RequestValidation.NonHexOffset
|
||||
.Error(
|
||||
_fieldName = fieldName,
|
||||
_offsetValue = offsetValue,
|
||||
_message = message,
|
||||
)
|
||||
.asGrpcError,
|
||||
)
|
||||
LedgerApiErrors.RequestValidation.NonHexOffset
|
||||
.Error(
|
||||
_fieldName = fieldName,
|
||||
_offsetValue = offsetValue,
|
||||
_message = message,
|
||||
)
|
||||
.asGrpcError
|
||||
|
||||
def invalidDeduplicationPeriod(
|
||||
fieldName: String,
|
||||
message: String,
|
||||
definiteAnswer: Option[Boolean],
|
||||
maxDeduplicationDuration: Option[Duration],
|
||||
)(implicit contextualizedErrorLogger: ContextualizedErrorLogger): StatusRuntimeException =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
legacyInvalidField(fieldName, message, definiteAnswer),
|
||||
LedgerApiErrors.RequestValidation.InvalidDeduplicationPeriodField
|
||||
.Reject(message, maxDeduplicationDuration)
|
||||
.asGrpcError,
|
||||
)
|
||||
LedgerApiErrors.RequestValidation.InvalidDeduplicationPeriodField
|
||||
.Reject(message, maxDeduplicationDuration)
|
||||
.asGrpcError
|
||||
|
||||
/** @param fieldName An invalid field's name.
|
||||
* @param message A status' message.
|
||||
* @param definiteAnswer A flag that says whether it is a definite answer. Provided only in the context of command deduplication.
|
||||
* @return An exception with the [[Code.INVALID_ARGUMENT]] status code.
|
||||
*/
|
||||
def invalidField(
|
||||
fieldName: String,
|
||||
message: String,
|
||||
definiteAnswer: Option[Boolean],
|
||||
)(implicit contextualizedErrorLogger: ContextualizedErrorLogger): StatusRuntimeException =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = legacyInvalidField(fieldName, message, definiteAnswer),
|
||||
v2 = ledgerRequestValidationInvalidField(fieldName, message).asGrpcError,
|
||||
)
|
||||
ledgerRequestValidationInvalidField(fieldName, message).asGrpcError
|
||||
|
||||
private def ledgerRequestValidationInvalidField(fieldName: String, message: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
@ -353,19 +200,6 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch
|
||||
.Reject(s"Invalid field $fieldName: $message")
|
||||
}
|
||||
|
||||
private def legacyInvalidField(
|
||||
fieldName: String,
|
||||
message: String,
|
||||
definiteAnswer: Option[Boolean],
|
||||
): StatusRuntimeException = {
|
||||
val statusBuilder = Status
|
||||
.newBuilder()
|
||||
.setCode(Code.INVALID_ARGUMENT.value())
|
||||
.setMessage(s"Invalid field $fieldName: $message")
|
||||
addDefiniteAnswerDetails(definiteAnswer, statusBuilder)
|
||||
grpcError(statusBuilder.build())
|
||||
}
|
||||
|
||||
/** @param message A status' message.
|
||||
* @param definiteAnswer A flag that says whether it is a definite answer. Provided only in the context of command deduplication.
|
||||
* @return An exception with the [[Code.ABORTED]] status code.
|
||||
@ -380,177 +214,77 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch
|
||||
grpcError(statusBuilder.build())
|
||||
}
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def isTimeoutUnknown_wasAborted(message: String, definiteAnswer: Option[Boolean])(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException = {
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = aborted(message, definiteAnswer),
|
||||
v2 = LedgerApiErrors.RequestTimeOut
|
||||
.Reject(message, definiteAnswer.getOrElse(false))
|
||||
.asGrpcError,
|
||||
)
|
||||
}
|
||||
): StatusRuntimeException =
|
||||
LedgerApiErrors.RequestTimeOut
|
||||
.Reject(message, definiteAnswer.getOrElse(false))
|
||||
.asGrpcError
|
||||
|
||||
def packageUploadRejected(message: String, definiteAnswer: Option[Boolean])(implicit
|
||||
def packageUploadRejected(message: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException = {
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = invalidArgumentV1(definiteAnswer, message),
|
||||
v2 = LedgerApiErrors.AdminServices.PackageUploadRejected.Reject(message).asGrpcError,
|
||||
)
|
||||
}
|
||||
): StatusRuntimeException =
|
||||
LedgerApiErrors.AdminServices.PackageUploadRejected.Reject(message).asGrpcError
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def configurationEntryRejected(message: String, definiteAnswer: Option[Boolean])(implicit
|
||||
def configurationEntryRejected(message: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException = {
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = aborted(message, definiteAnswer),
|
||||
v2 = LedgerApiErrors.AdminServices.ConfigurationEntryRejected.Reject(message).asGrpcError,
|
||||
)
|
||||
}
|
||||
): StatusRuntimeException =
|
||||
LedgerApiErrors.AdminServices.ConfigurationEntryRejected.Reject(message).asGrpcError
|
||||
|
||||
// permission denied is intentionally without description to ensure we don't leak security relevant information by accident
|
||||
def permissionDenied(cause: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException = errorCodesVersionSwitcher.choose(
|
||||
v1 = {
|
||||
contextualizedErrorLogger.warn(s"Permission denied. Reason: $cause.")
|
||||
new ApiException(
|
||||
io.grpc.Status.PERMISSION_DENIED,
|
||||
new Metadata(),
|
||||
)
|
||||
},
|
||||
v2 = LedgerApiErrors.AuthorizationChecks.PermissionDenied.Reject(cause).asGrpcError,
|
||||
)
|
||||
): StatusRuntimeException =
|
||||
LedgerApiErrors.AuthorizationChecks.PermissionDenied.Reject(cause).asGrpcError
|
||||
|
||||
def unauthenticatedMissingJwtToken()(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException = errorCodesVersionSwitcher.choose(
|
||||
v1 = new ApiException(
|
||||
io.grpc.Status.UNAUTHENTICATED,
|
||||
new Metadata(),
|
||||
),
|
||||
v2 = LedgerApiErrors.AuthorizationChecks.Unauthenticated
|
||||
): StatusRuntimeException =
|
||||
LedgerApiErrors.AuthorizationChecks.Unauthenticated
|
||||
.MissingJwtToken()
|
||||
.asGrpcError,
|
||||
)
|
||||
.asGrpcError
|
||||
|
||||
def internalAuthenticationError(securitySafeMessage: String, exception: Throwable)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = {
|
||||
contextualizedErrorLogger.warn(
|
||||
s"$securitySafeMessage: ${exception.getMessage}"
|
||||
)
|
||||
new ApiException(
|
||||
io.grpc.Status.INTERNAL.withDescription(truncated(securitySafeMessage)),
|
||||
new Metadata(),
|
||||
)
|
||||
},
|
||||
v2 = LedgerApiErrors.AuthorizationChecks.InternalAuthorizationError
|
||||
.Reject(securitySafeMessage, exception)
|
||||
.asGrpcError,
|
||||
)
|
||||
LedgerApiErrors.AuthorizationChecks.InternalAuthorizationError
|
||||
.Reject(securitySafeMessage, exception)
|
||||
.asGrpcError
|
||||
|
||||
/** @param definiteAnswer A flag that says whether it is a definite answer. Provided only in the context of command deduplication.
|
||||
* @return An exception with the [[Code.UNAVAILABLE]] status code.
|
||||
*/
|
||||
def missingLedgerConfig(
|
||||
legacyGrpcStatusCode: io.grpc.Status.Code
|
||||
)(definiteAnswer: Option[Boolean])(implicit
|
||||
def missingLedgerConfig()(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = {
|
||||
val statusBuilder = Status
|
||||
.newBuilder()
|
||||
.setCode(legacyGrpcStatusCode.value())
|
||||
.setMessage("The ledger configuration is not available.")
|
||||
addDefiniteAnswerDetails(definiteAnswer, statusBuilder)
|
||||
grpcError(statusBuilder.build())
|
||||
},
|
||||
v2 = LedgerApiErrors.RequestValidation.NotFound.LedgerConfiguration
|
||||
.Reject()
|
||||
.asGrpcError,
|
||||
)
|
||||
LedgerApiErrors.RequestValidation.NotFound.LedgerConfiguration
|
||||
.Reject()
|
||||
.asGrpcError
|
||||
|
||||
def missingLedgerConfig(
|
||||
v1Status: RpcStatus,
|
||||
message: String,
|
||||
)(implicit
|
||||
def missingLedgerConfig(message: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): com.google.rpc.status.Status =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = v1Status,
|
||||
v2 = GrpcStatus.toProto(
|
||||
LedgerApiErrors.RequestValidation.NotFound.LedgerConfiguration
|
||||
.RejectWithMessage(message)
|
||||
.asGrpcStatusFromContext
|
||||
),
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.RequestValidation.NotFound.LedgerConfiguration
|
||||
.RejectWithMessage(message)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
def participantPrunedDataAccessed(message: String, earliestOffset: Offset)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = grpcError(
|
||||
Status
|
||||
.newBuilder()
|
||||
.setCode(Code.NOT_FOUND.value())
|
||||
.setMessage(message)
|
||||
.build()
|
||||
),
|
||||
v2 = LedgerApiErrors.RequestValidation.ParticipantPrunedDataAccessed
|
||||
.Reject(message, earliestOffset.toHexString)
|
||||
.asGrpcError,
|
||||
)
|
||||
LedgerApiErrors.RequestValidation.ParticipantPrunedDataAccessed
|
||||
.Reject(message, earliestOffset.toHexString)
|
||||
.asGrpcError
|
||||
|
||||
/** @param definiteAnswer A flag that says whether it is a definite answer. Provided only in the context of command deduplication.
|
||||
* @return An exception with the [[Code.UNAVAILABLE]] status code.
|
||||
/** @return An exception with the [[Code.UNAVAILABLE]] status code.
|
||||
*/
|
||||
def serviceNotRunning(serviceName: String)(definiteAnswer: Option[Boolean])(implicit
|
||||
def serviceNotRunning(serviceName: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = {
|
||||
val statusBuilder = Status
|
||||
.newBuilder()
|
||||
.setCode(Code.UNAVAILABLE.value())
|
||||
.setMessage(s"$serviceName has been shut down.")
|
||||
addDefiniteAnswerDetails(definiteAnswer, statusBuilder)
|
||||
grpcError(statusBuilder.build())
|
||||
},
|
||||
v2 = LedgerApiErrors.ServiceNotRunning.Reject(serviceName).asGrpcError,
|
||||
)
|
||||
LedgerApiErrors.ServiceNotRunning.Reject(serviceName).asGrpcError
|
||||
|
||||
def trackerFailure(msg: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): StatusRuntimeException =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = {
|
||||
val builder = Status
|
||||
.newBuilder()
|
||||
.setCode(Code.INTERNAL.value())
|
||||
.setMessage(msg)
|
||||
grpcError(builder.build())
|
||||
},
|
||||
v2 = LedgerApiErrors.InternalError.Generic(msg).asGrpcError,
|
||||
)
|
||||
|
||||
private def invalidArgumentV1(
|
||||
definiteAnswer: Option[Boolean],
|
||||
message: String,
|
||||
): StatusRuntimeException = {
|
||||
val statusBuilder = Status
|
||||
.newBuilder()
|
||||
.setCode(Code.INVALID_ARGUMENT.value())
|
||||
.setMessage(s"Invalid argument: $message")
|
||||
addDefiniteAnswerDetails(definiteAnswer, statusBuilder)
|
||||
grpcError(statusBuilder.build())
|
||||
}
|
||||
LedgerApiErrors.InternalError.Generic(msg).asGrpcError
|
||||
|
||||
/** Transforms Protobuf [[Status]] objects, possibly including metadata packed as [[ErrorInfo]] objects,
|
||||
* into exceptions with metadata in the trailers.
|
||||
@ -583,30 +317,19 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch
|
||||
def partyNotKnownOnLedger(reason: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): com.google.rpc.status.Status =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = RpcStatus
|
||||
.of(Code.INVALID_ARGUMENT.value(), s"Parties not known on ledger: $reason", Seq.empty),
|
||||
v2 = GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.PartyNotKnownOnLedger
|
||||
.RejectDeprecated(reason)
|
||||
.asGrpcStatusFromContext
|
||||
),
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.PartyNotKnownOnLedger
|
||||
.RejectDeprecated(reason)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
def contractsNotFound(missingContractIds: Set[String])(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): com.google.rpc.status.Status =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = RpcStatus.of(
|
||||
Code.ABORTED.value(),
|
||||
s"Inconsistent: Could not lookup contracts: ${missingContractIds.mkString("[", ", ", "]")}",
|
||||
Seq.empty,
|
||||
),
|
||||
v2 = GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.ContractNotFound
|
||||
.MultipleContractsNotFound(missingContractIds)
|
||||
.asGrpcStatusFromContext
|
||||
),
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.ContractNotFound
|
||||
.MultipleContractsNotFound(missingContractIds)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
def inconsistentContractKeys(
|
||||
@ -615,111 +338,73 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch
|
||||
)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): com.google.rpc.status.Status =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = RpcStatus.of(
|
||||
Code.ABORTED.value(),
|
||||
s"Inconsistent: Contract key lookup with different results: expected [$lookupResult], actual [$currentResult]",
|
||||
Seq.empty,
|
||||
),
|
||||
v2 = GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.InconsistentContractKey
|
||||
.Reject(
|
||||
s"Contract key lookup with different results: expected [$lookupResult], actual [$currentResult]"
|
||||
)
|
||||
.asGrpcStatusFromContext
|
||||
),
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.InconsistentContractKey
|
||||
.Reject(
|
||||
s"Contract key lookup with different results: expected [$lookupResult], actual [$currentResult]"
|
||||
)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
def duplicateContractKey(reason: String, key: GlobalKey)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): com.google.rpc.status.Status =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = RpcStatus.of(Code.ABORTED.value(), s"Inconsistent: $reason", Seq.empty),
|
||||
v2 = GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.DuplicateContractKey
|
||||
.RejectWithContractKeyArg(reason, key)
|
||||
.asGrpcStatusFromContext
|
||||
),
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.DuplicateContractKey
|
||||
.RejectWithContractKeyArg(reason, key)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
def partiesNotKnownToLedger(parties: Set[String])(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): com.google.rpc.status.Status =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = RpcStatus
|
||||
.of(
|
||||
Code.INVALID_ARGUMENT.value(),
|
||||
s"Parties not known on ledger: ${parties.mkString("[", ", ", "]")}",
|
||||
Seq.empty,
|
||||
),
|
||||
v2 = GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.PartyNotKnownOnLedger
|
||||
.Reject(parties)
|
||||
.asGrpcStatusFromContext
|
||||
),
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.PartyNotKnownOnLedger
|
||||
.Reject(parties)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
def submitterCannotActViaParticipant(reason: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): com.google.rpc.status.Status =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = RpcStatus.of(
|
||||
Code.PERMISSION_DENIED.value(),
|
||||
s"Submitted cannot act via participant: $reason",
|
||||
Seq.empty,
|
||||
),
|
||||
v2 = GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.SubmitterCannotActViaParticipant
|
||||
.Reject(reason)
|
||||
.asGrpcStatusFromContext
|
||||
),
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.SubmitterCannotActViaParticipant
|
||||
.Reject(reason)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
def invalidLedgerTime(reason: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): com.google.rpc.status.Status =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = RpcStatus.of(Code.ABORTED.value(), s"Invalid ledger time: $reason", Seq.empty),
|
||||
v2 = GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.InvalidLedgerTime
|
||||
.RejectSimple(reason)
|
||||
.asGrpcStatusFromContext
|
||||
),
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.InvalidLedgerTime
|
||||
.RejectSimple(reason)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
def invalidLedgerTime(
|
||||
v1Status: RpcStatus,
|
||||
ledgerTime: Instant,
|
||||
ledgerTimeLowerBound: Instant,
|
||||
ledgerTimeUpperBound: Instant,
|
||||
)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): com.google.rpc.status.Status = {
|
||||
val details =
|
||||
s"Ledger time $ledgerTime outside of range [$ledgerTimeLowerBound, $ledgerTimeUpperBound]"
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = v1Status,
|
||||
v2 = GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.InvalidLedgerTime
|
||||
.RejectEnriched(
|
||||
details,
|
||||
ledgerTime,
|
||||
ledgerTimeLowerBound,
|
||||
ledgerTimeUpperBound,
|
||||
)
|
||||
.asGrpcStatusFromContext
|
||||
),
|
||||
): com.google.rpc.status.Status =
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.InvalidLedgerTime
|
||||
.RejectEnriched(
|
||||
s"Ledger time $ledgerTime outside of range [$ledgerTimeLowerBound, $ledgerTimeUpperBound]",
|
||||
ledgerTime,
|
||||
ledgerTimeLowerBound,
|
||||
ledgerTimeUpperBound,
|
||||
)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
}
|
||||
|
||||
def inconsistent(reason: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): com.google.rpc.status.Status =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = RpcStatus.of(Code.ABORTED.value(), s"Inconsistent: $reason", Seq.empty),
|
||||
v2 = GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.Inconsistent.Reject(reason).asGrpcStatusFromContext
|
||||
),
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.Inconsistent.Reject(reason).asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
object Deprecated {
|
||||
@ -727,39 +412,24 @@ class ErrorFactories private (errorCodesVersionSwitcher: ErrorCodesVersionSwitch
|
||||
def disputed(reason: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): com.google.rpc.status.Status =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = RpcStatus.of(Code.INVALID_ARGUMENT.value(), s"Disputed: $reason", Seq.empty),
|
||||
v2 = GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.Disputed.Reject(reason).asGrpcStatusFromContext
|
||||
),
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.Disputed.Reject(reason).asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
@deprecated
|
||||
def outOfQuota(reason: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): com.google.rpc.status.Status =
|
||||
errorCodesVersionSwitcher.choose(
|
||||
v1 = RpcStatus.of(Code.ABORTED.value(), s"Resources exhausted: $reason", Seq.empty),
|
||||
v2 = GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.OutOfQuota.Reject(reason).asGrpcStatusFromContext
|
||||
),
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.OutOfQuota.Reject(reason).asGrpcStatusFromContext
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ErrorFactories {
|
||||
val SelfServiceErrorCodeFactories: ErrorFactories = ErrorFactories(
|
||||
new ErrorCodesVersionSwitcher(enableSelfServiceErrorCodes = true)
|
||||
)
|
||||
|
||||
def apply(errorCodesVersionSwitcher: ErrorCodesVersionSwitcher): ErrorFactories =
|
||||
new ErrorFactories(errorCodesVersionSwitcher)
|
||||
|
||||
def apply(useSelfServiceErrorCodes: Boolean): ErrorFactories =
|
||||
new ErrorFactories(
|
||||
new ErrorCodesVersionSwitcher(enableSelfServiceErrorCodes = useSelfServiceErrorCodes)
|
||||
)
|
||||
def apply() = new ErrorFactories
|
||||
|
||||
private[daml] lazy val definiteAnswers = Map(
|
||||
true -> AnyProto.pack[ErrorInfo](
|
||||
|
@ -24,18 +24,18 @@ class FieldValidations private (errorFactories: ErrorFactories) {
|
||||
case None => Right(None)
|
||||
case Some(`ledgerId`) => Right(Some(ledgerId))
|
||||
case Some(mismatching) =>
|
||||
Left(ledgerIdMismatch(ledgerId, mismatching, definiteAnswer = Some(false)))
|
||||
Left(ledgerIdMismatch(ledgerId, mismatching))
|
||||
}
|
||||
|
||||
def requireNonEmptyString(s: String, fieldName: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): Either[StatusRuntimeException, String] =
|
||||
Either.cond(s.nonEmpty, s, missingField(fieldName, definiteAnswer = Some(false)))
|
||||
Either.cond(s.nonEmpty, s, missingField(fieldName))
|
||||
|
||||
def requireIdentifier(s: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): Either[StatusRuntimeException, Ref.Name] =
|
||||
Ref.Name.fromString(s).left.map(invalidArgument(definiteAnswer = Some(false)))
|
||||
Ref.Name.fromString(s).left.map(invalidArgument)
|
||||
|
||||
private def requireNonEmptyParsedId[T](parser: String => Either[String, T])(
|
||||
s: String,
|
||||
@ -44,9 +44,9 @@ class FieldValidations private (errorFactories: ErrorFactories) {
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): Either[StatusRuntimeException, T] =
|
||||
if (s.isEmpty)
|
||||
Left(missingField(fieldName, definiteAnswer = Some(false)))
|
||||
Left(missingField(fieldName))
|
||||
else
|
||||
parser(s).left.map(invalidField(fieldName, _, definiteAnswer = Some(false)))
|
||||
parser(s).left.map(invalidField(fieldName, _))
|
||||
|
||||
def requireName(
|
||||
s: String,
|
||||
@ -67,7 +67,7 @@ class FieldValidations private (errorFactories: ErrorFactories) {
|
||||
def requireParty(s: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): Either[StatusRuntimeException, Ref.Party] =
|
||||
Ref.Party.fromString(s).left.map(invalidArgument(definiteAnswer = Some(false)))
|
||||
Ref.Party.fromString(s).left.map(invalidArgument)
|
||||
|
||||
def requireParties(parties: Set[String])(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
@ -107,7 +107,7 @@ class FieldValidations private (errorFactories: ErrorFactories) {
|
||||
def requireLedgerString(s: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): Either[StatusRuntimeException, Ref.LedgerString] =
|
||||
Ref.LedgerString.fromString(s).left.map(invalidArgument(definiteAnswer = Some(false)))
|
||||
Ref.LedgerString.fromString(s).left.map(invalidArgument)
|
||||
|
||||
def validateSubmissionId(s: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
@ -117,7 +117,7 @@ class FieldValidations private (errorFactories: ErrorFactories) {
|
||||
.fromString(nonEmptyString)
|
||||
.map(domain.SubmissionId(_))
|
||||
.left
|
||||
.map(invalidField("submission_id", _, definiteAnswer = Some(false)))
|
||||
.map(invalidField("submission_id", _))
|
||||
}
|
||||
|
||||
def requireContractId(
|
||||
@ -126,8 +126,8 @@ class FieldValidations private (errorFactories: ErrorFactories) {
|
||||
)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): Either[StatusRuntimeException, ContractId] =
|
||||
if (s.isEmpty) Left(missingField(fieldName, definiteAnswer = Some(false)))
|
||||
else ContractId.fromString(s).left.map(invalidField(fieldName, _, definiteAnswer = Some(false)))
|
||||
if (s.isEmpty) Left(missingField(fieldName))
|
||||
else ContractId.fromString(s).left.map(invalidField(fieldName, _))
|
||||
|
||||
def requireDottedName(
|
||||
s: String,
|
||||
@ -135,7 +135,7 @@ class FieldValidations private (errorFactories: ErrorFactories) {
|
||||
)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): Either[StatusRuntimeException, Ref.DottedName] =
|
||||
Ref.DottedName.fromString(s).left.map(invalidField(fieldName, _, definiteAnswer = Some(false)))
|
||||
Ref.DottedName.fromString(s).left.map(invalidField(fieldName, _))
|
||||
|
||||
def requireNonEmpty[M[_] <: Iterable[_], T](
|
||||
s: M[T],
|
||||
@ -144,13 +144,13 @@ class FieldValidations private (errorFactories: ErrorFactories) {
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): Either[StatusRuntimeException, M[T]] =
|
||||
if (s.nonEmpty) Right(s)
|
||||
else Left(missingField(fieldName, definiteAnswer = Some(false)))
|
||||
else Left(missingField(fieldName))
|
||||
|
||||
def requirePresence[T](option: Option[T], fieldName: String)(implicit
|
||||
contextualizedErrorLogger: ContextualizedErrorLogger
|
||||
): Either[StatusRuntimeException, T] =
|
||||
option.fold[Either[StatusRuntimeException, T]](
|
||||
Left(missingField(fieldName, definiteAnswer = Some(false)))
|
||||
Left(missingField(fieldName))
|
||||
)(Right(_))
|
||||
|
||||
def validateIdentifier(identifier: Identifier)(implicit
|
||||
|
@ -30,39 +30,6 @@ trait ValidatorTestUtils extends Matchers with Inside with OptionValues { self:
|
||||
protected val transactionId = "42"
|
||||
protected val ledgerEnd = domain.LedgerOffset.Absolute(Ref.LedgerString.assertFromString("1000"))
|
||||
|
||||
/** Fixture that facilitates testing validators with and without self-service error codes.
|
||||
*
|
||||
* @param testedFactory Creates an instance of a validator to be tested.
|
||||
* Accepts a boolean to decide if self-service error codes should be enabled.
|
||||
*/
|
||||
class ValidatorFixture[T](testedFactory: Boolean => T) {
|
||||
def testRequestFailure(
|
||||
testedRequest: T => Either[StatusRuntimeException, _],
|
||||
expectedCodeV1: Code,
|
||||
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[String, String],
|
||||
)
|
||||
requestMustFailWith(
|
||||
request = testedRequest(testedFactory(true)),
|
||||
code = expectedCodeV2,
|
||||
description = expectedDescriptionV2,
|
||||
metadataV2,
|
||||
)
|
||||
}
|
||||
|
||||
def tested(enabledSelfServiceErrorCodes: Boolean): T = {
|
||||
testedFactory(enabledSelfServiceErrorCodes)
|
||||
}
|
||||
}
|
||||
|
||||
protected def hasExpectedFilters(req: transaction.GetTransactionsRequest) = {
|
||||
val filtersByParty = req.filter.filtersByParty
|
||||
filtersByParty should have size 1
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
package com.daml.ledger.api
|
||||
|
||||
import com.daml.error.{ErrorCodesVersionSwitcher, NoLogging}
|
||||
import com.daml.error.NoLogging
|
||||
import com.daml.ledger.api.v1.value.Value.Sum
|
||||
import com.daml.ledger.api.v1.{value => api}
|
||||
import com.daml.ledger.api.validation.{ValidatorTestUtils, ValueValidator}
|
||||
@ -28,8 +28,7 @@ class ValueConversionRoundTripTest
|
||||
|
||||
private val constructor: String = "constructor"
|
||||
|
||||
private val errorCodesVersionSwitcher = mock[ErrorCodesVersionSwitcher]
|
||||
private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
private val errorFactories = ErrorFactories()
|
||||
private val fieldValidations = FieldValidations(errorFactories)
|
||||
private val valueValidator = new ValueValidator(errorFactories, fieldValidations)
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
package com.daml.ledger.api.validation
|
||||
|
||||
import com.daml.error.{ContextualizedErrorLogger, ErrorCodesVersionSwitcher, NoLogging}
|
||||
import com.daml.error.{ContextualizedErrorLogger, NoLogging}
|
||||
import com.daml.ledger.api.domain
|
||||
import com.daml.ledger.api.messages.command.completion.CompletionStreamRequest
|
||||
import com.daml.ledger.api.v1.command_completion_service.{
|
||||
@ -37,21 +37,11 @@ class CompletionServiceRequestValidatorTest
|
||||
|
||||
private val endReq = CompletionEndRequest(expectedLedgerId)
|
||||
|
||||
private val errorCodesVersionSwitcher_mock = mock[ErrorCodesVersionSwitcher]
|
||||
private val validator = new CompletionServiceRequestValidator(
|
||||
domain.LedgerId(expectedLedgerId),
|
||||
PartyNameChecker.AllowAllParties,
|
||||
errorCodesVersionSwitcher = errorCodesVersionSwitcher_mock,
|
||||
)
|
||||
|
||||
val fixture = new ValidatorFixture((selfServiceErrorCodesEnabled: Boolean) => {
|
||||
new CompletionServiceRequestValidator(
|
||||
domain.LedgerId(expectedLedgerId),
|
||||
PartyNameChecker.AllowAllParties,
|
||||
errorCodesVersionSwitcher = new ErrorCodesVersionSwitcher(selfServiceErrorCodesEnabled),
|
||||
)
|
||||
})
|
||||
|
||||
"CompletionRequestValidation" when {
|
||||
|
||||
"validating gRPC completion requests" should {
|
||||
@ -61,36 +51,32 @@ class CompletionServiceRequestValidatorTest
|
||||
validator.validateGrpcCompletionStreamRequest(grpcCompletionReq.withLedgerId(""))
|
||||
) { case Right(req) =>
|
||||
req shouldBe completionReq.copy(ledgerId = None)
|
||||
verifyZeroInteractions(errorCodesVersionSwitcher_mock)
|
||||
}
|
||||
}
|
||||
|
||||
"return the correct error on missing application ID" in {
|
||||
fixture.testRequestFailure(
|
||||
testedRequest = _.validateGrpcCompletionStreamRequest(
|
||||
requestMustFailWith(
|
||||
request = validator.validateGrpcCompletionStreamRequest(
|
||||
grpcCompletionReq.withApplicationId("")
|
||||
),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Missing field: application_id",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"MISSING_FIELD(8,0): The submitted command is missing a mandatory field: application_id",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"return the correct error on unknown begin boundary" in {
|
||||
fixture.testRequestFailure(
|
||||
testedRequest = _.validateGrpcCompletionStreamRequest(
|
||||
requestMustFailWith(
|
||||
request = validator.validateGrpcCompletionStreamRequest(
|
||||
grpcCompletionReq.withOffset(
|
||||
LedgerOffset(LedgerOffset.Value.Boundary(LedgerBoundary.Unrecognized(7)))
|
||||
)
|
||||
),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 =
|
||||
"Invalid argument: Unknown ledger boundary value '7' in field offset.boundary",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"INVALID_ARGUMENT(8,0): The submitted command has invalid arguments: Unknown ledger boundary value '7' in field offset.boundary",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
@ -99,7 +85,6 @@ class CompletionServiceRequestValidatorTest
|
||||
validator.validateGrpcCompletionStreamRequest(grpcCompletionReq)
|
||||
) { case Right(req) =>
|
||||
req shouldBe completionReq
|
||||
verifyZeroInteractions(errorCodesVersionSwitcher_mock)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -111,28 +96,26 @@ class CompletionServiceRequestValidatorTest
|
||||
validator.validateCompletionStreamRequest(completionReq.copy(ledgerId = None), ledgerEnd)
|
||||
) { case Right(req) =>
|
||||
req shouldBe completionReq.copy(ledgerId = None)
|
||||
verifyZeroInteractions(errorCodesVersionSwitcher_mock)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"return the correct error on missing party" in {
|
||||
fixture.testRequestFailure(
|
||||
testedRequest = _.validateCompletionStreamRequest(
|
||||
requestMustFailWith(
|
||||
request = validator.validateCompletionStreamRequest(
|
||||
completionReq.copy(parties = Set.empty),
|
||||
ledgerEnd,
|
||||
),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Missing field: parties",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"MISSING_FIELD(8,0): The submitted command is missing a mandatory field: parties",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"return the correct error when offset is after ledger end" in {
|
||||
fixture.testRequestFailure(
|
||||
testedRequest = _.validateCompletionStreamRequest(
|
||||
requestMustFailWith(
|
||||
request = validator.validateCompletionStreamRequest(
|
||||
completionReq.copy(offset =
|
||||
Some(
|
||||
domain.LedgerOffset.Absolute(
|
||||
@ -142,11 +125,10 @@ class CompletionServiceRequestValidatorTest
|
||||
),
|
||||
ledgerEnd,
|
||||
),
|
||||
expectedCodeV1 = OUT_OF_RANGE,
|
||||
expectedDescriptionV1 = "Begin offset (1001) is after ledger end (1000)",
|
||||
expectedCodeV2 = OUT_OF_RANGE,
|
||||
expectedDescriptionV2 =
|
||||
code = OUT_OF_RANGE,
|
||||
description =
|
||||
"OFFSET_AFTER_LEDGER_END(12,0): Begin offset (1001) is after ledger end (1000)",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
@ -161,7 +143,6 @@ class CompletionServiceRequestValidatorTest
|
||||
req.applicationId shouldEqual expectedApplicationId
|
||||
req.parties shouldEqual Set(party)
|
||||
req.offset shouldEqual None
|
||||
verifyZeroInteractions(errorCodesVersionSwitcher_mock)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -169,14 +150,13 @@ class CompletionServiceRequestValidatorTest
|
||||
"validating completions end requests" should {
|
||||
|
||||
"fail on ledger ID mismatch" in {
|
||||
fixture.testRequestFailure(
|
||||
testedRequest = _.validateCompletionEndRequest(endReq.withLedgerId("mismatchedLedgerId")),
|
||||
expectedCodeV1 = NOT_FOUND,
|
||||
expectedDescriptionV1 =
|
||||
"Ledger ID 'mismatchedLedgerId' not found. Actual Ledger ID is 'expectedLedgerId'.",
|
||||
expectedCodeV2 = NOT_FOUND,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request =
|
||||
validator.validateCompletionEndRequest(endReq.withLedgerId("mismatchedLedgerId")),
|
||||
code = NOT_FOUND,
|
||||
description =
|
||||
"LEDGER_ID_MISMATCH(11,0): Ledger ID 'mismatchedLedgerId' not found. Actual Ledger ID is 'expectedLedgerId'.",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
@ -184,49 +164,40 @@ class CompletionServiceRequestValidatorTest
|
||||
inside(
|
||||
validator.validateCompletionEndRequest(endReq)
|
||||
) { case Right(_) =>
|
||||
verifyZeroInteractions(errorCodesVersionSwitcher_mock)
|
||||
succeed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"applying party name checks" should {
|
||||
|
||||
val knowsPartyOnlyFixture = new ValidatorFixture(enabled => {
|
||||
new CompletionServiceRequestValidator(
|
||||
domain.LedgerId(expectedLedgerId),
|
||||
PartyNameChecker.AllowPartySet(Set(party)),
|
||||
new ErrorCodesVersionSwitcher(enabled),
|
||||
)
|
||||
})
|
||||
val partyRestrictiveValidator = new CompletionServiceRequestValidator(
|
||||
domain.LedgerId(expectedLedgerId),
|
||||
PartyNameChecker.AllowPartySet(Set(party)),
|
||||
)
|
||||
|
||||
val unknownParties = List("party", "Alice", "Bob").map(Ref.Party.assertFromString).toSet
|
||||
val knownParties = List("party").map(Ref.Party.assertFromString)
|
||||
|
||||
"reject completion requests for unknown parties" in {
|
||||
knowsPartyOnlyFixture.testRequestFailure(
|
||||
testedRequest = _.validateCompletionStreamRequest(
|
||||
requestMustFailWith(
|
||||
request = partyRestrictiveValidator.validateCompletionStreamRequest(
|
||||
completionReq.copy(parties = unknownParties),
|
||||
ledgerEnd,
|
||||
),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Invalid argument: Unknown parties: [Alice, Bob]",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"INVALID_ARGUMENT(8,0): The submitted command has invalid arguments: Unknown parties: [Alice, Bob]",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"accept transaction requests for known parties" in {
|
||||
knowsPartyOnlyFixture
|
||||
.tested(enabledSelfServiceErrorCodes = true)
|
||||
.validateGrpcCompletionStreamRequest(
|
||||
grpcCompletionReq.withParties(knownParties)
|
||||
) shouldBe a[Right[_, _]]
|
||||
knowsPartyOnlyFixture
|
||||
.tested(enabledSelfServiceErrorCodes = false)
|
||||
.validateGrpcCompletionStreamRequest(
|
||||
grpcCompletionReq.withParties(knownParties)
|
||||
) shouldBe a[Right[_, _]]
|
||||
partyRestrictiveValidator.validateGrpcCompletionStreamRequest(
|
||||
grpcCompletionReq.withParties(knownParties)
|
||||
) shouldBe a[Right[_, _]]
|
||||
partyRestrictiveValidator.validateGrpcCompletionStreamRequest(
|
||||
grpcCompletionReq.withParties(knownParties)
|
||||
) shouldBe a[Right[_, _]]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
package com.daml.ledger.api.validation
|
||||
|
||||
import com.daml.error.{ContextualizedErrorLogger, ErrorCodesVersionSwitcher, NoLogging}
|
||||
import com.daml.error.{ContextualizedErrorLogger, NoLogging}
|
||||
import com.daml.ledger.api.DomainMocks
|
||||
import com.daml.ledger.api.v1.value.Identifier
|
||||
import com.daml.platform.server.api.validation.{ErrorFactories, FieldValidations}
|
||||
@ -16,10 +16,7 @@ class IdentifierValidatorTest extends AsyncWordSpec with ValidatorTestUtils with
|
||||
private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = NoLogging
|
||||
|
||||
private val errorFactories_mock = mock[ErrorFactories]
|
||||
private val fieldValidations = FieldValidations(errorFactories_mock)
|
||||
private val fixture = new ValidatorFixture(enabled =>
|
||||
FieldValidations(ErrorFactories(new ErrorCodesVersionSwitcher(enabled)))
|
||||
)
|
||||
private val fieldValidations = FieldValidations(ErrorFactories())
|
||||
|
||||
object api {
|
||||
val identifier = Identifier("package", moduleName = "module", entityName = "entity")
|
||||
@ -33,24 +30,23 @@ class IdentifierValidatorTest extends AsyncWordSpec with ValidatorTestUtils with
|
||||
}
|
||||
|
||||
"not allow missing package ids" in {
|
||||
fixture.testRequestFailure(
|
||||
_.validateIdentifier(api.identifier.withPackageId("")),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = """Missing field: package_id""",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
fieldValidations.validateIdentifier(api.identifier.withPackageId("")),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"MISSING_FIELD(8,0): The submitted command is missing a mandatory field: package_id",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"not allow missing names" in {
|
||||
fixture.testRequestFailure(
|
||||
_.validateIdentifier(api.identifier.withModuleName("").withEntityName("")),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Invalid field module_name: Expected a non-empty string",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request =
|
||||
fieldValidations.validateIdentifier(api.identifier.withModuleName("").withEntityName("")),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"INVALID_FIELD(8,0): The submitted command has a field with invalid value: Invalid field module_name: Expected a non-empty string",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ package com.daml.ledger.api.validation
|
||||
import java.time.{Instant, Duration => JDuration}
|
||||
|
||||
import com.daml.api.util.{DurationConversion, TimestampConversion}
|
||||
import com.daml.error.{ContextualizedErrorLogger, ErrorCodesVersionSwitcher, NoLogging}
|
||||
import com.daml.error.{ContextualizedErrorLogger, NoLogging}
|
||||
import com.daml.ledger.api.DomainMocks.{applicationId, commandId, submissionId, workflowId}
|
||||
import com.daml.ledger.api.domain.{LedgerId, Commands => ApiCommands}
|
||||
import com.daml.ledger.api.v1.commands.Commands.{DeduplicationPeriod => DeduplicationPeriodProto}
|
||||
@ -21,7 +21,7 @@ import com.daml.lf.value.{Value => Lf}
|
||||
import com.daml.platform.server.api.validation.{ErrorFactories, FieldValidations}
|
||||
import com.google.protobuf.duration.Duration
|
||||
import com.google.protobuf.empty.Empty
|
||||
import io.grpc.Status.Code.{INVALID_ARGUMENT, NOT_FOUND, UNAVAILABLE}
|
||||
import io.grpc.Status.Code.{INVALID_ARGUMENT, NOT_FOUND}
|
||||
import org.mockito.MockitoSugar
|
||||
import org.scalatest.prop.TableDrivenPropertyChecks
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
@ -132,24 +132,10 @@ class SubmitRequestValidatorTest
|
||||
|
||||
private def unexpectedError = sys.error("unexpected error")
|
||||
|
||||
private val errorCodesVersionSwitcher_mock = mock[ErrorCodesVersionSwitcher]
|
||||
|
||||
private val commandValidatorFixture = new ValidatorFixture(selfServiceErrorCodesEnabled =>
|
||||
new CommandsValidator(ledgerId, new ErrorCodesVersionSwitcher(selfServiceErrorCodesEnabled))
|
||||
)
|
||||
private val valueValidatorFixture = new ValidatorFixture(selfServiceErrorCodesEnabled => {
|
||||
val errorCodesVersionSwitcher = new ErrorCodesVersionSwitcher(selfServiceErrorCodesEnabled)
|
||||
val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
new ValueValidator(
|
||||
errorFactories,
|
||||
FieldValidations(errorFactories),
|
||||
)
|
||||
})
|
||||
|
||||
private val testedCommandValidator =
|
||||
new CommandsValidator(ledgerId, errorCodesVersionSwitcher_mock)
|
||||
new CommandsValidator(ledgerId)
|
||||
private val testedValueValidator = {
|
||||
val errorFactories = ErrorFactories(errorCodesVersionSwitcher_mock)
|
||||
val errorFactories = ErrorFactories()
|
||||
new ValueValidator(
|
||||
errorFactories,
|
||||
FieldValidations(errorFactories),
|
||||
@ -168,18 +154,17 @@ class SubmitRequestValidatorTest
|
||||
}
|
||||
|
||||
"reject requests with empty commands" in {
|
||||
commandValidatorFixture.testRequestFailure(
|
||||
_.validateCommands(
|
||||
requestMustFailWith(
|
||||
request = testedCommandValidator.validateCommands(
|
||||
api.commands.withCommands(Seq.empty),
|
||||
internal.ledgerTime,
|
||||
internal.submittedAt,
|
||||
Some(internal.maxDeduplicationDuration),
|
||||
),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Missing field: commands",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"MISSING_FIELD(8,0): The submitted command is missing a mandatory field: commands",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
@ -194,7 +179,6 @@ class SubmitRequestValidatorTest
|
||||
}
|
||||
|
||||
"tolerate a missing workflowId" in {
|
||||
|
||||
testedCommandValidator.validateCommands(
|
||||
api.commands.withWorkflowId(""),
|
||||
internal.ledgerTime,
|
||||
@ -209,53 +193,47 @@ class SubmitRequestValidatorTest
|
||||
}
|
||||
|
||||
"not allow missing applicationId" in {
|
||||
|
||||
commandValidatorFixture.testRequestFailure(
|
||||
_.validateCommands(
|
||||
requestMustFailWith(
|
||||
request = testedCommandValidator.validateCommands(
|
||||
api.commands.withApplicationId(""),
|
||||
internal.ledgerTime,
|
||||
internal.submittedAt,
|
||||
Some(internal.maxDeduplicationDuration),
|
||||
),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Missing field: application_id",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"MISSING_FIELD(8,0): The submitted command is missing a mandatory field: application_id",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"not allow missing commandId" in {
|
||||
|
||||
commandValidatorFixture.testRequestFailure(
|
||||
_.validateCommands(
|
||||
requestMustFailWith(
|
||||
request = testedCommandValidator.validateCommands(
|
||||
api.commands.withCommandId(""),
|
||||
internal.ledgerTime,
|
||||
internal.submittedAt,
|
||||
Some(internal.maxDeduplicationDuration),
|
||||
),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Missing field: command_id",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"MISSING_FIELD(8,0): The submitted command is missing a mandatory field: command_id",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"not allow missing submitter" in {
|
||||
|
||||
commandValidatorFixture.testRequestFailure(
|
||||
_.validateCommands(
|
||||
requestMustFailWith(
|
||||
request = testedCommandValidator.validateCommands(
|
||||
api.commands.withParty(""),
|
||||
internal.ledgerTime,
|
||||
internal.submittedAt,
|
||||
Some(internal.maxDeduplicationDuration),
|
||||
),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = """Missing field: party or act_as""",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"MISSING_FIELD(8,0): The submitted command is missing a mandatory field: party or act_as",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
@ -369,18 +347,17 @@ class SubmitRequestValidatorTest
|
||||
DeduplicationPeriodProto.DeduplicationDuration(Duration.of(-1, 0)),
|
||||
)
|
||||
) { deduplication =>
|
||||
commandValidatorFixture.testRequestFailure(
|
||||
_.validateCommands(
|
||||
requestMustFailWith(
|
||||
testedCommandValidator.validateCommands(
|
||||
api.commands.copy(deduplicationPeriod = deduplication),
|
||||
internal.ledgerTime,
|
||||
internal.submittedAt,
|
||||
Some(internal.maxDeduplicationDuration),
|
||||
),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Invalid field deduplication_period: Duration must be positive",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"INVALID_FIELD(8,0): The submitted command has a field with invalid value: Invalid field deduplication_period: Duration must be positive",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -430,14 +407,13 @@ class SubmitRequestValidatorTest
|
||||
}
|
||||
|
||||
"not allow missing ledger configuration" in {
|
||||
|
||||
commandValidatorFixture.testRequestFailure(
|
||||
_.validateCommands(api.commands, internal.ledgerTime, internal.submittedAt, None),
|
||||
expectedCodeV1 = UNAVAILABLE,
|
||||
expectedDescriptionV1 = "The ledger configuration is not available.",
|
||||
expectedCodeV2 = NOT_FOUND,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = testedCommandValidator
|
||||
.validateCommands(api.commands, internal.ledgerTime, internal.submittedAt, None),
|
||||
code = NOT_FOUND,
|
||||
description =
|
||||
"LEDGER_CONFIGURATION_NOT_FOUND(11,0): The ledger configuration could not be retrieved.",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -462,13 +438,12 @@ class SubmitRequestValidatorTest
|
||||
}
|
||||
|
||||
"reject non valid party" in {
|
||||
valueValidatorFixture.testRequestFailure(
|
||||
_.validateValue(DomainMocks.values.invalidApiParty),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = DomainMocks.values.invalidPartyMsg,
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = testedValueValidator.validateValue(DomainMocks.values.invalidApiParty),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"""INVALID_ARGUMENT(8,0): The submitted command has invalid arguments: non expected character 0x40 in Daml-LF Party "p@rty"""",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -518,13 +493,12 @@ class SubmitRequestValidatorTest
|
||||
forEvery(absoluteValues) { (absoluteValue, _) =>
|
||||
val s = sign + absoluteValue
|
||||
val input = Value(Sum.Numeric(s))
|
||||
valueValidatorFixture.testRequestFailure(
|
||||
_.validateValue(input),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = s"""Invalid argument: Could not read Numeric string "$s"""",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = testedValueValidator.validateValue(input),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
s"""INVALID_ARGUMENT(8,0): The submitted command has invalid arguments: Could not read Numeric string "$s"""",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -550,13 +524,12 @@ class SubmitRequestValidatorTest
|
||||
forEvery(absoluteValues) { absoluteValue =>
|
||||
val s = sign + absoluteValue
|
||||
val input = Value(Sum.Numeric(s))
|
||||
valueValidatorFixture.testRequestFailure(
|
||||
_.validateValue(input),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = s"""Invalid argument: Could not read Numeric string "$s"""",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = testedValueValidator.validateValue(input),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
s"""INVALID_ARGUMENT(8,0): The submitted command has invalid arguments: Could not read Numeric string "$s"""",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -604,13 +577,12 @@ class SubmitRequestValidatorTest
|
||||
|
||||
forEvery(testCases) { long =>
|
||||
val input = Value(Sum.Timestamp(long))
|
||||
valueValidatorFixture.testRequestFailure(
|
||||
_.validateValue(input),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = s"Invalid argument: out of bound Timestamp $long",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = testedValueValidator.validateValue(input),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
s"INVALID_ARGUMENT(8,0): The submitted command has invalid arguments: out of bound Timestamp $long",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -643,13 +615,12 @@ class SubmitRequestValidatorTest
|
||||
|
||||
forEvery(testCases) { int =>
|
||||
val input = Value(Sum.Date(int))
|
||||
valueValidatorFixture.testRequestFailure(
|
||||
_.validateValue(input),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = s"Invalid argument: out of bound Date $int",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = testedValueValidator.validateValue(input),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
s"INVALID_ARGUMENT(8,0): The submitted command has invalid arguments: out of bound Date $int",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -703,13 +674,12 @@ class SubmitRequestValidatorTest
|
||||
"not allow missing record values" in {
|
||||
val record =
|
||||
Value(Sum.Record(Record(Some(api.identifier), Seq(RecordField(api.label, None)))))
|
||||
valueValidatorFixture.testRequestFailure(
|
||||
_.validateValue(record),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Missing field: value",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = testedValueValidator.validateValue(record),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"MISSING_FIELD(8,0): The submitted command is missing a mandatory field: value",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
@ -738,25 +708,23 @@ class SubmitRequestValidatorTest
|
||||
|
||||
"not allow missing constructor" in {
|
||||
val variant = Value(Sum.Variant(Variant(None, "", Some(Value(api.int64)))))
|
||||
valueValidatorFixture.testRequestFailure(
|
||||
_.validateValue(variant),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Missing field: constructor",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = testedValueValidator.validateValue(variant),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"MISSING_FIELD(8,0): The submitted command is missing a mandatory field: constructor",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"not allow missing values" in {
|
||||
val variant = Value(Sum.Variant(Variant(None, api.constructor, None)))
|
||||
valueValidatorFixture.testRequestFailure(
|
||||
_.validateValue(variant),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Missing field: value",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = testedValueValidator.validateValue(variant),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"MISSING_FIELD(8,0): The submitted command is missing a mandatory field: value",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
@ -784,13 +752,12 @@ class SubmitRequestValidatorTest
|
||||
ApiList(Seq(DomainMocks.values.validApiParty, DomainMocks.values.invalidApiParty))
|
||||
)
|
||||
)
|
||||
valueValidatorFixture.testRequestFailure(
|
||||
_.validateValue(input),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = DomainMocks.values.invalidPartyMsg,
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = testedValueValidator.validateValue(input),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"""INVALID_ARGUMENT(8,0): The submitted command has invalid arguments: non expected character 0x40 in Daml-LF Party "p@rty"""",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -811,13 +778,12 @@ class SubmitRequestValidatorTest
|
||||
|
||||
"reject optional containing invalid values" in {
|
||||
val input = Value(Sum.Optional(ApiOptional(Some(DomainMocks.values.invalidApiParty))))
|
||||
valueValidatorFixture.testRequestFailure(
|
||||
_.validateValue(input),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = DomainMocks.values.invalidPartyMsg,
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = testedValueValidator.validateValue(input),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"""INVALID_ARGUMENT(8,0): The submitted command has invalid arguments: non expected character 0x40 in Daml-LF Party "p@rty"""",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -856,14 +822,12 @@ class SubmitRequestValidatorTest
|
||||
ApiMap.Entry(k, Some(Value(Sum.Int64(v))))
|
||||
}
|
||||
val input = Value(Sum.Map(ApiMap(apiEntries.toSeq)))
|
||||
valueValidatorFixture.testRequestFailure(
|
||||
_.validateValue(input),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 =
|
||||
s"Invalid argument: key ${Utf8.sha256("1")} duplicated when trying to build map",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = testedValueValidator.validateValue(input),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"INVALID_ARGUMENT(8,0): The submitted command has invalid arguments: key 6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b duplicated when trying to build map",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
@ -874,13 +838,12 @@ class SubmitRequestValidatorTest
|
||||
ApiMap.Entry("2", Some(DomainMocks.values.invalidApiParty)),
|
||||
)
|
||||
val input = Value(Sum.Map(ApiMap(apiEntries)))
|
||||
valueValidatorFixture.testRequestFailure(
|
||||
_.validateValue(input),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = DomainMocks.values.invalidPartyMsg,
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = testedValueValidator.validateValue(input),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"""INVALID_ARGUMENT(8,0): The submitted command has invalid arguments: non expected character 0x40 in Daml-LF Party "p@rty"""",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
package com.daml.ledger.api.validation
|
||||
|
||||
import com.daml.error.{ContextualizedErrorLogger, ErrorCodesVersionSwitcher, NoLogging}
|
||||
import com.daml.error.{ContextualizedErrorLogger, NoLogging}
|
||||
import com.daml.ledger.api.domain
|
||||
import com.daml.ledger.api.v1.ledger_offset.LedgerOffset
|
||||
import com.daml.ledger.api.v1.ledger_offset.LedgerOffset.LedgerBoundary
|
||||
@ -68,27 +68,18 @@ class TransactionServiceRequestValidatorTest
|
||||
private val txByIdReq =
|
||||
GetTransactionByIdRequest(expectedLedgerId, transactionId, Seq(party))
|
||||
|
||||
private val errorCodesVersionSwitcher_mock = mock[ErrorCodesVersionSwitcher]
|
||||
private val testedValidator = new TransactionServiceRequestValidator(
|
||||
private val validator = new TransactionServiceRequestValidator(
|
||||
domain.LedgerId(expectedLedgerId),
|
||||
PartyNameChecker.AllowAllParties,
|
||||
ErrorFactories(errorCodesVersionSwitcher_mock),
|
||||
ErrorFactories(),
|
||||
)
|
||||
|
||||
private val fixture = new ValidatorFixture((selfServiceErrorCodesEnabled: Boolean) => {
|
||||
new TransactionServiceRequestValidator(
|
||||
domain.LedgerId(expectedLedgerId),
|
||||
PartyNameChecker.AllowAllParties,
|
||||
ErrorFactories(new ErrorCodesVersionSwitcher(selfServiceErrorCodesEnabled)),
|
||||
)
|
||||
})
|
||||
|
||||
"TransactionRequestValidation" when {
|
||||
|
||||
"validating regular requests" should {
|
||||
|
||||
"accept requests with empty ledger ID" in {
|
||||
inside(testedValidator.validate(txReq.withLedgerId(""), ledgerEnd)) { case Right(req) =>
|
||||
inside(validator.validate(txReq.withLedgerId(""), ledgerEnd)) { case Right(req) =>
|
||||
req.ledgerId shouldEqual None
|
||||
req.startExclusive shouldEqual domain.LedgerOffset.LedgerBegin
|
||||
req.endInclusive shouldEqual Some(domain.LedgerOffset.Absolute(absoluteOffset))
|
||||
@ -101,131 +92,120 @@ class TransactionServiceRequestValidatorTest
|
||||
}
|
||||
|
||||
"return the correct error on missing filter" in {
|
||||
fixture.testRequestFailure(
|
||||
_.validate(txReq.update(_.optionalFilter := None), ledgerEnd),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Missing field: filter",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
validator.validate(txReq.update(_.optionalFilter := None), ledgerEnd),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"MISSING_FIELD(8,0): The submitted command is missing a mandatory field: filter",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"return the correct error on empty filter" in {
|
||||
fixture.testRequestFailure(
|
||||
_.validate(
|
||||
requestMustFailWith(
|
||||
request = validator.validate(
|
||||
txReq.update(_.filter.filtersByParty := Map.empty),
|
||||
ledgerEnd,
|
||||
),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Invalid argument: filtersByParty cannot be empty",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"INVALID_ARGUMENT(8,0): The submitted command has invalid arguments: filtersByParty cannot be empty",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"return the correct error on missing begin" in {
|
||||
fixture.testRequestFailure(
|
||||
_.validate(txReq.update(_.optionalBegin := None), ledgerEnd),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Missing field: begin",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = validator.validate(txReq.update(_.optionalBegin := None), ledgerEnd),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"MISSING_FIELD(8,0): The submitted command is missing a mandatory field: begin",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"return the correct error on empty begin " in {
|
||||
fixture.testRequestFailure(
|
||||
_.validate(txReq.update(_.begin := LedgerOffset()), ledgerEnd),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Missing field: begin.(boundary|value)",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = validator.validate(txReq.update(_.begin := LedgerOffset()), ledgerEnd),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"MISSING_FIELD(8,0): The submitted command is missing a mandatory field: begin.(boundary|value)",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"return the correct error on empty end " in {
|
||||
fixture.testRequestFailure(
|
||||
_.validate(txReq.withEnd(LedgerOffset()), ledgerEnd),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Missing field: end.(boundary|value)",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = validator.validate(txReq.withEnd(LedgerOffset()), ledgerEnd),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"MISSING_FIELD(8,0): The submitted command is missing a mandatory field: end.(boundary|value)",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"return the correct error on unknown begin boundary" in {
|
||||
fixture.testRequestFailure(
|
||||
_.validate(
|
||||
requestMustFailWith(
|
||||
request = validator.validate(
|
||||
txReq.withBegin(
|
||||
LedgerOffset(LedgerOffset.Value.Boundary(LedgerBoundary.Unrecognized(7)))
|
||||
),
|
||||
ledgerEnd,
|
||||
),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 =
|
||||
"Invalid argument: Unknown ledger boundary value '7' in field begin.boundary",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"INVALID_ARGUMENT(8,0): The submitted command has invalid arguments: Unknown ledger boundary value '7' in field begin.boundary",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"return the correct error on unknown end boundary" in {
|
||||
fixture.testRequestFailure(
|
||||
_.validate(
|
||||
requestMustFailWith(
|
||||
request = validator.validate(
|
||||
txReq.withEnd(
|
||||
LedgerOffset(LedgerOffset.Value.Boundary(LedgerBoundary.Unrecognized(7)))
|
||||
),
|
||||
ledgerEnd,
|
||||
),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 =
|
||||
"Invalid argument: Unknown ledger boundary value '7' in field end.boundary",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"INVALID_ARGUMENT(8,0): The submitted command has invalid arguments: Unknown ledger boundary value '7' in field end.boundary",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"return the correct error when begin offset is after ledger end" in {
|
||||
fixture.testRequestFailure(
|
||||
_.validate(
|
||||
requestMustFailWith(
|
||||
request = validator.validate(
|
||||
txReq.withBegin(
|
||||
LedgerOffset(LedgerOffset.Value.Absolute((ledgerEnd.value.toInt + 1).toString))
|
||||
),
|
||||
ledgerEnd,
|
||||
),
|
||||
expectedCodeV1 = OUT_OF_RANGE,
|
||||
expectedDescriptionV1 = "Begin offset (1001) is after ledger end (1000)",
|
||||
expectedCodeV2 = OUT_OF_RANGE,
|
||||
expectedDescriptionV2 =
|
||||
code = OUT_OF_RANGE,
|
||||
description =
|
||||
"OFFSET_AFTER_LEDGER_END(12,0): Begin offset (1001) is after ledger end (1000)",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"return the correct error when end offset is after ledger end" in {
|
||||
fixture.testRequestFailure(
|
||||
_.validate(
|
||||
requestMustFailWith(
|
||||
request = validator.validate(
|
||||
txReq.withEnd(
|
||||
LedgerOffset(LedgerOffset.Value.Absolute((ledgerEnd.value.toInt + 1).toString))
|
||||
),
|
||||
ledgerEnd,
|
||||
),
|
||||
expectedCodeV1 = OUT_OF_RANGE,
|
||||
expectedDescriptionV1 = "End offset (1001) is after ledger end (1000)",
|
||||
expectedCodeV2 = OUT_OF_RANGE,
|
||||
expectedDescriptionV2 =
|
||||
code = OUT_OF_RANGE,
|
||||
description =
|
||||
"OFFSET_AFTER_LEDGER_END(12,0): End offset (1001) is after ledger end (1000)",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"tolerate missing end" in {
|
||||
inside(testedValidator.validate(txReq.update(_.optionalEnd := None), ledgerEnd)) {
|
||||
inside(validator.validate(txReq.update(_.optionalEnd := None), ledgerEnd)) {
|
||||
case Right(req) =>
|
||||
req.ledgerId shouldEqual Some(expectedLedgerId)
|
||||
req.startExclusive shouldEqual domain.LedgerOffset.LedgerBegin
|
||||
@ -239,7 +219,7 @@ class TransactionServiceRequestValidatorTest
|
||||
|
||||
"tolerate empty filters_inclusive" in {
|
||||
inside(
|
||||
testedValidator.validate(
|
||||
validator.validate(
|
||||
txReq.update(_.filter.filtersByParty.modify(_.map { case (p, f) =>
|
||||
p -> f.update(_.inclusive := InclusiveFilters(Nil))
|
||||
})),
|
||||
@ -261,7 +241,7 @@ class TransactionServiceRequestValidatorTest
|
||||
|
||||
"tolerate missing filters_inclusive" in {
|
||||
inside(
|
||||
testedValidator.validate(
|
||||
validator.validate(
|
||||
txReq.update(_.filter.filtersByParty.modify(_.map { case (p, f) =>
|
||||
p -> f.update(_.optionalInclusive := None)
|
||||
})),
|
||||
@ -282,7 +262,7 @@ class TransactionServiceRequestValidatorTest
|
||||
}
|
||||
|
||||
"tolerate all fields filled out" in {
|
||||
inside(testedValidator.validate(txReq, ledgerEnd)) { case Right(req) =>
|
||||
inside(validator.validate(txReq, ledgerEnd)) { case Right(req) =>
|
||||
req.ledgerId shouldEqual Some(expectedLedgerId)
|
||||
req.startExclusive shouldEqual domain.LedgerOffset.LedgerBegin
|
||||
req.endInclusive shouldEqual Some(domain.LedgerOffset.Absolute(absoluteOffset))
|
||||
@ -295,7 +275,7 @@ class TransactionServiceRequestValidatorTest
|
||||
"validating tree requests" should {
|
||||
|
||||
"tolerate missing filters_inclusive" in {
|
||||
inside(testedValidator.validateTree(txTreeReq, ledgerEnd)) { case Right(req) =>
|
||||
inside(validator.validateTree(txTreeReq, ledgerEnd)) { case Right(req) =>
|
||||
req.ledgerId shouldEqual Some(expectedLedgerId)
|
||||
req.startExclusive shouldEqual domain.LedgerOffset.LedgerBegin
|
||||
req.endInclusive shouldEqual Some(domain.LedgerOffset.Absolute(absoluteOffset))
|
||||
@ -306,51 +286,47 @@ class TransactionServiceRequestValidatorTest
|
||||
}
|
||||
|
||||
"not tolerate having filters_inclusive" in {
|
||||
fixture.testRequestFailure(
|
||||
_.validateTree(
|
||||
requestMustFailWith(
|
||||
request = validator.validateTree(
|
||||
txTreeReq.update(_.filter.filtersByParty.modify(_.map { case (p, f) =>
|
||||
p -> f.update(_.optionalInclusive := Some(InclusiveFilters()))
|
||||
})),
|
||||
ledgerEnd,
|
||||
),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 =
|
||||
"Invalid argument: party attempted subscription for templates []. Template filtration is not supported on GetTransactionTrees RPC. To get filtered data, use the GetTransactions RPC.",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"INVALID_ARGUMENT(8,0): The submitted command has invalid arguments: party attempted subscription for templates []. Template filtration is not supported on GetTransactionTrees RPC. To get filtered data, use the GetTransactions RPC.",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"return the correct error when begin offset is after ledger end" in {
|
||||
fixture.testRequestFailure(
|
||||
_.validateTree(
|
||||
requestMustFailWith(
|
||||
request = validator.validateTree(
|
||||
txTreeReq.withBegin(
|
||||
LedgerOffset(LedgerOffset.Value.Absolute((ledgerEnd.value.toInt + 1).toString))
|
||||
),
|
||||
ledgerEnd,
|
||||
),
|
||||
expectedCodeV1 = OUT_OF_RANGE,
|
||||
expectedDescriptionV1 = "Begin offset (1001) is after ledger end (1000)",
|
||||
expectedCodeV2 = OUT_OF_RANGE,
|
||||
expectedDescriptionV2 =
|
||||
code = OUT_OF_RANGE,
|
||||
description =
|
||||
"OFFSET_AFTER_LEDGER_END(12,0): Begin offset (1001) is after ledger end (1000)",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"return the correct error when end offset is after ledger end" in {
|
||||
fixture.testRequestFailure(
|
||||
_.validateTree(
|
||||
requestMustFailWith(
|
||||
request = validator.validateTree(
|
||||
txTreeReq.withEnd(
|
||||
LedgerOffset(LedgerOffset.Value.Absolute((ledgerEnd.value.toInt + 1).toString))
|
||||
),
|
||||
ledgerEnd,
|
||||
),
|
||||
expectedCodeV1 = OUT_OF_RANGE,
|
||||
expectedDescriptionV1 = "End offset (1001) is after ledger end (1000)",
|
||||
expectedCodeV2 = OUT_OF_RANGE,
|
||||
expectedDescriptionV2 =
|
||||
code = OUT_OF_RANGE,
|
||||
description =
|
||||
"OFFSET_AFTER_LEDGER_END(12,0): End offset (1001) is after ledger end (1000)",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -358,19 +334,17 @@ class TransactionServiceRequestValidatorTest
|
||||
"validating ledger end requests" should {
|
||||
|
||||
"fail on ledger ID mismatch" in {
|
||||
fixture.testRequestFailure(
|
||||
_.validateLedgerEnd(endReq.withLedgerId("mismatchedLedgerId")),
|
||||
expectedCodeV1 = NOT_FOUND,
|
||||
expectedDescriptionV1 =
|
||||
"Ledger ID 'mismatchedLedgerId' not found. Actual Ledger ID is 'expectedLedgerId'.",
|
||||
expectedCodeV2 = NOT_FOUND,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = validator.validateLedgerEnd(endReq.withLedgerId("mismatchedLedgerId")),
|
||||
code = NOT_FOUND,
|
||||
description =
|
||||
"LEDGER_ID_MISMATCH(11,0): Ledger ID 'mismatchedLedgerId' not found. Actual Ledger ID is 'expectedLedgerId'.",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"succeed validating a correct request" in {
|
||||
inside(testedValidator.validateLedgerEnd(endReq)) { case Right(_) =>
|
||||
inside(validator.validateLedgerEnd(endReq)) { case Right(_) =>
|
||||
succeed
|
||||
}
|
||||
}
|
||||
@ -379,41 +353,37 @@ class TransactionServiceRequestValidatorTest
|
||||
"validating transaction by id requests" should {
|
||||
|
||||
"fail on ledger ID mismatch" in {
|
||||
fixture.testRequestFailure(
|
||||
_.validateTransactionById(txByIdReq.withLedgerId("mismatchedLedgerId")),
|
||||
expectedCodeV1 = NOT_FOUND,
|
||||
expectedDescriptionV1 =
|
||||
"Ledger ID 'mismatchedLedgerId' not found. Actual Ledger ID is 'expectedLedgerId'.",
|
||||
expectedCodeV2 = NOT_FOUND,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = validator.validateTransactionById(txByIdReq.withLedgerId("mismatchedLedgerId")),
|
||||
code = NOT_FOUND,
|
||||
description =
|
||||
"LEDGER_ID_MISMATCH(11,0): Ledger ID 'mismatchedLedgerId' not found. Actual Ledger ID is 'expectedLedgerId'.",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"fail on empty transactionId" in {
|
||||
fixture.testRequestFailure(
|
||||
_.validateTransactionById(txByIdReq.withTransactionId("")),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Missing field: transaction_id",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = validator.validateTransactionById(txByIdReq.withTransactionId("")),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"MISSING_FIELD(8,0): The submitted command is missing a mandatory field: transaction_id",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"fail on empty requesting parties" in {
|
||||
fixture.testRequestFailure(
|
||||
_.validateTransactionById(txByIdReq.withRequestingParties(Nil)),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Missing field: requesting_parties",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = validator.validateTransactionById(txByIdReq.withRequestingParties(Nil)),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"MISSING_FIELD(8,0): The submitted command is missing a mandatory field: requesting_parties",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"return passed ledger ID" in {
|
||||
inside(testedValidator.validateTransactionById(txByIdReq)) { case Right(out) =>
|
||||
inside(validator.validateTransactionById(txByIdReq)) { case Right(out) =>
|
||||
out should have(Symbol("ledgerId")(Some(expectedLedgerId)))
|
||||
}
|
||||
}
|
||||
@ -423,42 +393,39 @@ class TransactionServiceRequestValidatorTest
|
||||
"validating transaction by event id requests" should {
|
||||
|
||||
"fail on ledger ID mismatch" in {
|
||||
fixture.testRequestFailure(
|
||||
_.validateTransactionByEventId(txByEvIdReq.withLedgerId("mismatchedLedgerId")),
|
||||
expectedCodeV1 = NOT_FOUND,
|
||||
expectedDescriptionV1 =
|
||||
"Ledger ID 'mismatchedLedgerId' not found. Actual Ledger ID is 'expectedLedgerId'.",
|
||||
expectedCodeV2 = NOT_FOUND,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request =
|
||||
validator.validateTransactionByEventId(txByEvIdReq.withLedgerId("mismatchedLedgerId")),
|
||||
code = NOT_FOUND,
|
||||
description =
|
||||
"LEDGER_ID_MISMATCH(11,0): Ledger ID 'mismatchedLedgerId' not found. Actual Ledger ID is 'expectedLedgerId'.",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"fail on empty eventId" in {
|
||||
fixture.testRequestFailure(
|
||||
_.validateTransactionByEventId(txByEvIdReq.withEventId("")),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Missing field: event_id",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = validator.validateTransactionByEventId(txByEvIdReq.withEventId("")),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"MISSING_FIELD(8,0): The submitted command is missing a mandatory field: event_id",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"fail on empty requesting parties" in {
|
||||
fixture.testRequestFailure(
|
||||
_.validateTransactionByEventId(txByEvIdReq.withRequestingParties(Nil)),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Missing field: requesting_parties",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = validator.validateTransactionByEventId(txByEvIdReq.withRequestingParties(Nil)),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"MISSING_FIELD(8,0): The submitted command is missing a mandatory field: requesting_parties",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"return passed ledger ID" in {
|
||||
inside(
|
||||
testedValidator.validateTransactionByEventId(txByEvIdReq)
|
||||
validator.validateTransactionByEventId(txByEvIdReq)
|
||||
) { case Right(out) =>
|
||||
out should have(Symbol("ledgerId")(Some(expectedLedgerId)))
|
||||
}
|
||||
@ -468,13 +435,11 @@ class TransactionServiceRequestValidatorTest
|
||||
|
||||
"applying party name checks" should {
|
||||
|
||||
val knowsPartyOnlyFixture = new ValidatorFixture((selfServiceErrorCodesEnabled: Boolean) => {
|
||||
new TransactionServiceRequestValidator(
|
||||
domain.LedgerId(expectedLedgerId),
|
||||
PartyNameChecker.AllowPartySet(Set(party)),
|
||||
ErrorFactories(new ErrorCodesVersionSwitcher(selfServiceErrorCodesEnabled)),
|
||||
)
|
||||
})
|
||||
val partyRestrictiveValidator = new TransactionServiceRequestValidator(
|
||||
domain.LedgerId(expectedLedgerId),
|
||||
PartyNameChecker.AllowPartySet(Set(party)),
|
||||
ErrorFactories(),
|
||||
)
|
||||
|
||||
val partyWithUnknowns = List("party", "Alice", "Bob")
|
||||
val filterWithUnknown =
|
||||
@ -483,85 +448,75 @@ class TransactionServiceRequestValidatorTest
|
||||
TransactionFilter(Map(party -> Filters.defaultInstance))
|
||||
|
||||
"reject transaction requests for unknown parties" in {
|
||||
knowsPartyOnlyFixture.testRequestFailure(
|
||||
_.validate(txReq.withFilter(filterWithUnknown), ledgerEnd),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Invalid argument: Unknown parties: [Alice, Bob]",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request =
|
||||
partyRestrictiveValidator.validate(txReq.withFilter(filterWithUnknown), ledgerEnd),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"INVALID_ARGUMENT(8,0): The submitted command has invalid arguments: Unknown parties: [Alice, Bob]",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"reject transaction tree requests for unknown parties" in {
|
||||
knowsPartyOnlyFixture.testRequestFailure(
|
||||
_.validateTree(txTreeReq.withFilter(filterWithUnknown), ledgerEnd),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Invalid argument: Unknown parties: [Alice, Bob]",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
requestMustFailWith(
|
||||
request = partyRestrictiveValidator
|
||||
.validateTree(txTreeReq.withFilter(filterWithUnknown), ledgerEnd),
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"INVALID_ARGUMENT(8,0): The submitted command has invalid arguments: Unknown parties: [Alice, Bob]",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"reject transaction by id requests for unknown parties" in {
|
||||
knowsPartyOnlyFixture.testRequestFailure(
|
||||
_.validateTransactionById(
|
||||
requestMustFailWith(
|
||||
request = partyRestrictiveValidator.validateTransactionById(
|
||||
txByIdReq.withRequestingParties(partyWithUnknowns)
|
||||
),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Invalid argument: Unknown parties: [Alice, Bob]",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"INVALID_ARGUMENT(8,0): The submitted command has invalid arguments: Unknown parties: [Alice, Bob]",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"reject transaction by event id requests for unknown parties" in {
|
||||
knowsPartyOnlyFixture.testRequestFailure(
|
||||
_.validateTransactionById(
|
||||
requestMustFailWith(
|
||||
request = partyRestrictiveValidator.validateTransactionById(
|
||||
txByIdReq.withRequestingParties(partyWithUnknowns)
|
||||
),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 = "Invalid argument: Unknown parties: [Alice, Bob]",
|
||||
expectedCodeV2 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV2 =
|
||||
code = INVALID_ARGUMENT,
|
||||
description =
|
||||
"INVALID_ARGUMENT(8,0): The submitted command has invalid arguments: Unknown parties: [Alice, Bob]",
|
||||
metadata = Map.empty,
|
||||
)
|
||||
}
|
||||
|
||||
"accept transaction requests for known parties" in {
|
||||
knowsPartyOnlyFixture
|
||||
.tested(true)
|
||||
.validate(
|
||||
txReq.withFilter(filterWithKnown),
|
||||
ledgerEnd,
|
||||
) shouldBe a[Right[_, _]]
|
||||
partyRestrictiveValidator.validate(
|
||||
txReq.withFilter(filterWithKnown),
|
||||
ledgerEnd,
|
||||
) shouldBe a[Right[_, _]]
|
||||
}
|
||||
|
||||
"accept transaction tree requests for known parties" in {
|
||||
knowsPartyOnlyFixture
|
||||
.tested(true)
|
||||
.validateTree(
|
||||
txTreeReq.withFilter(filterWithKnown),
|
||||
ledgerEnd,
|
||||
) shouldBe a[Right[_, _]]
|
||||
partyRestrictiveValidator.validateTree(
|
||||
txTreeReq.withFilter(filterWithKnown),
|
||||
ledgerEnd,
|
||||
) shouldBe a[Right[_, _]]
|
||||
}
|
||||
|
||||
"accept transaction by id requests for known parties" in {
|
||||
knowsPartyOnlyFixture
|
||||
.tested(true)
|
||||
.validateTransactionById(
|
||||
txByIdReq.withRequestingParties(List("party"))
|
||||
) shouldBe a[Right[_, _]]
|
||||
partyRestrictiveValidator.validateTransactionById(
|
||||
txByIdReq.withRequestingParties(List("party"))
|
||||
) shouldBe a[Right[_, _]]
|
||||
}
|
||||
|
||||
"accept transaction by event id requests for known parties" in {
|
||||
knowsPartyOnlyFixture
|
||||
.tested(true)
|
||||
.validateTransactionById(
|
||||
txByIdReq.withRequestingParties(List("party"))
|
||||
) shouldBe a[Right[_, _]]
|
||||
partyRestrictiveValidator.validateTransactionById(
|
||||
txByIdReq.withRequestingParties(List("party"))
|
||||
) shouldBe a[Right[_, _]]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
package com.daml.platform.server.api.services.grpc
|
||||
|
||||
import com.daml.error.ErrorCodesVersionSwitcher
|
||||
import com.daml.ledger.api.domain.LedgerId
|
||||
import com.daml.ledger.api.testing.utils.MockMessages._
|
||||
import com.daml.ledger.api.v1.command_service.CommandServiceGrpc.CommandService
|
||||
@ -53,7 +52,6 @@ class GrpcCommandServiceSpec
|
||||
val grpcCommandService = new GrpcCommandService(
|
||||
mockCommandService,
|
||||
ledgerId = LedgerId(ledgerId),
|
||||
errorCodesVersionSwitcher = mock[ErrorCodesVersionSwitcher],
|
||||
currentLedgerTime = () => Instant.EPOCH,
|
||||
currentUtcTime = () => Instant.EPOCH,
|
||||
maxDeduplicationTime = () => Some(Duration.ZERO),
|
||||
|
@ -5,7 +5,6 @@ package com.daml.platform.server.api.services.grpc
|
||||
|
||||
import java.time.{Duration, Instant}
|
||||
import com.codahale.metrics.MetricRegistry
|
||||
import com.daml.error.ErrorCodesVersionSwitcher
|
||||
import com.daml.ledger.api.domain.LedgerId
|
||||
import com.daml.ledger.api.messages.command.submission.SubmitRequest
|
||||
import com.daml.ledger.api.testing.utils.MockMessages._
|
||||
@ -30,7 +29,6 @@ class GrpcCommandSubmissionServiceSpec
|
||||
with Matchers
|
||||
with ArgumentMatchersSugar {
|
||||
private implicit val loggingContext: LoggingContext = LoggingContext.ForTesting
|
||||
private val errorCodesVersionSwitcher_mock = mock[ErrorCodesVersionSwitcher]
|
||||
private val generatedSubmissionId = "generated-submission-id"
|
||||
|
||||
import GrpcCommandSubmissionServiceSpec._
|
||||
@ -102,7 +100,6 @@ class GrpcCommandSubmissionServiceSpec
|
||||
maxDeduplicationTime = () => Some(Duration.ZERO),
|
||||
submissionIdGenerator = () => Ref.SubmissionId.assertFromString(generatedSubmissionId),
|
||||
metrics = new Metrics(new MetricRegistry),
|
||||
errorCodesVersionSwitcher = errorCodesVersionSwitcher_mock,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
package com.daml.platform.server.api.services.grpc
|
||||
|
||||
import com.daml.error.ErrorCodesVersionSwitcher
|
||||
import com.daml.grpc.GrpcException
|
||||
import com.daml.grpc.adapter.server.rs.MockServerCallStreamObserver
|
||||
import com.daml.ledger.api.health._
|
||||
@ -32,7 +31,7 @@ final class GrpcHealthServiceSpec
|
||||
|
||||
"HealthService" should {
|
||||
"report SERVING if there are no health checks" in {
|
||||
val service = new GrpcHealthService(new HealthChecks, errorCodesVersionSwitcherMock)
|
||||
val service = new GrpcHealthService(new HealthChecks)
|
||||
|
||||
for {
|
||||
response <- service.check(HealthCheckRequest())
|
||||
@ -43,8 +42,7 @@ final class GrpcHealthServiceSpec
|
||||
|
||||
"report SERVING if there is one healthy check" in {
|
||||
val service = new GrpcHealthService(
|
||||
new HealthChecks("component" -> healthyComponent),
|
||||
errorCodesVersionSwitcherMock,
|
||||
new HealthChecks("component" -> healthyComponent)
|
||||
)
|
||||
|
||||
for {
|
||||
@ -56,8 +54,7 @@ final class GrpcHealthServiceSpec
|
||||
|
||||
"report NOT_SERVING if there is one unhealthy check" in {
|
||||
val service = new GrpcHealthService(
|
||||
new HealthChecks("component" -> unhealthyComponent),
|
||||
errorCodesVersionSwitcherMock,
|
||||
new HealthChecks("component" -> unhealthyComponent)
|
||||
)
|
||||
|
||||
for {
|
||||
@ -73,8 +70,7 @@ final class GrpcHealthServiceSpec
|
||||
"component A" -> healthyComponent,
|
||||
"component B" -> healthyComponent,
|
||||
"component C" -> healthyComponent,
|
||||
),
|
||||
errorCodesVersionSwitcherMock,
|
||||
)
|
||||
)
|
||||
|
||||
service.check(HealthCheckRequest())
|
||||
@ -91,8 +87,7 @@ final class GrpcHealthServiceSpec
|
||||
"component A" -> healthyComponent,
|
||||
"component B" -> unhealthyComponent,
|
||||
"component C" -> healthyComponent,
|
||||
),
|
||||
errorCodesVersionSwitcherMock,
|
||||
)
|
||||
)
|
||||
|
||||
for {
|
||||
@ -104,8 +99,7 @@ final class GrpcHealthServiceSpec
|
||||
|
||||
"report SERVING when querying a single, healthy component" in {
|
||||
val service = new GrpcHealthService(
|
||||
new HealthChecks("component" -> healthyComponent),
|
||||
errorCodesVersionSwitcherMock,
|
||||
new HealthChecks("component" -> healthyComponent)
|
||||
)
|
||||
|
||||
for {
|
||||
@ -117,8 +111,7 @@ final class GrpcHealthServiceSpec
|
||||
|
||||
"report NOT_SERVING when querying a single, unhealthy component" in {
|
||||
val service = new GrpcHealthService(
|
||||
new HealthChecks("component" -> unhealthyComponent),
|
||||
errorCodesVersionSwitcherMock,
|
||||
new HealthChecks("component" -> unhealthyComponent)
|
||||
)
|
||||
|
||||
for {
|
||||
@ -134,8 +127,7 @@ final class GrpcHealthServiceSpec
|
||||
"component A" -> healthyComponent,
|
||||
"component B" -> healthyComponent,
|
||||
"component C" -> unhealthyComponent,
|
||||
),
|
||||
errorCodesVersionSwitcherMock,
|
||||
)
|
||||
)
|
||||
|
||||
for {
|
||||
@ -151,8 +143,7 @@ final class GrpcHealthServiceSpec
|
||||
"component A" -> unhealthyComponent,
|
||||
"component B" -> healthyComponent,
|
||||
"component C" -> healthyComponent,
|
||||
),
|
||||
errorCodesVersionSwitcherMock,
|
||||
)
|
||||
)
|
||||
|
||||
for {
|
||||
@ -174,7 +165,6 @@ final class GrpcHealthServiceSpec
|
||||
"component B" -> componentWithHealthBackedBy(() => componentBHealth),
|
||||
"component C" -> componentWithHealthBackedBy(() => componentCHealth),
|
||||
),
|
||||
errorCodesVersionSwitcherMock,
|
||||
maximumWatchFrequency = 1.millisecond,
|
||||
)
|
||||
|
||||
@ -240,7 +230,6 @@ final class GrpcHealthServiceSpec
|
||||
"component B" -> componentWithHealthBackedBy(() => componentBHealth),
|
||||
"component C" -> componentWithHealthBackedBy(() => componentCHealth),
|
||||
),
|
||||
errorCodesVersionSwitcherMock,
|
||||
maximumWatchFrequency = 1.millisecond,
|
||||
)
|
||||
|
||||
@ -292,64 +281,31 @@ final class GrpcHealthServiceSpec
|
||||
}
|
||||
}
|
||||
|
||||
"fail gracefully when a non-existent component is checked (returns V1 error codes)" in {
|
||||
failOnNonExistentCheckedComponent(usesSelfServiceErrorCodes = false)
|
||||
}
|
||||
|
||||
"fail gracefully when a non-existent component is checked (returns V2 error codes)" in {
|
||||
failOnNonExistentCheckedComponent(usesSelfServiceErrorCodes = true)
|
||||
}
|
||||
|
||||
"fail gracefully when a non-existent component is watched (returns V1 error codes)" in {
|
||||
failOnNonExistentWatchedComponent(usesSelfServiceErrorCodes = false)
|
||||
}
|
||||
|
||||
"fail gracefully when a non-existent component is watched (returns V2 error codes)" in {
|
||||
failOnNonExistentWatchedComponent(usesSelfServiceErrorCodes = true)
|
||||
}
|
||||
|
||||
private def failOnNonExistentCheckedComponent(usesSelfServiceErrorCodes: Boolean) = {
|
||||
"fail gracefully when a non-existent component is checked" in {
|
||||
val service = new GrpcHealthService(
|
||||
new HealthChecks("component" -> unhealthyComponent),
|
||||
new ErrorCodesVersionSwitcher(usesSelfServiceErrorCodes),
|
||||
new HealthChecks("component" -> unhealthyComponent)
|
||||
)
|
||||
|
||||
for {
|
||||
throwable <- service.check(HealthCheckRequest("another component")).failed
|
||||
} yield assertErrorCode(usesSelfServiceErrorCodes, throwable)
|
||||
service.check(HealthCheckRequest("another component")).failed.map(assertErrorCode)
|
||||
}
|
||||
|
||||
private def failOnNonExistentWatchedComponent(usesSelfServiceErrorCodes: Boolean) = {
|
||||
"fail gracefully when a non-existent component is watched" in {
|
||||
val responseObserver = new MockServerCallStreamObserver[HealthCheckResponse]
|
||||
val service = new GrpcHealthService(
|
||||
new HealthChecks("component" -> unhealthyComponent),
|
||||
new ErrorCodesVersionSwitcher(usesSelfServiceErrorCodes),
|
||||
new HealthChecks("component" -> unhealthyComponent)
|
||||
)
|
||||
|
||||
service.watch(HealthCheckRequest("another component"), responseObserver)
|
||||
responseObserver.demandResponse()
|
||||
|
||||
for {
|
||||
throwable <- responseObserver.completionFuture.failed
|
||||
} yield assertErrorCode(usesSelfServiceErrorCodes, throwable)
|
||||
responseObserver.completionFuture.failed.map(assertErrorCode)
|
||||
}
|
||||
|
||||
private def assertErrorCode(usesSelfServiceErrorCodes: Boolean, throwable: Throwable) =
|
||||
private def assertErrorCode(throwable: Throwable) =
|
||||
throwable match {
|
||||
case GrpcException.NOT_FOUND() if !usesSelfServiceErrorCodes => succeed
|
||||
case GrpcException.INVALID_ARGUMENT() if usesSelfServiceErrorCodes => succeed
|
||||
case GrpcException.INVALID_ARGUMENT() => succeed
|
||||
case ex => fail(s"Expected a NOT_FOUND error, but got $ex")
|
||||
}
|
||||
|
||||
// On the happy flow, this parameter is not used.
|
||||
// Negative tests returning errors should explicitly instantiate it
|
||||
private def errorCodesVersionSwitcherMock: ErrorCodesVersionSwitcher =
|
||||
new ErrorCodesVersionSwitcher(false) {
|
||||
override def choose[X](v1: => X, v2: => X): X = {
|
||||
val _ = (v1, v2)
|
||||
fail("Should not be called")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object GrpcHealthServiceSpec {
|
||||
|
@ -10,7 +10,7 @@ import com.daml.error.definitions.LedgerApiErrors.RequestValidation.InvalidDedup
|
||||
import com.daml.error.{ContextualizedErrorLogger, NoLogging}
|
||||
import com.daml.ledger.api.DeduplicationPeriod.DeduplicationDuration
|
||||
import com.daml.ledger.api.validation.ValidatorTestUtils
|
||||
import io.grpc.Status.Code.{FAILED_PRECONDITION, INVALID_ARGUMENT}
|
||||
import io.grpc.Status.Code.FAILED_PRECONDITION
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.prop.TableDrivenPropertyChecks
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
@ -23,28 +23,22 @@ class DeduplicationPeriodValidatorSpec
|
||||
|
||||
private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = NoLogging
|
||||
private val maxDeduplicationDuration = time.Duration.ofSeconds(5)
|
||||
private val deduplicationValidatorFixture = new ValidatorFixture(selfServiceErrorCodesEnabled =>
|
||||
new DeduplicationPeriodValidator(ErrorFactories(selfServiceErrorCodesEnabled))
|
||||
)
|
||||
private val validator = new DeduplicationPeriodValidator(ErrorFactories())
|
||||
|
||||
"not allow deduplication duration exceeding maximum deduplication duration" in {
|
||||
val durationSecondsExceedingMax = maxDeduplicationDuration.plusSeconds(1).getSeconds
|
||||
deduplicationValidatorFixture.testRequestFailure(
|
||||
_.validate(
|
||||
requestMustFailWith(
|
||||
request = validator.validate(
|
||||
DeduplicationDuration(
|
||||
Duration.ofSeconds(durationSecondsExceedingMax)
|
||||
),
|
||||
maxDeduplicationDuration,
|
||||
),
|
||||
expectedCodeV1 = INVALID_ARGUMENT,
|
||||
expectedDescriptionV1 =
|
||||
s"Invalid field deduplication_period: The given deduplication duration of ${java.time.Duration
|
||||
.ofSeconds(durationSecondsExceedingMax)} exceeds the maximum deduplication time of ${maxDeduplicationDuration}",
|
||||
expectedCodeV2 = FAILED_PRECONDITION,
|
||||
expectedDescriptionV2 =
|
||||
code = FAILED_PRECONDITION,
|
||||
description =
|
||||
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 ${maxDeduplicationDuration}",
|
||||
metadataV2 = Map(
|
||||
metadata = Map(
|
||||
ValidMaxDeduplicationFieldKey -> maxDeduplicationDuration.toString
|
||||
),
|
||||
)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -64,18 +64,16 @@ object Assertions {
|
||||
)
|
||||
}
|
||||
|
||||
//TODO error codes daml 2.0: remove this two-level function
|
||||
/** Asserts GRPC error codes depending on the self-service error codes feature in the Ledger API. */
|
||||
def assertGrpcError(
|
||||
t: Throwable,
|
||||
selfServiceErrorCode: ErrorCode,
|
||||
errorCode: ErrorCode,
|
||||
exceptionMessageSubstring: Option[String],
|
||||
checkDefiniteAnswerMetadata: Boolean = false,
|
||||
additionalErrorAssertions: Throwable => Unit = _ => (),
|
||||
): Unit =
|
||||
assertGrpcErrorRegex(
|
||||
t,
|
||||
selfServiceErrorCode,
|
||||
errorCode,
|
||||
exceptionMessageSubstring
|
||||
.map(msgSubstring => Pattern.compile(Pattern.quote(msgSubstring))),
|
||||
checkDefiniteAnswerMetadata,
|
||||
@ -90,7 +88,7 @@ object Assertions {
|
||||
@tailrec
|
||||
def assertGrpcErrorRegex(
|
||||
t: Throwable,
|
||||
selfServiceErrorCode: ErrorCode, //TODO error codes daml 2.0: rename to errorCode
|
||||
errorCode: ErrorCode,
|
||||
optPattern: Option[Pattern],
|
||||
checkDefiniteAnswerMetadata: Boolean = false,
|
||||
additionalErrorAssertions: Throwable => Unit = _ => (),
|
||||
@ -99,14 +97,14 @@ object Assertions {
|
||||
case RetryStrategy.FailedRetryException(cause) =>
|
||||
assertGrpcErrorRegex(
|
||||
cause,
|
||||
selfServiceErrorCode,
|
||||
errorCode,
|
||||
optPattern,
|
||||
checkDefiniteAnswerMetadata,
|
||||
additionalErrorAssertions,
|
||||
)
|
||||
case exception: StatusRuntimeException =>
|
||||
optPattern.foreach(assertMatches(exception.getMessage, _))
|
||||
assertSelfServiceErrorCode(exception, selfServiceErrorCode)
|
||||
assertErrorCode(exception, errorCode)
|
||||
if (checkDefiniteAnswerMetadata) assertDefiniteAnswer(exception)
|
||||
additionalErrorAssertions(exception)
|
||||
case _ =>
|
||||
@ -156,8 +154,7 @@ object Assertions {
|
||||
}
|
||||
}
|
||||
|
||||
//TODO error codes daml 2.0: rename this
|
||||
def assertSelfServiceErrorCode(
|
||||
def assertErrorCode(
|
||||
statusRuntimeException: StatusRuntimeException,
|
||||
expectedErrorCode: ErrorCode,
|
||||
): Unit = {
|
||||
|
@ -669,17 +669,17 @@ final class CommandDeduplicationIT(
|
||||
private def submitRequestAndAssertSyncFailure(
|
||||
ledger: ParticipantTestContext,
|
||||
request: SubmitRequest,
|
||||
code: Code,
|
||||
selfServiceErrorCode: ErrorCode,
|
||||
grpcCode: Code,
|
||||
errorCode: ErrorCode,
|
||||
additionalErrorAssertions: Throwable => Unit = _ => (),
|
||||
)(implicit ec: ExecutionContext): Future[Unit] =
|
||||
ledger
|
||||
.submit(request)
|
||||
.mustFail(s"Request expected to fail with code $code")
|
||||
.mustFail(s"Request expected to fail with code $grpcCode")
|
||||
.map(
|
||||
assertGrpcError(
|
||||
_,
|
||||
selfServiceErrorCode,
|
||||
errorCode,
|
||||
exceptionMessageSubstring = None,
|
||||
checkDefiniteAnswerMetadata = true,
|
||||
additionalErrorAssertions,
|
||||
@ -698,7 +698,7 @@ final class CommandDeduplicationIT(
|
||||
.map(
|
||||
assertGrpcError(
|
||||
_,
|
||||
selfServiceErrorCode = LedgerApiErrors.ConsistencyErrors.DuplicateCommand,
|
||||
errorCode = LedgerApiErrors.ConsistencyErrors.DuplicateCommand,
|
||||
exceptionMessageSubstring = None,
|
||||
checkDefiniteAnswerMetadata = true,
|
||||
assertDeduplicatedSubmissionIdAndOffsetOnError(
|
||||
|
@ -200,7 +200,7 @@ class CommandDeduplicationPeriodValidationIT extends LedgerTestSuite {
|
||||
failure,
|
||||
// Canton returns INVALID_DEDUPLICATION_PERIOD with earliest_offset metadata
|
||||
// KV returns PARTICIPANT_PRUNED_DATA_ACCESSED with earliest_offset metadata
|
||||
selfServiceErrorCode =
|
||||
errorCode =
|
||||
if (isOffsetNativelySupported)
|
||||
LedgerApiErrors.RequestValidation.InvalidDeduplicationPeriodField
|
||||
else LedgerApiErrors.RequestValidation.ParticipantPrunedDataAccessed,
|
||||
|
@ -9,7 +9,7 @@ import com.daml.ledger.api.refinements.ApiTypes.Party
|
||||
import com.daml.ledger.api.testtool.infrastructure.Allocation._
|
||||
import com.daml.ledger.api.testtool.infrastructure.Assertions.{
|
||||
assertGrpcError,
|
||||
assertSelfServiceErrorCode,
|
||||
assertErrorCode,
|
||||
fail,
|
||||
}
|
||||
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
|
||||
@ -128,7 +128,7 @@ final class ContractIdIT extends LedgerTestSuite {
|
||||
} yield result match {
|
||||
case Failure(exception: StatusRuntimeException)
|
||||
if Try(
|
||||
assertSelfServiceErrorCode(
|
||||
assertErrorCode(
|
||||
statusRuntimeException = exception,
|
||||
expectedErrorCode = LedgerApiErrors.ConsistencyErrors.ContractNotFound,
|
||||
)
|
||||
|
@ -58,7 +58,7 @@ final class UserManagementServiceIT extends LedgerTestSuite {
|
||||
def assertTooManyUserRightsError(t: Throwable): Unit = {
|
||||
assertGrpcError(
|
||||
t = t,
|
||||
selfServiceErrorCode = LedgerApiErrors.AdminServices.TooManyUserRights,
|
||||
errorCode = LedgerApiErrors.AdminServices.TooManyUserRights,
|
||||
exceptionMessageSubstring = None,
|
||||
)
|
||||
}
|
||||
@ -128,7 +128,7 @@ final class UserManagementServiceIT extends LedgerTestSuite {
|
||||
.mustFail(context = problem)
|
||||
} yield assertGrpcError(
|
||||
t = throwable,
|
||||
selfServiceErrorCode = expectedErrorCode,
|
||||
errorCode = expectedErrorCode,
|
||||
exceptionMessageSubstring = None,
|
||||
)
|
||||
}
|
||||
@ -358,12 +358,12 @@ final class UserManagementServiceIT extends LedgerTestSuite {
|
||||
|
||||
assertGrpcError(
|
||||
t = onBadTokenError,
|
||||
selfServiceErrorCode = LedgerApiErrors.RequestValidation.InvalidArgument,
|
||||
errorCode = LedgerApiErrors.RequestValidation.InvalidArgument,
|
||||
exceptionMessageSubstring = None,
|
||||
)
|
||||
assertGrpcError(
|
||||
t = onNegativePageSizeError,
|
||||
selfServiceErrorCode = LedgerApiErrors.RequestValidation.InvalidArgument,
|
||||
errorCode = LedgerApiErrors.RequestValidation.InvalidArgument,
|
||||
exceptionMessageSubstring = None,
|
||||
)
|
||||
assert(
|
||||
@ -525,7 +525,7 @@ final class UserManagementServiceIT extends LedgerTestSuite {
|
||||
private def assertUserNotFound(t: Throwable): Unit = {
|
||||
assertGrpcError(
|
||||
t = t,
|
||||
selfServiceErrorCode = LedgerApiErrors.AdminServices.UserNotFound,
|
||||
errorCode = LedgerApiErrors.AdminServices.UserNotFound,
|
||||
exceptionMessageSubstring = None,
|
||||
)
|
||||
}
|
||||
@ -535,7 +535,7 @@ final class UserManagementServiceIT extends LedgerTestSuite {
|
||||
): Unit = {
|
||||
assertGrpcError(
|
||||
t = t,
|
||||
selfServiceErrorCode = LedgerApiErrors.AdminServices.UserAlreadyExists,
|
||||
errorCode = LedgerApiErrors.AdminServices.UserAlreadyExists,
|
||||
exceptionMessageSubstring = None,
|
||||
)
|
||||
}
|
||||
|
@ -55,6 +55,6 @@ private[memory] class InMemoryLedgerFactory(dispatcher: Dispatcher[Index], state
|
||||
state = state,
|
||||
engine = engine,
|
||||
committerExecutionContext = materializer.executionContext,
|
||||
).map(writer => new KeyValueReadWriteFactory(config, metrics, reader, writer))
|
||||
).map(writer => new KeyValueReadWriteFactory(metrics, reader, writer))
|
||||
}
|
||||
}
|
||||
|
@ -68,7 +68,6 @@ class InMemoryLedgerReaderWriterIntegrationSpec
|
||||
KeyValueParticipantStateReader(
|
||||
reader = reader,
|
||||
metrics = metrics,
|
||||
enableSelfServiceErrorCodes = true,
|
||||
),
|
||||
new KeyValueParticipantStateWriter(
|
||||
writer = writer,
|
||||
|
@ -50,7 +50,7 @@ object SqlLedgerFactory extends LedgerFactory[ExtraConfig] {
|
||||
metrics = metrics.daml.kvutils.submission.validator.stateValueCache,
|
||||
),
|
||||
).map(ledgerReaderWriter =>
|
||||
new KeyValueReadWriteFactory(config, metrics, ledgerReaderWriter, ledgerReaderWriter)
|
||||
new KeyValueReadWriteFactory(metrics, ledgerReaderWriter, ledgerReaderWriter)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,6 @@ abstract class SqlLedgerReaderWriterIntegrationSpecBase(implementationName: Stri
|
||||
val reader = KeyValueParticipantStateReader(
|
||||
reader = readerWriter,
|
||||
metrics = metrics,
|
||||
enableSelfServiceErrorCodes = true,
|
||||
)
|
||||
val writer = new KeyValueParticipantStateWriter(
|
||||
readerWriter,
|
||||
|
@ -40,7 +40,6 @@ final case class Config[Extra](
|
||||
configurationLoadTimeout: Duration,
|
||||
commandConfig: CommandConfiguration,
|
||||
enableInMemoryFanOutForLedgerApi: Boolean,
|
||||
enableSelfServiceErrorCodes: Boolean,
|
||||
eventsPageSize: Int,
|
||||
eventsProcessingParallelism: Int,
|
||||
extra: Extra,
|
||||
@ -87,7 +86,6 @@ object Config {
|
||||
configurationLoadTimeout = Duration.ofSeconds(10),
|
||||
commandConfig = CommandConfiguration.default,
|
||||
enableInMemoryFanOutForLedgerApi = false,
|
||||
enableSelfServiceErrorCodes = true,
|
||||
eventsPageSize = IndexConfiguration.DefaultEventsPageSize,
|
||||
eventsProcessingParallelism = IndexConfiguration.DefaultEventsProcessingParallelism,
|
||||
extra = extra,
|
||||
|
@ -82,7 +82,6 @@ trait ConfigProvider[ExtraConfig] {
|
||||
maxTransactionsInMemoryFanOutBufferSize =
|
||||
participantConfig.maxTransactionsInMemoryFanOutBufferSize,
|
||||
enableInMemoryFanOutForLedgerApi = config.enableInMemoryFanOutForLedgerApi,
|
||||
enableSelfServiceErrorCodes = config.enableSelfServiceErrorCodes,
|
||||
userManagementConfig = config.userManagementConfig,
|
||||
)
|
||||
|
||||
|
@ -66,17 +66,6 @@ final class ConfigSpec
|
||||
)
|
||||
behavior of "Runner"
|
||||
|
||||
it should "enable self-service error codes by default" in {
|
||||
val actual = configParser(
|
||||
Seq(
|
||||
dumpIndexMetadataCommand,
|
||||
"some-jdbc-url",
|
||||
)
|
||||
)
|
||||
|
||||
actual.value.enableSelfServiceErrorCodes shouldBe true
|
||||
}
|
||||
|
||||
it should "succeed when server's private key is encrypted and secret-url is provided" in {
|
||||
val actual = configParser(
|
||||
Seq(
|
||||
|
@ -176,7 +176,6 @@ da_scala_library(
|
||||
"//ledger-api/rs-grpc-bridge",
|
||||
"//ledger-api/sample-service",
|
||||
"//ledger-api/testing-utils",
|
||||
"//ledger/error",
|
||||
"//ledger/ledger-api-client",
|
||||
"//ledger/ledger-api-common",
|
||||
"//ledger/ledger-api-domain",
|
||||
|
@ -42,6 +42,5 @@ case class ApiServerConfig(
|
||||
maxContractKeyStateCacheSize: Long,
|
||||
maxTransactionsInMemoryFanOutBufferSize: Long,
|
||||
enableInMemoryFanOutForLedgerApi: Boolean,
|
||||
enableSelfServiceErrorCodes: Boolean,
|
||||
userManagementConfig: UserManagementConfig,
|
||||
)
|
||||
|
@ -5,7 +5,6 @@ package com.daml.platform.apiserver
|
||||
|
||||
import akka.stream.Materializer
|
||||
import com.daml.api.util.TimeProvider
|
||||
import com.daml.error.ErrorCodesVersionSwitcher
|
||||
import com.daml.grpc.adapter.ExecutionSequencerFactory
|
||||
import com.daml.ledger.api.auth.Authorizer
|
||||
import com.daml.ledger.api.auth.services._
|
||||
@ -86,7 +85,6 @@ private[daml] object ApiServices {
|
||||
healthChecks: HealthChecks,
|
||||
seedService: SeedService,
|
||||
managementServiceTimeout: Duration,
|
||||
enableSelfServiceErrorCodes: Boolean,
|
||||
checkOverloaded: TelemetryContext => Option[state.SubmissionResult],
|
||||
ledgerFeatures: LedgerFeatures,
|
||||
userManagementConfig: UserManagementConfig,
|
||||
@ -114,9 +112,6 @@ private[daml] object ApiServices {
|
||||
servicesExecutionContext = servicesExecutionContext,
|
||||
)
|
||||
|
||||
private val errorsVersionsSwitcher =
|
||||
new ErrorCodesVersionSwitcher(enableSelfServiceErrorCodes = enableSelfServiceErrorCodes)
|
||||
|
||||
override def acquire()(implicit context: ResourceContext): Resource[ApiServices] = {
|
||||
logger.info(engine.info.toString)
|
||||
for {
|
||||
@ -148,30 +143,28 @@ private[daml] object ApiServices {
|
||||
checkOverloaded: TelemetryContext => Option[state.SubmissionResult],
|
||||
)(implicit executionContext: ExecutionContext): List[BindableService] = {
|
||||
val apiTransactionService =
|
||||
ApiTransactionService.create(ledgerId, transactionsService, metrics, errorsVersionsSwitcher)
|
||||
ApiTransactionService.create(ledgerId, transactionsService, metrics)
|
||||
|
||||
val apiLedgerIdentityService =
|
||||
ApiLedgerIdentityService.create(() => identityService.getLedgerId(), errorsVersionsSwitcher)
|
||||
ApiLedgerIdentityService.create(() => identityService.getLedgerId())
|
||||
|
||||
val apiVersionService =
|
||||
ApiVersionService.create(
|
||||
enableSelfServiceErrorCodes,
|
||||
ledgerFeatures,
|
||||
userManagementConfig = userManagementConfig,
|
||||
)
|
||||
|
||||
val apiPackageService =
|
||||
ApiPackageService.create(ledgerId, packagesService, errorsVersionsSwitcher)
|
||||
ApiPackageService.create(ledgerId, packagesService)
|
||||
|
||||
val apiConfigurationService =
|
||||
ApiLedgerConfigurationService.create(ledgerId, configurationService, errorsVersionsSwitcher)
|
||||
ApiLedgerConfigurationService.create(ledgerId, configurationService)
|
||||
|
||||
val (completionService, grpcCompletionService) =
|
||||
ApiCommandCompletionService.create(
|
||||
ledgerId,
|
||||
completionsService,
|
||||
metrics,
|
||||
errorsVersionsSwitcher,
|
||||
)
|
||||
|
||||
val apiActiveContractsService =
|
||||
@ -179,13 +172,12 @@ private[daml] object ApiServices {
|
||||
ledgerId,
|
||||
activeContractsService,
|
||||
metrics,
|
||||
errorsVersionsSwitcher,
|
||||
)
|
||||
|
||||
val apiTimeServiceOpt =
|
||||
optTimeServiceBackend.map(tsb =>
|
||||
new TimeServiceAuthorization(
|
||||
ApiTimeService.create(ledgerId, tsb, errorsVersionsSwitcher),
|
||||
ApiTimeService.create(ledgerId, tsb),
|
||||
authorizer,
|
||||
)
|
||||
)
|
||||
@ -200,14 +192,13 @@ private[daml] object ApiServices {
|
||||
|
||||
val apiReflectionService = ProtoReflectionService.newInstance()
|
||||
|
||||
val apiHealthService = new GrpcHealthService(healthChecks, errorsVersionsSwitcher)
|
||||
val apiHealthService = new GrpcHealthService(healthChecks)
|
||||
|
||||
val maybeApiUserManagementService: Option[UserManagementServiceAuthorization] =
|
||||
if (userManagementConfig.enabled) {
|
||||
val apiUserManagementService =
|
||||
new ApiUserManagementService(
|
||||
userManagementStore,
|
||||
errorsVersionsSwitcher,
|
||||
maxUsersPageSize = userManagementConfig.maxUsersPageSize,
|
||||
)
|
||||
val authorized =
|
||||
@ -218,7 +209,7 @@ private[daml] object ApiServices {
|
||||
}
|
||||
|
||||
val apiMeteringReportService =
|
||||
new ApiMeteringReportService(participantId, meteringStore, errorsVersionsSwitcher)
|
||||
new ApiMeteringReportService(participantId, meteringStore)
|
||||
|
||||
apiTimeServiceOpt.toList :::
|
||||
writeServiceBackedApiServices :::
|
||||
@ -274,7 +265,6 @@ private[daml] object ApiServices {
|
||||
partyConfig.implicitPartyAllocation
|
||||
),
|
||||
metrics,
|
||||
errorsVersionsSwitcher,
|
||||
)
|
||||
|
||||
// Note: the command service uses the command submission, command completion, and transaction
|
||||
@ -298,7 +288,6 @@ private[daml] object ApiServices {
|
||||
timeProvider = timeProvider,
|
||||
ledgerConfigurationSubscription = ledgerConfigurationSubscription,
|
||||
metrics = metrics,
|
||||
errorsVersionsSwitcher,
|
||||
)
|
||||
|
||||
val apiPartyManagementService = ApiPartyManagementService.createApiService(
|
||||
@ -306,7 +295,6 @@ private[daml] object ApiServices {
|
||||
transactionsService,
|
||||
writeService,
|
||||
managementServiceTimeout,
|
||||
errorsVersionsSwitcher,
|
||||
)
|
||||
|
||||
val apiPackageManagementService = ApiPackageManagementService.createApiService(
|
||||
@ -315,21 +303,18 @@ private[daml] object ApiServices {
|
||||
writeService,
|
||||
managementServiceTimeout,
|
||||
engine,
|
||||
errorsVersionsSwitcher,
|
||||
)
|
||||
|
||||
val apiConfigManagementService = ApiConfigManagementService.createApiService(
|
||||
configManagementService,
|
||||
writeService,
|
||||
timeProvider,
|
||||
errorsVersionsSwitcher,
|
||||
)
|
||||
|
||||
val apiParticipantPruningService =
|
||||
ApiParticipantPruningService.createApiService(
|
||||
indexService,
|
||||
writeService,
|
||||
errorsVersionsSwitcher,
|
||||
)
|
||||
|
||||
List(
|
||||
|
@ -9,7 +9,6 @@ import akka.actor.ActorSystem
|
||||
import akka.stream.Materializer
|
||||
import com.daml.api.util.TimeProvider
|
||||
import com.daml.buildinfo.BuildInfo
|
||||
import com.daml.error.ErrorCodesVersionSwitcher
|
||||
import com.daml.ledger.api.auth.interceptor.AuthorizationInterceptor
|
||||
import com.daml.ledger.api.auth.{AuthService, Authorizer}
|
||||
import com.daml.ledger.api.health.HealthChecks
|
||||
@ -75,14 +74,10 @@ object StandaloneApiServer {
|
||||
}
|
||||
}
|
||||
|
||||
val errorCodesVersionSwitcher = new ErrorCodesVersionSwitcher(
|
||||
config.enableSelfServiceErrorCodes
|
||||
)
|
||||
val authorizer = new Authorizer(
|
||||
Clock.systemUTC.instant _,
|
||||
ledgerId,
|
||||
participantId,
|
||||
errorCodesVersionSwitcher,
|
||||
userManagementStore,
|
||||
servicesExecutionContext,
|
||||
userRightsCheckIntervalInSeconds = userManagementConfig.cacheExpiryAfterWriteInSeconds,
|
||||
@ -113,7 +108,6 @@ object StandaloneApiServer {
|
||||
healthChecks = healthChecksWithIndexService,
|
||||
seedService = SeedService(config.seeding),
|
||||
managementServiceTimeout = config.managementServiceTimeout,
|
||||
enableSelfServiceErrorCodes = config.enableSelfServiceErrorCodes,
|
||||
checkOverloaded = checkOverloaded,
|
||||
userManagementStore = userManagementStore,
|
||||
ledgerFeatures = ledgerFeatures,
|
||||
@ -130,7 +124,6 @@ object StandaloneApiServer {
|
||||
authService,
|
||||
Option.when(config.userManagementConfig.enabled)(userManagementStore),
|
||||
servicesExecutionContext,
|
||||
errorCodesVersionSwitcher,
|
||||
) :: otherInterceptors,
|
||||
servicesExecutionContext,
|
||||
metrics,
|
||||
|
@ -92,7 +92,6 @@ object StandaloneIndexService {
|
||||
maxContractKeyStateCacheSize = config.maxContractKeyStateCacheSize,
|
||||
maxTransactionsInMemoryFanOutBufferSize = config.maxTransactionsInMemoryFanOutBufferSize,
|
||||
enableInMemoryFanOutForLedgerApi = config.enableInMemoryFanOutForLedgerApi,
|
||||
enableSelfServiceErrorCodes = config.enableSelfServiceErrorCodes,
|
||||
)
|
||||
.map(index => new SpannedIndexService(new TimedIndexService(index, metrics)))
|
||||
} yield indexService
|
||||
|
@ -6,11 +6,7 @@ package com.daml.platform.apiserver.services
|
||||
import akka.NotUsed
|
||||
import akka.stream.Materializer
|
||||
import akka.stream.scaladsl.Source
|
||||
import com.daml.error.{
|
||||
ContextualizedErrorLogger,
|
||||
DamlContextualizedErrorLogger,
|
||||
ErrorCodesVersionSwitcher,
|
||||
}
|
||||
import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger}
|
||||
import com.daml.grpc.adapter.ExecutionSequencerFactory
|
||||
import com.daml.ledger.api.domain.LedgerId
|
||||
import com.daml.ledger.api.v1.active_contracts_service.ActiveContractsServiceGrpc.ActiveContractsService
|
||||
@ -73,15 +69,14 @@ private[apiserver] object ApiActiveContractsService {
|
||||
ledgerId: LedgerId,
|
||||
backend: ACSBackend,
|
||||
metrics: Metrics,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
)(implicit
|
||||
mat: Materializer,
|
||||
esf: ExecutionSequencerFactory,
|
||||
executionContext: ExecutionContext,
|
||||
loggingContext: LoggingContext,
|
||||
): ActiveContractsService with GrpcApiService = {
|
||||
val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
val field = FieldValidations(ErrorFactories(errorCodesVersionSwitcher))
|
||||
val errorFactories = ErrorFactories()
|
||||
val field = FieldValidations(ErrorFactories())
|
||||
val service = new ApiActiveContractsService(
|
||||
backend = backend,
|
||||
metrics = metrics,
|
||||
|
@ -8,7 +8,7 @@ import java.util.concurrent.atomic.AtomicLong
|
||||
import akka.NotUsed
|
||||
import akka.stream.Materializer
|
||||
import akka.stream.scaladsl.Source
|
||||
import com.daml.error.{DamlContextualizedErrorLogger, ErrorCodesVersionSwitcher}
|
||||
import com.daml.error.DamlContextualizedErrorLogger
|
||||
import com.daml.grpc.adapter.ExecutionSequencerFactory
|
||||
import com.daml.ledger.api.domain.{LedgerId, LedgerOffset}
|
||||
import com.daml.ledger.api.messages.command.completion.CompletionStreamRequest
|
||||
@ -89,7 +89,6 @@ private[apiserver] object ApiCommandCompletionService {
|
||||
ledgerId: LedgerId,
|
||||
completionsService: IndexCompletionsService,
|
||||
metrics: Metrics,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
)(implicit
|
||||
materializer: Materializer,
|
||||
esf: ExecutionSequencerFactory,
|
||||
@ -99,7 +98,6 @@ private[apiserver] object ApiCommandCompletionService {
|
||||
val validator = new CompletionServiceRequestValidator(
|
||||
ledgerId,
|
||||
PartyNameChecker.AllowAllParties,
|
||||
errorCodesVersionSwitcher,
|
||||
)
|
||||
val impl: CommandCompletionService =
|
||||
new ApiCommandCompletionService(completionsService, validator, metrics)
|
||||
|
@ -10,11 +10,7 @@ import akka.NotUsed
|
||||
import akka.stream.Materializer
|
||||
import akka.stream.scaladsl.{Flow, Keep, Source}
|
||||
import com.daml.api.util.TimeProvider
|
||||
import com.daml.error.{
|
||||
ContextualizedErrorLogger,
|
||||
DamlContextualizedErrorLogger,
|
||||
ErrorCodesVersionSwitcher,
|
||||
}
|
||||
import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger}
|
||||
import com.daml.ledger.api.SubmissionIdGenerator
|
||||
import com.daml.ledger.api.domain.LedgerId
|
||||
import com.daml.ledger.api.messages.command.completion.CompletionStreamRequest
|
||||
@ -61,7 +57,6 @@ import scala.util.Try
|
||||
private[apiserver] final class ApiCommandService private[services] (
|
||||
transactionServices: TransactionServices,
|
||||
submissionTracker: Tracker,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
)(implicit
|
||||
executionContext: ExecutionContext,
|
||||
loggingContext: LoggingContext,
|
||||
@ -70,7 +65,7 @@ private[apiserver] final class ApiCommandService private[services] (
|
||||
|
||||
private val logger = ContextualizedLogger.get(this.getClass)
|
||||
|
||||
private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
private val errorFactories = ErrorFactories()
|
||||
|
||||
@volatile private var running = true
|
||||
|
||||
@ -169,7 +164,7 @@ private[apiserver] final class ApiCommandService private[services] (
|
||||
} else {
|
||||
Future
|
||||
.failed(
|
||||
errorFactories.serviceNotRunning("Command Service")(definiteAnswer = Some(false))
|
||||
errorFactories.serviceNotRunning("Command Service")
|
||||
)
|
||||
}
|
||||
|
||||
@ -215,13 +210,12 @@ private[apiserver] object ApiCommandService {
|
||||
timeProvider: TimeProvider,
|
||||
ledgerConfigurationSubscription: LedgerConfigurationSubscription,
|
||||
metrics: Metrics,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
)(implicit
|
||||
materializer: Materializer,
|
||||
executionContext: ExecutionContext,
|
||||
loggingContext: LoggingContext,
|
||||
): CommandServiceGrpc.CommandService with GrpcApiService = {
|
||||
val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
val errorFactories = ErrorFactories()
|
||||
val ledgerOffsetValidator = new LedgerOffsetValidator(errorFactories)
|
||||
val submissionTracker = new TrackerMap.SelfCleaning(
|
||||
configuration.trackerRetentionPeriod,
|
||||
@ -237,10 +231,8 @@ private[apiserver] object ApiCommandService {
|
||||
trackerCleanupInterval,
|
||||
)
|
||||
new GrpcCommandService(
|
||||
service =
|
||||
new ApiCommandService(transactionServices, submissionTracker, errorCodesVersionSwitcher),
|
||||
service = new ApiCommandService(transactionServices, submissionTracker),
|
||||
ledgerId = configuration.ledgerId,
|
||||
errorCodesVersionSwitcher = errorCodesVersionSwitcher,
|
||||
currentLedgerTime = () => timeProvider.getCurrentTime,
|
||||
currentUtcTime = () => Instant.now,
|
||||
maxDeduplicationTime = () =>
|
||||
|
@ -7,7 +7,6 @@ import akka.NotUsed
|
||||
import akka.stream.Materializer
|
||||
import akka.stream.scaladsl.Source
|
||||
import com.daml.api.util.DurationConversion._
|
||||
import com.daml.error.ErrorCodesVersionSwitcher
|
||||
import com.daml.grpc.adapter.ExecutionSequencerFactory
|
||||
import com.daml.ledger.api.domain.LedgerId
|
||||
import com.daml.ledger.api.v1.ledger_configuration_service._
|
||||
@ -61,14 +60,13 @@ private[apiserver] object ApiLedgerConfigurationService {
|
||||
def create(
|
||||
ledgerId: LedgerId,
|
||||
configurationService: IndexConfigurationService,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
)(implicit
|
||||
esf: ExecutionSequencerFactory,
|
||||
materializer: Materializer,
|
||||
executionContext: ExecutionContext,
|
||||
loggingContext: LoggingContext,
|
||||
): LedgerConfigurationServiceGrpc.LedgerConfigurationService with GrpcApiService = {
|
||||
val fieldValidations = FieldValidations(ErrorFactories(errorCodesVersionSwitcher))
|
||||
val fieldValidations = FieldValidations(ErrorFactories())
|
||||
new LedgerConfigurationServiceValidation(
|
||||
service = new ApiLedgerConfigurationService(configurationService),
|
||||
ledgerId = ledgerId,
|
||||
|
@ -3,11 +3,7 @@
|
||||
|
||||
package com.daml.platform.apiserver.services
|
||||
|
||||
import com.daml.error.{
|
||||
ContextualizedErrorLogger,
|
||||
DamlContextualizedErrorLogger,
|
||||
ErrorCodesVersionSwitcher,
|
||||
}
|
||||
import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger}
|
||||
import com.daml.ledger.api.domain.LedgerId
|
||||
import com.daml.ledger.api.v1.ledger_identity_service.LedgerIdentityServiceGrpc.{
|
||||
LedgerIdentityService => GrpcLedgerIdentityService
|
||||
@ -27,8 +23,7 @@ import scala.annotation.nowarn
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
private[apiserver] final class ApiLedgerIdentityService private (
|
||||
getLedgerId: () => Future[LedgerId],
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
getLedgerId: () => Future[LedgerId]
|
||||
)(implicit executionContext: ExecutionContext, loggingContext: LoggingContext)
|
||||
extends GrpcLedgerIdentityService
|
||||
with GrpcApiService {
|
||||
@ -36,7 +31,7 @@ private[apiserver] final class ApiLedgerIdentityService private (
|
||||
private implicit val contextualizedErrorLogger: ContextualizedErrorLogger =
|
||||
new DamlContextualizedErrorLogger(logger, loggingContext, None)
|
||||
|
||||
private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
private val errorFactories = ErrorFactories()
|
||||
|
||||
@volatile var closed = false
|
||||
|
||||
@ -46,7 +41,7 @@ private[apiserver] final class ApiLedgerIdentityService private (
|
||||
): Future[GetLedgerIdentityResponse] = {
|
||||
logger.info(s"Received request for ledger identity: $request")
|
||||
if (closed)
|
||||
Future.failed(errorFactories.serviceNotRunning("Ledger Identity Service")(None))
|
||||
Future.failed(errorFactories.serviceNotRunning("Ledger Identity Service"))
|
||||
else
|
||||
getLedgerId()
|
||||
.map(ledgerId => GetLedgerIdentityResponse(ledgerId.unwrap))
|
||||
@ -63,12 +58,11 @@ private[apiserver] final class ApiLedgerIdentityService private (
|
||||
|
||||
private[apiserver] object ApiLedgerIdentityService {
|
||||
def create(
|
||||
getLedgerId: () => Future[LedgerId],
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
getLedgerId: () => Future[LedgerId]
|
||||
)(implicit
|
||||
executionContext: ExecutionContext,
|
||||
loggingContext: LoggingContext,
|
||||
): ApiLedgerIdentityService with BindableService = {
|
||||
new ApiLedgerIdentityService(getLedgerId, errorCodesVersionSwitcher)
|
||||
new ApiLedgerIdentityService(getLedgerId)
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
package com.daml.platform.apiserver.services
|
||||
|
||||
import com.daml.daml_lf_dev.DamlLf.{Archive, HashFunction}
|
||||
import com.daml.error.{DamlContextualizedErrorLogger, ErrorCodesVersionSwitcher}
|
||||
import com.daml.error.DamlContextualizedErrorLogger
|
||||
import com.daml.ledger.api.domain.LedgerId
|
||||
import com.daml.ledger.api.v1.package_service.PackageServiceGrpc.PackageService
|
||||
import com.daml.ledger.api.v1.package_service.{HashFunction => APIHashFunction, _}
|
||||
@ -24,15 +24,14 @@ import io.grpc.{BindableService, ServerServiceDefinition}
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
private[apiserver] final class ApiPackageService private (
|
||||
backend: IndexPackagesService,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
backend: IndexPackagesService
|
||||
)(implicit executionContext: ExecutionContext, loggingContext: LoggingContext)
|
||||
extends PackageService
|
||||
with GrpcApiService {
|
||||
|
||||
private implicit val logger: ContextualizedLogger = ContextualizedLogger.get(this.getClass)
|
||||
|
||||
private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
private val errorFactories = ErrorFactories()
|
||||
|
||||
override def bindService(): ServerServiceDefinition =
|
||||
PackageServiceGrpc.bindService(this, executionContext)
|
||||
@ -97,7 +96,7 @@ private[apiserver] final class ApiPackageService private (
|
||||
ValidationLogger.logFailure(
|
||||
request,
|
||||
errorFactories
|
||||
.invalidArgument(Some(true))(s"Invalid package id: $errorMessage")(
|
||||
.invalidArgument(s"Invalid package id: $errorMessage")(
|
||||
createContextualizedErrorLogger
|
||||
),
|
||||
)
|
||||
@ -127,16 +126,14 @@ private[platform] object ApiPackageService {
|
||||
def create(
|
||||
ledgerId: LedgerId,
|
||||
backend: IndexPackagesService,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
)(implicit
|
||||
executionContext: ExecutionContext,
|
||||
loggingContext: LoggingContext,
|
||||
): PackageService with GrpcApiService = {
|
||||
val service = new ApiPackageService(
|
||||
backend = backend,
|
||||
errorCodesVersionSwitcher = errorCodesVersionSwitcher,
|
||||
backend = backend
|
||||
)
|
||||
val fieldValidations = FieldValidations(ErrorFactories(errorCodesVersionSwitcher))
|
||||
val fieldValidations = FieldValidations(ErrorFactories())
|
||||
new PackageServiceValidation(
|
||||
service = service,
|
||||
ledgerId = ledgerId,
|
||||
|
@ -9,12 +9,7 @@ import java.util.UUID
|
||||
import com.daml.api.util.TimeProvider
|
||||
import com.daml.error.ErrorCode.LoggingApiException
|
||||
import com.daml.error.definitions.{ErrorCauseExport, RejectionGenerators}
|
||||
import com.daml.error.{
|
||||
ContextualizedErrorLogger,
|
||||
DamlContextualizedErrorLogger,
|
||||
ErrorCause,
|
||||
ErrorCodesVersionSwitcher,
|
||||
}
|
||||
import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger, ErrorCause}
|
||||
import com.daml.ledger.api.domain.{LedgerId, SubmissionId, Commands => ApiCommands}
|
||||
import com.daml.ledger.api.messages.command.submission.SubmitRequest
|
||||
import com.daml.ledger.api.SubmissionIdGenerator
|
||||
@ -23,8 +18,6 @@ import com.daml.ledger.participant.state.index.v2._
|
||||
import com.daml.ledger.participant.state.{v2 => state}
|
||||
import com.daml.lf.crypto
|
||||
import com.daml.lf.data.Ref
|
||||
import com.daml.lf.engine.{Error => LfError}
|
||||
import com.daml.lf.interpretation.{Error => InterpretationError}
|
||||
import com.daml.lf.transaction.SubmittedTransaction
|
||||
import com.daml.logging.LoggingContext.withEnrichedLoggingContext
|
||||
import com.daml.logging.{ContextualizedLogger, LoggingContext}
|
||||
@ -40,9 +33,7 @@ import com.daml.platform.services.time.TimeProviderType
|
||||
import com.daml.scalautil.future.FutureConversion.CompletionStageConversionOps
|
||||
import com.daml.telemetry.TelemetryContext
|
||||
import com.daml.timer.Delayed
|
||||
import io.grpc.{Status, StatusRuntimeException}
|
||||
|
||||
import scala.annotation.nowarn
|
||||
import scala.jdk.FutureConverters.CompletionStageOps
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
import scala.util.{Failure, Success, Try}
|
||||
@ -61,7 +52,6 @@ private[apiserver] object ApiSubmissionService {
|
||||
checkOverloaded: TelemetryContext => Option[state.SubmissionResult],
|
||||
configuration: ApiSubmissionService.Configuration,
|
||||
metrics: Metrics,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
)(implicit
|
||||
executionContext: ExecutionContext,
|
||||
loggingContext: LoggingContext,
|
||||
@ -78,7 +68,6 @@ private[apiserver] object ApiSubmissionService {
|
||||
checkOverloaded,
|
||||
configuration,
|
||||
metrics,
|
||||
errorCodesVersionSwitcher,
|
||||
),
|
||||
ledgerId = ledgerId,
|
||||
currentLedgerTime = () => timeProvider.getCurrentTime,
|
||||
@ -87,7 +76,6 @@ private[apiserver] object ApiSubmissionService {
|
||||
ledgerConfigurationSubscription.latestConfiguration().map(_.maxDeduplicationTime),
|
||||
submissionIdGenerator = SubmissionIdGenerator.Random,
|
||||
metrics = metrics,
|
||||
errorCodesVersionSwitcher = errorCodesVersionSwitcher,
|
||||
)
|
||||
|
||||
final case class Configuration(
|
||||
@ -107,13 +95,12 @@ private[apiserver] final class ApiSubmissionService private[services] (
|
||||
checkOverloaded: TelemetryContext => Option[state.SubmissionResult],
|
||||
configuration: ApiSubmissionService.Configuration,
|
||||
metrics: Metrics,
|
||||
val errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
)(implicit executionContext: ExecutionContext, loggingContext: LoggingContext)
|
||||
extends CommandSubmissionService
|
||||
with AutoCloseable {
|
||||
|
||||
private val logger = ContextualizedLogger.get(this.getClass)
|
||||
private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
private val errorFactories = ErrorFactories()
|
||||
|
||||
override def submit(
|
||||
request: SubmitRequest
|
||||
@ -136,9 +123,7 @@ private[apiserver] final class ApiSubmissionService private[services] (
|
||||
.transform(handleSubmissionResult)
|
||||
case None =>
|
||||
Future.failed(
|
||||
errorFactories.missingLedgerConfig(Status.Code.UNAVAILABLE)(definiteAnswer =
|
||||
Some(false)
|
||||
)
|
||||
errorFactories.missingLedgerConfig()
|
||||
)
|
||||
}
|
||||
evaluatedCommand.andThen(logger.logErrorsOnCall[Unit])
|
||||
@ -278,41 +263,12 @@ private[apiserver] final class ApiSubmissionService private[services] (
|
||||
.toScalaUnwrapped
|
||||
}
|
||||
|
||||
/** This method encodes logic related to legacy error codes (V1).
|
||||
* Cf. self-service error codes (V2) in //ledger/error
|
||||
*/
|
||||
@nowarn("msg=deprecated")
|
||||
private def toStatusExceptionV1(
|
||||
errorCause: ErrorCause
|
||||
)(implicit contextualizedErrorLogger: ContextualizedErrorLogger): StatusRuntimeException = {
|
||||
// Explicitly instantiate here a V1-enabled ErrorFactories to support the legacy error code dispatching logic.
|
||||
val v1ErrorFactories = ErrorFactories(new ErrorCodesVersionSwitcher(false))
|
||||
errorCause match {
|
||||
case cause @ ErrorCause.DamlLf(error) =>
|
||||
error match {
|
||||
case LfError.Interpretation(
|
||||
LfError.Interpretation.DamlException(
|
||||
InterpretationError.ContractNotFound(_) |
|
||||
InterpretationError.DuplicateContractKey(_)
|
||||
),
|
||||
_,
|
||||
) | LfError.Validation(LfError.Validation.ReplayMismatch(_)) =>
|
||||
v1ErrorFactories.aborted(cause.explain, definiteAnswer = Some(false))
|
||||
case _ =>
|
||||
v1ErrorFactories.invalidArgument(definiteAnswer = Some(false))(cause.explain)
|
||||
}
|
||||
case cause: ErrorCause.LedgerTime =>
|
||||
v1ErrorFactories.aborted(cause.explain, definiteAnswer = Some(false))
|
||||
}
|
||||
}
|
||||
|
||||
private def failedOnCommandExecution(
|
||||
error: ErrorCause
|
||||
)(implicit contextualizedErrorLogger: ContextualizedErrorLogger): Future[CommandExecutionResult] =
|
||||
errorCodesVersionSwitcher.chooseAsFailedFuture(
|
||||
v1 = toStatusExceptionV1(error),
|
||||
v2 = RejectionGenerators
|
||||
.commandExecutorError(cause = ErrorCauseExport.fromErrorCause(error)),
|
||||
Future.failed(
|
||||
RejectionGenerators
|
||||
.commandExecutorError(cause = ErrorCauseExport.fromErrorCause(error))
|
||||
)
|
||||
|
||||
override def close(): Unit = ()
|
||||
|
@ -7,11 +7,7 @@ import akka.NotUsed
|
||||
import akka.stream.Materializer
|
||||
import akka.stream.scaladsl.Source
|
||||
import com.daml.api.util.TimestampConversion._
|
||||
import com.daml.error.{
|
||||
ContextualizedErrorLogger,
|
||||
DamlContextualizedErrorLogger,
|
||||
ErrorCodesVersionSwitcher,
|
||||
}
|
||||
import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger}
|
||||
import com.daml.grpc.adapter.ExecutionSequencerFactory
|
||||
import com.daml.ledger.api.domain.LedgerId
|
||||
import com.daml.ledger.api.v1.testing.time_service.TimeServiceGrpc.TimeService
|
||||
@ -33,7 +29,6 @@ import scala.concurrent.{ExecutionContext, Future}
|
||||
private[apiserver] final class ApiTimeService private (
|
||||
val ledgerId: LedgerId,
|
||||
backend: TimeServiceBackend,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
)(implicit
|
||||
protected val mat: Materializer,
|
||||
protected val esf: ExecutionSequencerFactory,
|
||||
@ -46,7 +41,7 @@ private[apiserver] final class ApiTimeService private (
|
||||
private implicit val contextualizedErrorLogger: ContextualizedErrorLogger =
|
||||
new DamlContextualizedErrorLogger(logger, loggingContext, None)
|
||||
|
||||
private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
private val errorFactories = ErrorFactories()
|
||||
private val fieldValidations = FieldValidations(errorFactories)
|
||||
private val dispatcher = SignalDispatcher[Instant]()
|
||||
|
||||
@ -97,7 +92,7 @@ private[apiserver] final class ApiTimeService private (
|
||||
if (success) Right(requestedTime)
|
||||
else
|
||||
Left(
|
||||
invalidArgument(None)(
|
||||
invalidArgument(
|
||||
s"current_time mismatch. Provided: $expectedTime. Actual: ${backend.getCurrentTime}"
|
||||
)
|
||||
)
|
||||
@ -115,7 +110,7 @@ private[apiserver] final class ApiTimeService private (
|
||||
Right(())
|
||||
else
|
||||
Left(
|
||||
invalidArgument(None)(
|
||||
invalidArgument(
|
||||
s"new_time [$requestedTime] is before current_time [$expectedTime]. Setting time backwards is not allowed."
|
||||
)
|
||||
)
|
||||
@ -153,12 +148,11 @@ private[apiserver] object ApiTimeService {
|
||||
def create(
|
||||
ledgerId: LedgerId,
|
||||
backend: TimeServiceBackend,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
)(implicit
|
||||
mat: Materializer,
|
||||
esf: ExecutionSequencerFactory,
|
||||
executionContext: ExecutionContext,
|
||||
loggingContext: LoggingContext,
|
||||
): TimeService with GrpcApiService =
|
||||
new ApiTimeService(ledgerId, backend, errorCodesVersionSwitcher)
|
||||
new ApiTimeService(ledgerId, backend)
|
||||
}
|
||||
|
@ -3,11 +3,7 @@
|
||||
|
||||
package com.daml.platform.apiserver.services
|
||||
|
||||
import com.daml.error.{
|
||||
ContextualizedErrorLogger,
|
||||
DamlContextualizedErrorLogger,
|
||||
ErrorCodesVersionSwitcher,
|
||||
}
|
||||
import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger}
|
||||
import com.daml.ledger.api.v1.experimental_features.{
|
||||
ExperimentalFeatures,
|
||||
ExperimentalOptionalLedgerId,
|
||||
@ -36,7 +32,6 @@ import scala.util.Try
|
||||
import scala.util.control.NonFatal
|
||||
|
||||
private[apiserver] final class ApiVersionService private (
|
||||
enableSelfServiceErrorCodes: Boolean,
|
||||
ledgerFeatures: LedgerFeatures,
|
||||
userManagementConfig: UserManagementConfig,
|
||||
)(implicit
|
||||
@ -45,9 +40,8 @@ private[apiserver] final class ApiVersionService private (
|
||||
) extends VersionService
|
||||
with GrpcApiService {
|
||||
|
||||
private val errorCodesVersionSwitcher = new ErrorCodesVersionSwitcher(enableSelfServiceErrorCodes)
|
||||
private val logger = ContextualizedLogger.get(this.getClass)
|
||||
private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
private val errorFactories = ErrorFactories()
|
||||
private implicit val contextualizedErrorLogger: ContextualizedErrorLogger =
|
||||
new DamlContextualizedErrorLogger(logger, loggingContext, None)
|
||||
|
||||
@ -73,10 +67,9 @@ private[apiserver] final class ApiVersionService private (
|
||||
),
|
||||
experimental = Some(
|
||||
ExperimentalFeatures.of(
|
||||
selfServiceErrorCodes =
|
||||
Option.when(enableSelfServiceErrorCodes)(ExperimentalSelfServiceErrorCodes()): @nowarn(
|
||||
"cat=deprecation&origin=com\\.daml\\.ledger\\.api\\.v1\\.experimental_features\\..*"
|
||||
),
|
||||
selfServiceErrorCodes = Some(ExperimentalSelfServiceErrorCodes()): @nowarn(
|
||||
"cat=deprecation&origin=com\\.daml\\.ledger\\.api\\.v1\\.experimental_features\\..*"
|
||||
),
|
||||
staticTime = Some(ExperimentalStaticTime(supported = ledgerFeatures.staticTime)),
|
||||
commandDeduplication = Some(ledgerFeatures.commandDeduplicationFeatures),
|
||||
optionalLedgerId = Some(ExperimentalOptionalLedgerId()),
|
||||
@ -122,12 +115,10 @@ private[apiserver] final class ApiVersionService private (
|
||||
|
||||
private[apiserver] object ApiVersionService {
|
||||
def create(
|
||||
enableSelfServiceErrorCodes: Boolean,
|
||||
ledgerFeatures: LedgerFeatures,
|
||||
userManagementConfig: UserManagementConfig,
|
||||
)(implicit loggingContext: LoggingContext, ec: ExecutionContext): ApiVersionService =
|
||||
new ApiVersionService(
|
||||
enableSelfServiceErrorCodes,
|
||||
ledgerFeatures,
|
||||
userManagementConfig = userManagementConfig,
|
||||
)
|
||||
|
@ -7,11 +7,7 @@ import java.time.{Duration => JDuration}
|
||||
import akka.stream.Materializer
|
||||
import akka.stream.scaladsl.Source
|
||||
import com.daml.api.util.{DurationConversion, TimeProvider, TimestampConversion}
|
||||
import com.daml.error.{
|
||||
ContextualizedErrorLogger,
|
||||
DamlContextualizedErrorLogger,
|
||||
ErrorCodesVersionSwitcher,
|
||||
}
|
||||
import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger}
|
||||
import com.daml.ledger.api.domain
|
||||
import com.daml.ledger.api.domain.{ConfigurationEntry, LedgerOffset}
|
||||
import com.daml.ledger.api.v1.admin.config_management_service.ConfigManagementServiceGrpc.ConfigManagementService
|
||||
@ -28,7 +24,7 @@ import com.daml.platform.apiserver.services.logging
|
||||
import com.daml.platform.server.api.ValidationLogger
|
||||
import com.daml.platform.server.api.validation.{ErrorFactories, FieldValidations}
|
||||
import com.daml.telemetry.{DefaultTelemetry, TelemetryContext}
|
||||
import io.grpc.{ServerServiceDefinition, Status, StatusRuntimeException}
|
||||
import io.grpc.{ServerServiceDefinition, StatusRuntimeException}
|
||||
|
||||
import scala.jdk.FutureConverters.CompletionStageOps
|
||||
import scala.concurrent.duration.{Duration, FiniteDuration}
|
||||
@ -40,7 +36,6 @@ private[apiserver] final class ApiConfigManagementService private (
|
||||
writeService: state.WriteConfigService,
|
||||
timeProvider: TimeProvider,
|
||||
submissionIdGenerator: String => Ref.SubmissionId,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
)(implicit
|
||||
materializer: Materializer,
|
||||
executionContext: ExecutionContext,
|
||||
@ -48,7 +43,7 @@ private[apiserver] final class ApiConfigManagementService private (
|
||||
) extends ConfigManagementService
|
||||
with GrpcApiService {
|
||||
private implicit val logger: ContextualizedLogger = ContextualizedLogger.get(this.getClass)
|
||||
private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
private val errorFactories = ErrorFactories()
|
||||
private val fieldValidations = FieldValidations(errorFactories)
|
||||
|
||||
import errorFactories._
|
||||
@ -67,7 +62,7 @@ private[apiserver] final class ApiConfigManagementService private (
|
||||
Future.successful(configurationToResponse(configuration))
|
||||
case None =>
|
||||
Future.failed(
|
||||
missingLedgerConfig(Status.Code.NOT_FOUND)(Some(true))(
|
||||
missingLedgerConfig()(
|
||||
new DamlContextualizedErrorLogger(logger, loggingContext, None)
|
||||
)
|
||||
)
|
||||
@ -116,7 +111,7 @@ private[apiserver] final class ApiConfigManagementService private (
|
||||
logger.warn(
|
||||
"Could not get the current time model. The index does not yet have any ledger configuration."
|
||||
)
|
||||
Future.failed(missingLedgerConfig(Status.Code.UNAVAILABLE)(None))
|
||||
Future.failed(missingLedgerConfig())
|
||||
}
|
||||
(ledgerEndBeforeRequest, currentConfig) = configuration
|
||||
|
||||
@ -127,7 +122,7 @@ private[apiserver] final class ApiConfigManagementService private (
|
||||
Future.failed(
|
||||
ValidationLogger.logFailure(
|
||||
request,
|
||||
invalidArgument(None)(
|
||||
invalidArgument(
|
||||
s"Mismatching configuration generation, expected $expectedGeneration, received ${request.configurationGeneration}"
|
||||
),
|
||||
)
|
||||
@ -188,7 +183,7 @@ private[apiserver] final class ApiConfigManagementService private (
|
||||
minSkew = DurationConversion.fromProto(pMinSkew),
|
||||
maxSkew = DurationConversion.fromProto(pMaxSkew),
|
||||
) match {
|
||||
case Failure(err) => Left(invalidArgument(None)(err.toString))
|
||||
case Failure(err) => Left(invalidArgument(err.toString))
|
||||
case Success(ok) => Right(ok)
|
||||
}
|
||||
// TODO(JM): The maximum record time should be constrained, probably by the current active time model?
|
||||
@ -201,7 +196,7 @@ private[apiserver] final class ApiConfigManagementService private (
|
||||
}
|
||||
maximumRecordTime <- Time.Timestamp
|
||||
.fromInstant(mrtInstant)
|
||||
.fold(err => Left(invalidArgument(None)(err)), Right(_))
|
||||
.fold(err => Left(invalidArgument(err)), Right(_))
|
||||
} yield SetTimeModelParameters(newTimeModel, maximumRecordTime, timeToLive)
|
||||
}
|
||||
|
||||
@ -213,7 +208,6 @@ private[apiserver] object ApiConfigManagementService {
|
||||
readBackend: IndexConfigManagementService,
|
||||
writeBackend: state.WriteConfigService,
|
||||
timeProvider: TimeProvider,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
submissionIdGenerator: String => Ref.SubmissionId = augmentSubmissionId,
|
||||
)(implicit
|
||||
materializer: Materializer,
|
||||
@ -225,7 +219,6 @@ private[apiserver] object ApiConfigManagementService {
|
||||
writeBackend,
|
||||
timeProvider,
|
||||
submissionIdGenerator,
|
||||
errorCodesVersionSwitcher,
|
||||
)
|
||||
|
||||
private final class SynchronousResponseStrategy(
|
||||
@ -269,7 +262,7 @@ private[apiserver] object ApiConfigManagementService {
|
||||
submissionId: Ref.SubmissionId
|
||||
): PartialFunction[ConfigurationEntry, StatusRuntimeException] = {
|
||||
case domain.ConfigurationEntry.Rejected(`submissionId`, reason, _) =>
|
||||
errorFactories.configurationEntryRejected(reason, None)(
|
||||
errorFactories.configurationEntryRejected(reason)(
|
||||
new DamlContextualizedErrorLogger(logger, loggingContext, Some(submissionId))
|
||||
)
|
||||
}
|
||||
|
@ -3,11 +3,7 @@
|
||||
|
||||
package com.daml.platform.apiserver.services.admin
|
||||
|
||||
import com.daml.error.{
|
||||
ContextualizedErrorLogger,
|
||||
DamlContextualizedErrorLogger,
|
||||
ErrorCodesVersionSwitcher,
|
||||
}
|
||||
import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger}
|
||||
import com.daml.ledger.api.v1.admin.metering_report_service.MeteringReportServiceGrpc.MeteringReportService
|
||||
import com.daml.ledger.api.v1.admin.metering_report_service._
|
||||
import com.daml.ledger.participant.state.index.v2.MeteringStore
|
||||
@ -29,7 +25,6 @@ import scala.util.chaining.scalaUtilChainingOps
|
||||
private[apiserver] final class ApiMeteringReportService(
|
||||
participantId: Ref.ParticipantId,
|
||||
store: MeteringStore,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
clock: () => ProtoTimestamp = () => toProtoTimestamp(Timestamp.now()),
|
||||
)(implicit
|
||||
executionContext: ExecutionContext,
|
||||
@ -42,7 +37,7 @@ private[apiserver] final class ApiMeteringReportService(
|
||||
new DamlContextualizedErrorLogger(logger, loggingContext, None)
|
||||
|
||||
private val generator = new MeteringReportGenerator(participantId)
|
||||
private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
private val errorFactories = ErrorFactories()
|
||||
|
||||
override def bindService(): ServerServiceDefinition =
|
||||
MeteringReportServiceGrpc.bindService(this, executionContext)
|
||||
@ -73,7 +68,7 @@ private[apiserver] final class ApiMeteringReportService(
|
||||
case Right(f) => f
|
||||
case Left(error) =>
|
||||
Future.failed(
|
||||
ValidationLogger.logFailure(request, errorFactories.invalidArgument(None)(error))
|
||||
ValidationLogger.logFailure(request, errorFactories.invalidArgument(error))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -11,11 +11,7 @@ import com.daml.api.util.TimestampConversion
|
||||
import com.daml.daml_lf_dev.DamlLf.Archive
|
||||
import com.daml.error.definitions.LoggingPackageServiceError
|
||||
import com.daml.error.definitions.PackageServiceError.Validation
|
||||
import com.daml.error.{
|
||||
ContextualizedErrorLogger,
|
||||
DamlContextualizedErrorLogger,
|
||||
ErrorCodesVersionSwitcher,
|
||||
}
|
||||
import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger}
|
||||
import com.daml.ledger.api.domain.{LedgerOffset, PackageEntry}
|
||||
import com.daml.ledger.api.v1.admin.package_management_service.PackageManagementServiceGrpc.PackageManagementService
|
||||
import com.daml.ledger.api.v1.admin.package_management_service._
|
||||
@ -52,7 +48,6 @@ private[apiserver] final class ApiPackageManagementService private (
|
||||
engine: Engine,
|
||||
darReader: GenDarReader[Archive],
|
||||
submissionIdGenerator: String => Ref.SubmissionId,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
)(implicit
|
||||
materializer: Materializer,
|
||||
executionContext: ExecutionContext,
|
||||
@ -62,7 +57,7 @@ private[apiserver] final class ApiPackageManagementService private (
|
||||
|
||||
private implicit val logger: ContextualizedLogger = ContextualizedLogger.get(this.getClass)
|
||||
|
||||
private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
private val errorFactories = ErrorFactories()
|
||||
private val synchronousResponse = new SynchronousResponse(
|
||||
new SynchronousResponseStrategy(
|
||||
transactionsService,
|
||||
@ -160,7 +155,6 @@ private[apiserver] object ApiPackageManagementService {
|
||||
writeBackend: state.WritePackagesService,
|
||||
managementServiceTimeout: Duration,
|
||||
engine: Engine,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
darReader: GenDarReader[Archive] = DarParser,
|
||||
submissionIdGenerator: String => Ref.SubmissionId = augmentSubmissionId,
|
||||
)(implicit
|
||||
@ -176,7 +170,6 @@ private[apiserver] object ApiPackageManagementService {
|
||||
engine,
|
||||
darReader,
|
||||
submissionIdGenerator,
|
||||
errorCodesVersionSwitcher,
|
||||
)
|
||||
|
||||
private final class SynchronousResponseStrategy(
|
||||
@ -213,7 +206,7 @@ private[apiserver] object ApiPackageManagementService {
|
||||
submissionId: Ref.SubmissionId
|
||||
): PartialFunction[PackageEntry, StatusRuntimeException] = {
|
||||
case PackageEntry.PackageUploadRejected(`submissionId`, _, reason) =>
|
||||
errorFactories.packageUploadRejected(reason, definiteAnswer = None)(
|
||||
errorFactories.packageUploadRejected(reason)(
|
||||
new DamlContextualizedErrorLogger(logger, loggingContext, Some(submissionId))
|
||||
)
|
||||
}
|
||||
|
@ -3,11 +3,7 @@
|
||||
|
||||
package com.daml.platform.apiserver.services.admin
|
||||
|
||||
import com.daml.error.{
|
||||
ContextualizedErrorLogger,
|
||||
DamlContextualizedErrorLogger,
|
||||
ErrorCodesVersionSwitcher,
|
||||
}
|
||||
import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger}
|
||||
|
||||
import java.util.UUID
|
||||
import com.daml.ledger.api.v1.admin.participant_pruning_service.{
|
||||
@ -34,13 +30,12 @@ import scala.concurrent.{ExecutionContext, Future}
|
||||
final class ApiParticipantPruningService private (
|
||||
readBackend: IndexParticipantPruningService with LedgerEndService,
|
||||
writeBackend: state.WriteParticipantPruningService,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
)(implicit executionContext: ExecutionContext, loggingContext: LoggingContext)
|
||||
extends ParticipantPruningServiceGrpc.ParticipantPruningService
|
||||
with GrpcApiService {
|
||||
|
||||
private implicit val logger: ContextualizedLogger = ContextualizedLogger.get(this.getClass)
|
||||
private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
private val errorFactories = ErrorFactories()
|
||||
|
||||
import errorFactories._
|
||||
|
||||
@ -56,7 +51,7 @@ final class ApiParticipantPruningService private (
|
||||
)
|
||||
.left
|
||||
.map(err =>
|
||||
invalidArgument(None)(s"submission_id $err")(
|
||||
invalidArgument(s"submission_id $err")(
|
||||
contextualizedErrorLogger(request.submissionId)
|
||||
)
|
||||
)
|
||||
@ -143,7 +138,7 @@ final class ApiParticipantPruningService private (
|
||||
Either.cond(
|
||||
offset.nonEmpty,
|
||||
offset,
|
||||
invalidArgument(None)("prune_up_to not specified"),
|
||||
invalidArgument("prune_up_to not specified"),
|
||||
)
|
||||
|
||||
private def checkOffsetIsHexadecimal(
|
||||
@ -154,7 +149,7 @@ final class ApiParticipantPruningService private (
|
||||
.toEither
|
||||
.left
|
||||
.map(t =>
|
||||
nonHexOffset(None)(
|
||||
nonHexOffset(
|
||||
fieldName = "prune_up_to",
|
||||
offsetValue = pruneUpToString,
|
||||
message =
|
||||
@ -177,7 +172,7 @@ final class ApiParticipantPruningService private (
|
||||
Future.failed(
|
||||
// TODO error codes: Relax the constraint (pruneUpToString <= ledgerEnd.value)
|
||||
// and use offsetAfterLedgerEnd
|
||||
offsetOutOfRange(None)(
|
||||
offsetOutOfRange(
|
||||
s"prune_up_to needs to be before ledger end ${ledgerEnd.value}"
|
||||
)
|
||||
)
|
||||
@ -193,11 +188,10 @@ object ApiParticipantPruningService {
|
||||
def createApiService(
|
||||
readBackend: IndexParticipantPruningService with LedgerEndService,
|
||||
writeBackend: state.WriteParticipantPruningService,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
)(implicit
|
||||
executionContext: ExecutionContext,
|
||||
loggingContext: LoggingContext,
|
||||
): ParticipantPruningServiceGrpc.ParticipantPruningService with GrpcApiService =
|
||||
new ApiParticipantPruningService(readBackend, writeBackend, errorCodesVersionSwitcher)
|
||||
new ApiParticipantPruningService(readBackend, writeBackend)
|
||||
|
||||
}
|
||||
|
@ -7,11 +7,7 @@ import java.time.Duration
|
||||
import java.util.UUID
|
||||
import akka.stream.Materializer
|
||||
import akka.stream.scaladsl.Source
|
||||
import com.daml.error.{
|
||||
ContextualizedErrorLogger,
|
||||
DamlContextualizedErrorLogger,
|
||||
ErrorCodesVersionSwitcher,
|
||||
}
|
||||
import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger}
|
||||
import com.daml.ledger.api.domain.{LedgerOffset, PartyEntry}
|
||||
import com.daml.ledger.api.v1.admin.party_management_service.PartyManagementServiceGrpc.PartyManagementService
|
||||
import com.daml.ledger.api.v1.admin.party_management_service._
|
||||
@ -40,7 +36,6 @@ private[apiserver] final class ApiPartyManagementService private (
|
||||
transactionService: IndexTransactionsService,
|
||||
writeService: state.WritePartyService,
|
||||
managementServiceTimeout: Duration,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
submissionIdGenerator: Option[Ref.Party] => Ref.SubmissionId,
|
||||
)(implicit
|
||||
materializer: Materializer,
|
||||
@ -53,7 +48,7 @@ private[apiserver] final class ApiPartyManagementService private (
|
||||
private implicit val contextualizedErrorLogger: ContextualizedErrorLogger =
|
||||
new DamlContextualizedErrorLogger(logger, loggingContext, None)
|
||||
|
||||
private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
private val errorFactories = ErrorFactories()
|
||||
private val synchronousResponse = new SynchronousResponse(
|
||||
new SynchronousResponseStrategy(
|
||||
transactionService,
|
||||
@ -115,7 +110,7 @@ private[apiserver] final class ApiPartyManagementService private (
|
||||
error =>
|
||||
Future.failed(
|
||||
ValidationLogger
|
||||
.logFailure(request, errorFactories.invalidArgument(None)(error))
|
||||
.logFailure(request, errorFactories.invalidArgument(error))
|
||||
),
|
||||
party => Future.successful(Some(party)),
|
||||
)
|
||||
@ -159,7 +154,6 @@ private[apiserver] object ApiPartyManagementService {
|
||||
transactionsService: IndexTransactionsService,
|
||||
writeBackend: state.WritePartyService,
|
||||
managementServiceTimeout: Duration,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
submissionIdGenerator: Option[Ref.Party] => Ref.SubmissionId = CreateSubmissionId.withPrefix,
|
||||
)(implicit
|
||||
materializer: Materializer,
|
||||
@ -171,7 +165,6 @@ private[apiserver] object ApiPartyManagementService {
|
||||
transactionsService,
|
||||
writeBackend,
|
||||
managementServiceTimeout,
|
||||
errorCodesVersionSwitcher,
|
||||
submissionIdGenerator,
|
||||
)
|
||||
|
||||
@ -225,7 +218,7 @@ private[apiserver] object ApiPartyManagementService {
|
||||
submissionId: Ref.SubmissionId
|
||||
): PartialFunction[PartyEntry, StatusRuntimeException] = {
|
||||
case PartyEntry.AllocationRejected(`submissionId`, reason) =>
|
||||
errorFactories.invalidArgument(None)(reason)(
|
||||
errorFactories.invalidArgument(reason)(
|
||||
new DamlContextualizedErrorLogger(logger, loggingContext, Some(submissionId))
|
||||
)
|
||||
}
|
||||
|
@ -7,11 +7,7 @@ import java.nio.charset.StandardCharsets
|
||||
import java.util.Base64
|
||||
|
||||
import com.daml.error.definitions.LedgerApiErrors
|
||||
import com.daml.error.{
|
||||
ContextualizedErrorLogger,
|
||||
DamlContextualizedErrorLogger,
|
||||
ErrorCodesVersionSwitcher,
|
||||
}
|
||||
import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger}
|
||||
import com.daml.ledger.api.domain._
|
||||
import com.daml.ledger.api.v1.admin.user_management_service.{CreateUserResponse, GetUserResponse}
|
||||
import com.daml.ledger.api.v1.admin.{user_management_service => proto}
|
||||
@ -32,7 +28,6 @@ import scala.util.Try
|
||||
|
||||
private[apiserver] final class ApiUserManagementService(
|
||||
userManagementStore: UserManagementStore,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
maxUsersPageSize: Int,
|
||||
)(implicit
|
||||
executionContext: ExecutionContext,
|
||||
@ -43,7 +38,7 @@ private[apiserver] final class ApiUserManagementService(
|
||||
import ApiUserManagementService._
|
||||
|
||||
private implicit val logger: ContextualizedLogger = ContextualizedLogger.get(this.getClass)
|
||||
private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
private val errorFactories = ErrorFactories()
|
||||
private implicit val contextualizedErrorLogger: ContextualizedErrorLogger =
|
||||
new DamlContextualizedErrorLogger(logger, loggingContext, None)
|
||||
private val fieldValidations = FieldValidations(errorFactories)
|
||||
|
@ -6,11 +6,7 @@ package com.daml.platform.apiserver.services.transaction
|
||||
import akka.NotUsed
|
||||
import akka.stream.Materializer
|
||||
import akka.stream.scaladsl.Source
|
||||
import com.daml.error.{
|
||||
ContextualizedErrorLogger,
|
||||
DamlContextualizedErrorLogger,
|
||||
ErrorCodesVersionSwitcher,
|
||||
}
|
||||
import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger}
|
||||
import com.daml.grpc.adapter.ExecutionSequencerFactory
|
||||
import com.daml.ledger.api.domain.{
|
||||
Filters,
|
||||
@ -48,7 +44,6 @@ private[apiserver] object ApiTransactionService {
|
||||
ledgerId: LedgerId,
|
||||
transactionsService: IndexTransactionsService,
|
||||
metrics: Metrics,
|
||||
errorsVersionsSwitcher: ErrorCodesVersionSwitcher,
|
||||
)(implicit
|
||||
ec: ExecutionContext,
|
||||
mat: Materializer,
|
||||
@ -56,22 +51,21 @@ private[apiserver] object ApiTransactionService {
|
||||
loggingContext: LoggingContext,
|
||||
): GrpcTransactionService with BindableService =
|
||||
new GrpcTransactionService(
|
||||
new ApiTransactionService(transactionsService, metrics, errorsVersionsSwitcher),
|
||||
new ApiTransactionService(transactionsService, metrics),
|
||||
ledgerId,
|
||||
PartyNameChecker.AllowAllParties,
|
||||
ErrorFactories(errorsVersionsSwitcher),
|
||||
ErrorFactories(),
|
||||
)
|
||||
}
|
||||
|
||||
private[apiserver] final class ApiTransactionService private (
|
||||
transactionsService: IndexTransactionsService,
|
||||
metrics: Metrics,
|
||||
errorCodesVersionSwitcher: ErrorCodesVersionSwitcher,
|
||||
)(implicit executionContext: ExecutionContext, loggingContext: LoggingContext)
|
||||
extends TransactionService {
|
||||
|
||||
private val logger: ContextualizedLogger = ContextualizedLogger.get(this.getClass)
|
||||
private val errorFactories = ErrorFactories(errorCodesVersionSwitcher)
|
||||
private val errorFactories = ErrorFactories()
|
||||
|
||||
import errorFactories.transactionNotFound
|
||||
import errorFactories.invalidArgumentWasNotFound
|
||||
@ -146,7 +140,7 @@ private[apiserver] final class ApiTransactionService private (
|
||||
}
|
||||
.getOrElse {
|
||||
Future.failed {
|
||||
invalidArgumentWasNotFound(None)(s"invalid eventId: ${request.eventId}")
|
||||
invalidArgumentWasNotFound(s"invalid eventId: ${request.eventId}")
|
||||
}
|
||||
}
|
||||
.andThen(logger.logErrorsOnCall[GetTransactionResponse])
|
||||
@ -189,7 +183,7 @@ private[apiserver] final class ApiTransactionService private (
|
||||
}
|
||||
.getOrElse {
|
||||
val msg = s"eventId: ${request.eventId}"
|
||||
Future.failed(invalidArgumentWasNotFound(None)(msg))
|
||||
Future.failed(invalidArgumentWasNotFound(msg))
|
||||
}
|
||||
.andThen(logger.logErrorsOnCall[GetFlatTransactionResponse])
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
package com.daml.platform.index
|
||||
|
||||
import akka.stream.Materializer
|
||||
import com.daml.error.ErrorCodesVersionSwitcher
|
||||
import com.daml.ledger.api.domain.LedgerId
|
||||
import com.daml.ledger.participant.state.index.v2.IndexService
|
||||
import com.daml.ledger.resources.ResourceOwner
|
||||
@ -37,7 +36,6 @@ private[platform] object JdbcIndex {
|
||||
maxContractKeyStateCacheSize: Long,
|
||||
maxTransactionsInMemoryFanOutBufferSize: Long,
|
||||
enableInMemoryFanOutForLedgerApi: Boolean,
|
||||
enableSelfServiceErrorCodes: Boolean,
|
||||
)(implicit mat: Materializer, loggingContext: LoggingContext): ResourceOwner[IndexService] =
|
||||
new ReadOnlySqlLedger.Owner(
|
||||
dbSupport = dbSupport,
|
||||
@ -58,12 +56,12 @@ private[platform] object JdbcIndex {
|
||||
enableInMemoryFanOutForLedgerApi = enableInMemoryFanOutForLedgerApi,
|
||||
participantId = participantId,
|
||||
maxTransactionsInMemoryFanOutBufferSize = maxTransactionsInMemoryFanOutBufferSize,
|
||||
errorFactories = ErrorFactories(new ErrorCodesVersionSwitcher(enableSelfServiceErrorCodes)),
|
||||
errorFactories = ErrorFactories(),
|
||||
).map { ledger =>
|
||||
new LedgerBackedIndexService(
|
||||
MeteredReadOnlyLedger(ledger, metrics),
|
||||
participantId,
|
||||
errorFactories = ErrorFactories(new ErrorCodesVersionSwitcher(enableSelfServiceErrorCodes)),
|
||||
errorFactories = ErrorFactories(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ private[platform] final class LedgerBackedIndexService(
|
||||
Source.empty
|
||||
case Some(end) if begin > end =>
|
||||
Source.failed(
|
||||
errorFactories.offsetOutOfRange(None)(
|
||||
errorFactories.offsetOutOfRange(
|
||||
s"End offset ${end.toApiString} is before Begin offset ${begin.toApiString}."
|
||||
)(new DamlContextualizedErrorLogger(logger, loggingContext, None))
|
||||
)
|
||||
|
@ -411,7 +411,7 @@ private class JdbcLedgerDao(
|
||||
conn,
|
||||
)
|
||||
) {
|
||||
throw errorFactories.offsetOutOfRange(None)(
|
||||
throw errorFactories.offsetOutOfRange(
|
||||
"Pruning offset for all divulged contracts needs to be after the migration offset"
|
||||
)(new DamlContextualizedErrorLogger(logger, loggingContext, None))
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
package com.daml.platform.store.dao
|
||||
|
||||
import com.codahale.metrics.MetricRegistry
|
||||
import com.daml.error.ErrorCodesVersionSwitcher
|
||||
import com.daml.ledger.api.domain.{LedgerId, ParticipantId}
|
||||
import com.daml.ledger.api.testing.utils.AkkaBeforeAndAfterAll
|
||||
import com.daml.ledger.resources.{Resource, ResourceContext, ResourceOwner}
|
||||
@ -109,9 +108,7 @@ private[dao] trait JdbcLedgerDaoBackend extends AkkaBeforeAndAfterAll {
|
||||
|
||||
// `dbDispatcher` and `ledgerDao` depend on the `postgresFixture` which is in turn initialized `beforeAll`
|
||||
private var resource: Resource[LedgerDao] = _
|
||||
private val errorFactories = ErrorFactories(
|
||||
new ErrorCodesVersionSwitcher(enableSelfServiceErrorCodes = true)
|
||||
)
|
||||
private val errorFactories = ErrorFactories()
|
||||
|
||||
override protected def beforeAll(): Unit = {
|
||||
super.beforeAll()
|
||||
|
@ -7,7 +7,6 @@ import akka.NotUsed
|
||||
import akka.stream.Materializer
|
||||
import akka.stream.scaladsl.Source
|
||||
import com.daml.api.util.TimeProvider
|
||||
import com.daml.error.ErrorCodesVersionSwitcher
|
||||
import com.daml.grpc.{GrpcException, GrpcStatus}
|
||||
import com.daml.ledger.api.domain.{ConfigurationEntry, LedgerOffset}
|
||||
import com.daml.ledger.api.testing.utils.AkkaBeforeAndAfterAll
|
||||
@ -53,8 +52,6 @@ class ApiConfigManagementServiceSpec
|
||||
|
||||
private implicit val loggingContext: LoggingContext = LoggingContext.ForTesting
|
||||
|
||||
private val useSelfServiceErrorCodes = mock[ErrorCodesVersionSwitcher]
|
||||
|
||||
"ApiConfigManagementService" should {
|
||||
"get the time model" in {
|
||||
val indexedTimeModel = LedgerTimeModel(
|
||||
@ -76,7 +73,6 @@ class ApiConfigManagementServiceSpec
|
||||
),
|
||||
writeService,
|
||||
TimeProvider.UTC,
|
||||
useSelfServiceErrorCodes,
|
||||
)
|
||||
|
||||
apiConfigManagementService
|
||||
@ -84,17 +80,25 @@ class ApiConfigManagementServiceSpec
|
||||
.map { response =>
|
||||
response.timeModel should be(Some(expectedTimeModel))
|
||||
verifyZeroInteractions(writeService)
|
||||
verifyZeroInteractions(useSelfServiceErrorCodes)
|
||||
succeed
|
||||
}
|
||||
}
|
||||
|
||||
"return a `NOT_FOUND` error if a time model is not found (V1 error codes)" in {
|
||||
testReturnANotFoundErrorIfTimeModelNotFound(false)
|
||||
}
|
||||
"return a `NOT_FOUND` error if a time model is not found" in {
|
||||
val writeService = mock[WriteConfigService]
|
||||
val apiConfigManagementService = ApiConfigManagementService.createApiService(
|
||||
EmptyIndexConfigManagementService,
|
||||
writeService,
|
||||
TimeProvider.UTC,
|
||||
)
|
||||
|
||||
"return a `NOT_FOUND` error if a time model is not found (V2 self-service error codes)" in {
|
||||
testReturnANotFoundErrorIfTimeModelNotFound(true)
|
||||
apiConfigManagementService
|
||||
.getTimeModel(GetTimeModelRequest.defaultInstance)
|
||||
.transform(Success.apply)
|
||||
.map { response =>
|
||||
response should matchPattern { case Failure(GrpcException(GrpcStatus.NOT_FOUND(), _)) =>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"set a new time model" in {
|
||||
@ -133,7 +137,6 @@ class ApiConfigManagementServiceSpec
|
||||
indexService,
|
||||
writeService,
|
||||
timeProvider,
|
||||
useSelfServiceErrorCodes,
|
||||
)
|
||||
|
||||
apiConfigManagementService
|
||||
@ -154,17 +157,44 @@ class ApiConfigManagementServiceSpec
|
||||
.map { response =>
|
||||
response.configurationGeneration should be(expectedGeneration)
|
||||
currentConfiguration() should be(Some(expectedConfiguration))
|
||||
verifyZeroInteractions(useSelfServiceErrorCodes)
|
||||
succeed
|
||||
}
|
||||
}
|
||||
|
||||
"refuse to set a new time model if none is indexed (V1 error codes)" in {
|
||||
testRefuseToSetANewTimeModelIfNoneIsIndexedYet(false)
|
||||
}
|
||||
"refuse to set a new time model if none is indexed" in {
|
||||
val initialGeneration = 0L
|
||||
|
||||
"refuse to set a new time model if none is indexed (V2 self-service error codes)" in {
|
||||
testRefuseToSetANewTimeModelIfNoneIsIndexedYet(true)
|
||||
val timeProvider = TimeProvider.UTC
|
||||
val maximumRecordTime = timeProvider.getCurrentTime.plusSeconds(60)
|
||||
|
||||
val writeService = mock[WriteService]
|
||||
val apiConfigManagementService = ApiConfigManagementService.createApiService(
|
||||
EmptyIndexConfigManagementService,
|
||||
writeService,
|
||||
timeProvider,
|
||||
)
|
||||
|
||||
apiConfigManagementService
|
||||
.setTimeModel(
|
||||
SetTimeModelRequest.of(
|
||||
"a submission ID",
|
||||
maximumRecordTime = Some(Timestamp.of(maximumRecordTime.getEpochSecond, 0)),
|
||||
configurationGeneration = initialGeneration,
|
||||
newTimeModel = Some(
|
||||
TimeModel(
|
||||
avgTransactionLatency = Some(DurationProto.of(10, 0)),
|
||||
minSkew = Some(DurationProto.of(20, 0)),
|
||||
maxSkew = Some(DurationProto.of(40, 0)),
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
.transform(Success.apply)
|
||||
.map { response =>
|
||||
verifyZeroInteractions(writeService)
|
||||
response should matchPattern { case Failure(GrpcException(GrpcStatus.NOT_FOUND(), _)) =>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"propagate trace context" in {
|
||||
@ -172,7 +202,6 @@ class ApiConfigManagementServiceSpec
|
||||
new FakeStreamingIndexConfigManagementService(someConfigurationEntries),
|
||||
TestWriteConfigService,
|
||||
TimeProvider.UTC,
|
||||
useSelfServiceErrorCodes,
|
||||
_ => Ref.SubmissionId.assertFromString("aSubmission"),
|
||||
)
|
||||
|
||||
@ -186,73 +215,10 @@ class ApiConfigManagementServiceSpec
|
||||
}
|
||||
.map { _ =>
|
||||
spanExporter.finishedSpanAttributes should contain(anApplicationIdSpanAttribute)
|
||||
verifyZeroInteractions(useSelfServiceErrorCodes)
|
||||
succeed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def testReturnANotFoundErrorIfTimeModelNotFound(useSelfServiceErrorCodes: Boolean) = {
|
||||
val errorCodesVersionSwitcher = new ErrorCodesVersionSwitcher(useSelfServiceErrorCodes)
|
||||
val writeService = mock[WriteConfigService]
|
||||
val apiConfigManagementService = ApiConfigManagementService.createApiService(
|
||||
EmptyIndexConfigManagementService,
|
||||
writeService,
|
||||
TimeProvider.UTC,
|
||||
errorCodesVersionSwitcher,
|
||||
)
|
||||
|
||||
apiConfigManagementService
|
||||
.getTimeModel(GetTimeModelRequest.defaultInstance)
|
||||
.transform(Success.apply)
|
||||
.map { response =>
|
||||
response should matchPattern { case Failure(GrpcException(GrpcStatus.NOT_FOUND(), _)) =>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def testRefuseToSetANewTimeModelIfNoneIsIndexedYet(useSelfServiceErrorCodes: Boolean) = {
|
||||
val errorCodesVersionSwitcher = new ErrorCodesVersionSwitcher(useSelfServiceErrorCodes)
|
||||
val initialGeneration = 0L
|
||||
|
||||
val timeProvider = TimeProvider.UTC
|
||||
val maximumRecordTime = timeProvider.getCurrentTime.plusSeconds(60)
|
||||
|
||||
val writeService = mock[WriteService]
|
||||
val apiConfigManagementService = ApiConfigManagementService.createApiService(
|
||||
EmptyIndexConfigManagementService,
|
||||
writeService,
|
||||
timeProvider,
|
||||
errorCodesVersionSwitcher,
|
||||
)
|
||||
|
||||
apiConfigManagementService
|
||||
.setTimeModel(
|
||||
SetTimeModelRequest.of(
|
||||
"a submission ID",
|
||||
maximumRecordTime = Some(Timestamp.of(maximumRecordTime.getEpochSecond, 0)),
|
||||
configurationGeneration = initialGeneration,
|
||||
newTimeModel = Some(
|
||||
TimeModel(
|
||||
avgTransactionLatency = Some(DurationProto.of(10, 0)),
|
||||
minSkew = Some(DurationProto.of(20, 0)),
|
||||
maxSkew = Some(DurationProto.of(40, 0)),
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
.transform(Success.apply)
|
||||
.map { response =>
|
||||
verifyZeroInteractions(writeService)
|
||||
if (useSelfServiceErrorCodes) {
|
||||
response should matchPattern { case Failure(GrpcException(GrpcStatus.NOT_FOUND(), _)) =>
|
||||
}
|
||||
} else {
|
||||
response should matchPattern { case Failure(GrpcException(GrpcStatus.UNAVAILABLE(), _)) =>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ApiConfigManagementServiceSpec {
|
||||
|
@ -9,7 +9,6 @@ import java.util.zip.ZipInputStream
|
||||
import akka.stream.scaladsl.Source
|
||||
import com.daml.daml_lf_dev.DamlLf
|
||||
import com.daml.daml_lf_dev.DamlLf.Archive
|
||||
import com.daml.error.ErrorCodesVersionSwitcher
|
||||
import com.daml.ledger.api.domain.LedgerOffset.Absolute
|
||||
import com.daml.ledger.api.domain.PackageEntry
|
||||
import com.daml.ledger.api.testing.utils.AkkaBeforeAndAfterAll
|
||||
@ -49,7 +48,6 @@ class ApiPackageManagementServiceSpec
|
||||
import ApiPackageManagementServiceSpec._
|
||||
|
||||
private implicit val loggingContext: LoggingContext = LoggingContext.ForTesting
|
||||
private val errorCodesVersionSwitcher: ErrorCodesVersionSwitcher = mock[ErrorCodesVersionSwitcher]
|
||||
|
||||
"ApiPackageManagementService $suffix" should {
|
||||
"propagate trace context" in {
|
||||
@ -65,7 +63,6 @@ class ApiPackageManagementServiceSpec
|
||||
}
|
||||
.map { _ =>
|
||||
spanExporter.finishedSpanAttributes should contain(anApplicationIdSpanAttribute)
|
||||
verifyZeroInteractions(errorCodesVersionSwitcher)
|
||||
succeed
|
||||
}
|
||||
}
|
||||
@ -99,7 +96,6 @@ class ApiPackageManagementServiceSpec
|
||||
TestWritePackagesService,
|
||||
Duration.ZERO,
|
||||
mockEngine,
|
||||
errorCodesVersionSwitcher,
|
||||
mockDarReader,
|
||||
_ => Ref.SubmissionId.assertFromString("aSubmission"),
|
||||
)
|
||||
|
@ -6,7 +6,6 @@ package com.daml.platform.apiserver.services.admin
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.{CompletableFuture, CompletionStage}
|
||||
import akka.stream.scaladsl.Source
|
||||
import com.daml.error.ErrorCodesVersionSwitcher
|
||||
import com.daml.ledger.api.domain.LedgerOffset.Absolute
|
||||
import com.daml.ledger.api.domain.{PartyDetails, PartyEntry}
|
||||
import com.daml.ledger.api.testing.utils.AkkaBeforeAndAfterAll
|
||||
@ -36,7 +35,6 @@ class ApiPartyManagementServiceSpec
|
||||
with AkkaBeforeAndAfterAll {
|
||||
|
||||
private implicit val loggingContext: LoggingContext = LoggingContext.ForTesting
|
||||
private val errorCodesVersionSwitcher: ErrorCodesVersionSwitcher = mock[ErrorCodesVersionSwitcher]
|
||||
|
||||
"ApiPartyManagementService" should {
|
||||
"propagate trace context" in {
|
||||
@ -62,7 +60,6 @@ class ApiPartyManagementServiceSpec
|
||||
mockIndexTransactionsService,
|
||||
TestWritePartyService,
|
||||
Duration.ZERO,
|
||||
errorCodesVersionSwitcher,
|
||||
_ => Ref.SubmissionId.assertFromString("aSubmission"),
|
||||
)
|
||||
|
||||
@ -76,7 +73,6 @@ class ApiPartyManagementServiceSpec
|
||||
}
|
||||
.map { _ =>
|
||||
spanExporter.finishedSpanAttributes should contain(anApplicationIdSpanAttribute)
|
||||
verifyZeroInteractions(errorCodesVersionSwitcher)
|
||||
succeed
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
package com.daml.platform.apiserver.services
|
||||
|
||||
import com.daml.error.ErrorCodesVersionSwitcher
|
||||
import java.time.{Duration, Instant}
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.TimeUnit
|
||||
@ -40,8 +39,6 @@ class ApiCommandServiceSpec
|
||||
private implicit val resourceContext: ResourceContext = ResourceContext(executionContext)
|
||||
private implicit val loggingContext: LoggingContext = LoggingContext.ForTesting
|
||||
|
||||
private val errorCodesVersionSwitcher = mock[ErrorCodesVersionSwitcher]
|
||||
|
||||
s"the command service" should {
|
||||
val completionSuccess = CompletionResponse.CompletionSuccess(
|
||||
Completion(
|
||||
@ -71,7 +68,6 @@ class ApiCommandServiceSpec
|
||||
new ApiCommandService(
|
||||
UnimplementedTransactionServices,
|
||||
submissionTracker,
|
||||
errorCodesVersionSwitcher,
|
||||
)
|
||||
).use { stub =>
|
||||
val request = SubmitAndWaitRequest.of(Some(commands))
|
||||
@ -81,7 +77,6 @@ class ApiCommandServiceSpec
|
||||
verify(submissionTracker).track(
|
||||
eqTo(CommandSubmission(commands))
|
||||
)(any[ExecutionContext], any[LoggingContext])
|
||||
verifyZeroInteractions(errorCodesVersionSwitcher)
|
||||
succeed
|
||||
}
|
||||
}
|
||||
@ -111,7 +106,6 @@ class ApiCommandServiceSpec
|
||||
new ApiCommandService(
|
||||
UnimplementedTransactionServices,
|
||||
submissionTracker,
|
||||
errorCodesVersionSwitcher,
|
||||
),
|
||||
deadlineTicker,
|
||||
).use { stub =>
|
||||
@ -124,55 +118,42 @@ class ApiCommandServiceSpec
|
||||
verify(submissionTracker).track(
|
||||
eqTo(CommandSubmission(commands, timeout = Some(Duration.ofSeconds(30))))
|
||||
)(any[ExecutionContext], any[LoggingContext])
|
||||
verifyZeroInteractions(errorCodesVersionSwitcher)
|
||||
succeed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"time out if the tracker times out" in {
|
||||
def testTrackerTimeout(switcher: ErrorCodesVersionSwitcher, expectedStatusCode: Code) = {
|
||||
val commands = someCommands()
|
||||
val submissionTracker = mock[Tracker]
|
||||
when(
|
||||
submissionTracker.track(any[CommandSubmission])(
|
||||
any[ExecutionContext],
|
||||
any[LoggingContext],
|
||||
)
|
||||
).thenReturn(
|
||||
Future.successful(
|
||||
Left(
|
||||
CompletionResponse.QueueCompletionFailure(
|
||||
CompletionResponse.TimeoutResponse("command ID")
|
||||
)
|
||||
val commands = someCommands()
|
||||
val submissionTracker = mock[Tracker]
|
||||
when(
|
||||
submissionTracker.track(any[CommandSubmission])(
|
||||
any[ExecutionContext],
|
||||
any[LoggingContext],
|
||||
)
|
||||
).thenReturn(
|
||||
Future.successful(
|
||||
Left(
|
||||
CompletionResponse.QueueCompletionFailure(
|
||||
CompletionResponse.TimeoutResponse("command ID")
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
openChannel(
|
||||
new ApiCommandService(
|
||||
UnimplementedTransactionServices,
|
||||
submissionTracker,
|
||||
switcher,
|
||||
)
|
||||
).use { stub =>
|
||||
val request = SubmitAndWaitRequest.of(Some(commands))
|
||||
stub.submitAndWaitForTransactionId(request).failed.map {
|
||||
case RpcProtoExtractors.Exception(RpcProtoExtractors.Status(`expectedStatusCode`)) =>
|
||||
succeed
|
||||
case unexpected => fail(s"Unexpected exception", unexpected)
|
||||
}
|
||||
openChannel(
|
||||
new ApiCommandService(
|
||||
UnimplementedTransactionServices,
|
||||
submissionTracker,
|
||||
)
|
||||
).use { stub =>
|
||||
val request = SubmitAndWaitRequest.of(Some(commands))
|
||||
stub.submitAndWaitForTransactionId(request).failed.map {
|
||||
case RpcProtoExtractors.Exception(RpcProtoExtractors.Status(Code.DEADLINE_EXCEEDED)) =>
|
||||
succeed
|
||||
case unexpected => fail(s"Unexpected exception", unexpected)
|
||||
}
|
||||
}
|
||||
|
||||
testTrackerTimeout(
|
||||
new ErrorCodesVersionSwitcher(enableSelfServiceErrorCodes = false),
|
||||
Code.ABORTED,
|
||||
)
|
||||
testTrackerTimeout(
|
||||
new ErrorCodesVersionSwitcher(enableSelfServiceErrorCodes = true),
|
||||
Code.DEADLINE_EXCEEDED,
|
||||
)
|
||||
}
|
||||
|
||||
"close the supplied tracker when closed" in {
|
||||
@ -180,11 +161,9 @@ class ApiCommandServiceSpec
|
||||
val service = new ApiCommandService(
|
||||
UnimplementedTransactionServices,
|
||||
submissionTracker,
|
||||
errorCodesVersionSwitcher,
|
||||
)
|
||||
|
||||
verifyZeroInteractions(submissionTracker)
|
||||
verifyZeroInteractions(errorCodesVersionSwitcher)
|
||||
|
||||
service.close()
|
||||
verify(submissionTracker).close()
|
||||
|
@ -4,7 +4,7 @@
|
||||
package com.daml.platform.apiserver.services
|
||||
|
||||
import com.codahale.metrics.MetricRegistry
|
||||
import com.daml.error.{ErrorCause, ErrorCodesVersionSwitcher}
|
||||
import com.daml.error.ErrorCause
|
||||
import com.daml.ledger.api.domain.{CommandId, Commands, LedgerId, PartyDetails}
|
||||
import com.daml.ledger.api.messages.command.submission.SubmitRequest
|
||||
import com.daml.ledger.api.{DeduplicationPeriod, DomainMocks}
|
||||
@ -35,7 +35,7 @@ import io.grpc.{Status, StatusRuntimeException}
|
||||
import org.mockito.{ArgumentMatchersSugar, MockitoSugar}
|
||||
import org.scalatest.flatspec.AsyncFlatSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.{Assertion, Inside}
|
||||
import org.scalatest.Inside
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.CompletableFuture.completedFuture
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
@ -221,16 +221,6 @@ class ApiSubmissionServiceSpec
|
||||
behavior of "submit"
|
||||
|
||||
it should "return proper gRPC status codes for DamlLf errors" in {
|
||||
testProperGrpcStatusCodesForDamlLfErrors(useSelfServiceErrorCodes = false)
|
||||
}
|
||||
|
||||
it should "return proper gRPC status codes for DamlLf errors (self service error codes a.k.a v2 error codes)" in {
|
||||
testProperGrpcStatusCodesForDamlLfErrors(useSelfServiceErrorCodes = true)
|
||||
}
|
||||
|
||||
private def testProperGrpcStatusCodesForDamlLfErrors(
|
||||
useSelfServiceErrorCodes: Boolean
|
||||
): Future[Assertion] = {
|
||||
// given
|
||||
val partyManagementService = mock[IndexPartyManagementService]
|
||||
val writeService = mock[WriteService]
|
||||
@ -245,7 +235,7 @@ class ApiSubmissionServiceSpec
|
||||
),
|
||||
None,
|
||||
)
|
||||
) -> ((Status.ABORTED, Status.NOT_FOUND)),
|
||||
) -> Status.NOT_FOUND,
|
||||
ErrorCause.DamlLf(
|
||||
LfError.Interpretation(
|
||||
LfError.Interpretation.DamlException(
|
||||
@ -255,12 +245,12 @@ class ApiSubmissionServiceSpec
|
||||
),
|
||||
None,
|
||||
)
|
||||
) -> ((Status.ABORTED, Status.ALREADY_EXISTS)),
|
||||
) -> Status.ALREADY_EXISTS,
|
||||
ErrorCause.DamlLf(
|
||||
LfError.Validation(
|
||||
LfError.Validation.ReplayMismatch(ReplayMismatch(null, null))
|
||||
)
|
||||
) -> ((Status.ABORTED, Status.INTERNAL)),
|
||||
) -> Status.INTERNAL,
|
||||
ErrorCause.DamlLf(
|
||||
LfError.Preprocessing(
|
||||
LfError.Preprocessing.Lookup(
|
||||
@ -270,7 +260,7 @@ class ApiSubmissionServiceSpec
|
||||
)
|
||||
)
|
||||
)
|
||||
) -> ((Status.INVALID_ARGUMENT, Status.INVALID_ARGUMENT)),
|
||||
) -> Status.INVALID_ARGUMENT,
|
||||
ErrorCause.DamlLf(
|
||||
LfError.Interpretation(
|
||||
LfError.Interpretation.DamlException(
|
||||
@ -281,22 +271,15 @@ class ApiSubmissionServiceSpec
|
||||
),
|
||||
None,
|
||||
)
|
||||
) -> ((Status.INVALID_ARGUMENT, Status.INVALID_ARGUMENT)),
|
||||
ErrorCause.LedgerTime(0) -> ((Status.ABORTED, Status.ABORTED)),
|
||||
).map { case (key, (statusV1, statusV2)) =>
|
||||
if (useSelfServiceErrorCodes) {
|
||||
(key, statusV2)
|
||||
} else {
|
||||
(key, statusV1)
|
||||
}
|
||||
}
|
||||
) -> Status.INVALID_ARGUMENT,
|
||||
ErrorCause.LedgerTime(0) -> Status.ABORTED,
|
||||
)
|
||||
|
||||
val service = newSubmissionService(
|
||||
writeService,
|
||||
partyManagementService,
|
||||
implicitPartyAllocation = true,
|
||||
commandExecutor = mockCommandExecutor,
|
||||
useSelfServiceErrorCodes = useSelfServiceErrorCodes,
|
||||
)
|
||||
|
||||
// when
|
||||
@ -379,7 +362,6 @@ object ApiSubmissionServiceSpec {
|
||||
partyManagementService: IndexPartyManagementService,
|
||||
implicitPartyAllocation: Boolean,
|
||||
commandExecutor: CommandExecutor = null,
|
||||
useSelfServiceErrorCodes: Boolean = true,
|
||||
checkOverloaded: TelemetryContext => Option[SubmissionResult] = _ => None,
|
||||
)(implicit
|
||||
executionContext: ExecutionContext,
|
||||
@ -400,9 +382,6 @@ object ApiSubmissionServiceSpec {
|
||||
commandExecutor = commandExecutor,
|
||||
configuration = ApiSubmissionService.Configuration(implicitPartyAllocation),
|
||||
metrics = new Metrics(new MetricRegistry),
|
||||
errorCodesVersionSwitcher = new ErrorCodesVersionSwitcher(
|
||||
enableSelfServiceErrorCodes = useSelfServiceErrorCodes
|
||||
),
|
||||
checkOverloaded = checkOverloaded,
|
||||
)
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
package com.daml.platform.apiserver.services.admin
|
||||
|
||||
import com.daml.error.ErrorCodesVersionSwitcher
|
||||
import com.daml.ledger.api.v1.admin.metering_report_service.{
|
||||
ApplicationMeteringReport,
|
||||
GetMeteringReportRequest,
|
||||
@ -31,8 +30,6 @@ class ApiMeteringReportServiceSpec extends AsyncWordSpec with Matchers with Mock
|
||||
private val someParticipantId = Ref.ParticipantId.assertFromString("test-participant")
|
||||
private implicit val loggingContext: LoggingContext = LoggingContext.ForTesting
|
||||
|
||||
private val switcher = new ErrorCodesVersionSwitcher(true)
|
||||
|
||||
private val appIdA = Ref.ApplicationId.assertFromString("AppA")
|
||||
private val appIdB = Ref.ApplicationId.assertFromString("AppB")
|
||||
|
||||
@ -91,7 +88,7 @@ class ApiMeteringReportServiceSpec extends AsyncWordSpec with Matchers with Mock
|
||||
val expectedGenTime = toProtoTimestamp(Timestamp.now().addMicros(-1000))
|
||||
|
||||
val underTest =
|
||||
new ApiMeteringReportService(someParticipantId, store, switcher, () => expectedGenTime)
|
||||
new ApiMeteringReportService(someParticipantId, store, () => expectedGenTime)
|
||||
|
||||
val from = Timestamp(10000)
|
||||
|
||||
@ -115,7 +112,7 @@ class ApiMeteringReportServiceSpec extends AsyncWordSpec with Matchers with Mock
|
||||
val expectedGenTime = toProtoTimestamp(Timestamp.now().addMicros(-1000))
|
||||
|
||||
val underTest =
|
||||
new ApiMeteringReportService(someParticipantId, store, switcher, () => expectedGenTime)
|
||||
new ApiMeteringReportService(someParticipantId, store, () => expectedGenTime)
|
||||
|
||||
val from = Timestamp(10000)
|
||||
val to = Timestamp(20000)
|
||||
@ -138,7 +135,7 @@ class ApiMeteringReportServiceSpec extends AsyncWordSpec with Matchers with Mock
|
||||
}
|
||||
|
||||
"fail if the from timestamp is unset" in {
|
||||
val underTest = new ApiMeteringReportService(someParticipantId, mock[MeteringStore], switcher)
|
||||
val underTest = new ApiMeteringReportService(someParticipantId, mock[MeteringStore])
|
||||
val request = GetMeteringReportRequest.defaultInstance
|
||||
underTest.getMeteringReport(request).failed.map { _ => succeed }
|
||||
}
|
||||
|
@ -70,58 +70,13 @@ class QueueBackedTrackerSpec
|
||||
|
||||
"input is submitted, and the queue is backpressuring" should {
|
||||
"return a RESOURCE_EXHAUSTED error" in {
|
||||
|
||||
def testIt(useSelfServiceErrorCodes: Boolean, expectedStatusCode: com.google.rpc.Code) = {
|
||||
|
||||
val tracker = new QueueBackedTracker(
|
||||
queue,
|
||||
Future.successful(Done),
|
||||
ErrorFactories(useSelfServiceErrorCodes),
|
||||
)
|
||||
|
||||
tracker.track(input(1))
|
||||
tracker.track(input(2)).map { completion =>
|
||||
completion should matchPattern {
|
||||
case Left(
|
||||
CompletionResponse
|
||||
.QueueSubmitFailure(RpcProtoExtractors.Status(`expectedStatusCode`))
|
||||
) =>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
testIt(useSelfServiceErrorCodes = false, com.google.rpc.Code.RESOURCE_EXHAUSTED)
|
||||
testIt(useSelfServiceErrorCodes = true, com.google.rpc.Code.ABORTED)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
"input is submitted, and the queue has been completed" should {
|
||||
"return an UNAVAILABLE error with self-service error codes enabled" in {
|
||||
val tracker = new QueueBackedTracker(
|
||||
queue,
|
||||
Future.successful(Done),
|
||||
ErrorFactories(useSelfServiceErrorCodes = true),
|
||||
)
|
||||
queue.complete()
|
||||
tracker.track(input(2)).map { completion =>
|
||||
completion should matchPattern {
|
||||
case Left(
|
||||
CompletionResponse
|
||||
.QueueSubmitFailure(RpcProtoExtractors.Status(com.google.rpc.Code.UNAVAILABLE))
|
||||
) =>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"return an ABORTED error with self-service error codes disabled" in {
|
||||
val tracker = new QueueBackedTracker(
|
||||
queue,
|
||||
Future.successful(Done),
|
||||
ErrorFactories(useSelfServiceErrorCodes = false),
|
||||
ErrorFactories(),
|
||||
)
|
||||
|
||||
queue.complete()
|
||||
tracker.track(input(1))
|
||||
tracker.track(input(2)).map { completion =>
|
||||
completion should matchPattern {
|
||||
case Left(
|
||||
@ -133,12 +88,31 @@ class QueueBackedTrackerSpec
|
||||
}
|
||||
}
|
||||
|
||||
"input is submitted, and the queue has failed" should {
|
||||
"return an INTERNAL error with self-service error codes enabled" in {
|
||||
"input is submitted, and the queue has been completed" should {
|
||||
"return an UNAVAILABLE error" in {
|
||||
val tracker = new QueueBackedTracker(
|
||||
queue,
|
||||
Future.successful(Done),
|
||||
ErrorFactories(useSelfServiceErrorCodes = true),
|
||||
ErrorFactories(),
|
||||
)
|
||||
queue.complete()
|
||||
tracker.track(input(2)).map { completion =>
|
||||
completion should matchPattern {
|
||||
case Left(
|
||||
CompletionResponse
|
||||
.QueueSubmitFailure(RpcProtoExtractors.Status(com.google.rpc.Code.UNAVAILABLE))
|
||||
) =>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"input is submitted, and the queue has failed" should {
|
||||
"return an INTERNAL error" in {
|
||||
val tracker = new QueueBackedTracker(
|
||||
queue,
|
||||
Future.successful(Done),
|
||||
ErrorFactories(),
|
||||
)
|
||||
|
||||
queue.fail(TestingException("The queue fails with this error."))
|
||||
@ -151,58 +125,35 @@ class QueueBackedTrackerSpec
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"return an ABORTED error with self-service error codes disabled" in {
|
||||
val tracker = new QueueBackedTracker(
|
||||
queue,
|
||||
Future.successful(Done),
|
||||
ErrorFactories(useSelfServiceErrorCodes = false),
|
||||
)
|
||||
|
||||
queue.fail(TestingException("The queue fails with this error."))
|
||||
tracker.track(input(2)).map { completion =>
|
||||
completion should matchPattern {
|
||||
case Left(
|
||||
CompletionResponse
|
||||
.QueueSubmitFailure(RpcProtoExtractors.Status(com.google.rpc.Code.ABORTED))
|
||||
) =>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"input is submitted, and the offer method has thrown an exception" should {
|
||||
"return an INTERNAL error" in {
|
||||
def testIt(useSelfServiceErrorCodes: Boolean) = {
|
||||
val fakeQueue = new BoundedSourceQueue[QueueInput] {
|
||||
override def offer(elem: QueueInput): QueueOfferResult =
|
||||
throw new IllegalArgumentException("test")
|
||||
val fakeQueue = new BoundedSourceQueue[QueueInput] {
|
||||
override def offer(elem: QueueInput): QueueOfferResult =
|
||||
throw new IllegalArgumentException("test")
|
||||
|
||||
override def size(): Int = 0
|
||||
override def size(): Int = 0
|
||||
|
||||
override def complete(): Unit = ()
|
||||
override def complete(): Unit = ()
|
||||
|
||||
override def fail(ex: Throwable): Unit = ()
|
||||
}
|
||||
val tracker = new QueueBackedTracker(
|
||||
fakeQueue,
|
||||
Future.successful(Done),
|
||||
ErrorFactories(useSelfServiceErrorCodes),
|
||||
)
|
||||
override def fail(ex: Throwable): Unit = ()
|
||||
}
|
||||
val tracker = new QueueBackedTracker(
|
||||
fakeQueue,
|
||||
Future.successful(Done),
|
||||
ErrorFactories(),
|
||||
)
|
||||
|
||||
tracker.track(input(1))
|
||||
tracker.track(input(2)).map { completion =>
|
||||
completion should matchPattern {
|
||||
case Left(
|
||||
CompletionResponse
|
||||
.QueueSubmitFailure(RpcProtoExtractors.Status(com.google.rpc.Code.INTERNAL))
|
||||
) =>
|
||||
}
|
||||
tracker.track(input(1))
|
||||
tracker.track(input(2)).map { completion =>
|
||||
completion should matchPattern {
|
||||
case Left(
|
||||
CompletionResponse
|
||||
.QueueSubmitFailure(RpcProtoExtractors.Status(com.google.rpc.Code.INTERNAL))
|
||||
) =>
|
||||
}
|
||||
}
|
||||
|
||||
testIt(useSelfServiceErrorCodes = false)
|
||||
testIt(useSelfServiceErrorCodes = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,6 @@ da_scala_library(
|
||||
"//ledger/ledger-api-health",
|
||||
"//ledger/ledger-configuration",
|
||||
"//ledger/ledger-configuration/protobuf:ledger_configuration_proto_java",
|
||||
"//ledger/ledger-grpc",
|
||||
"//ledger/ledger-offset",
|
||||
"//ledger/ledger-resources",
|
||||
"//ledger/metrics",
|
||||
|
@ -33,7 +33,6 @@ da_scala_library(
|
||||
"//daml-lf/transaction",
|
||||
"//language-support/scala/bindings",
|
||||
"//ledger/caching",
|
||||
"//ledger/error",
|
||||
"//ledger/ledger-api-auth",
|
||||
"//ledger/ledger-api-common",
|
||||
"//ledger/ledger-api-health",
|
||||
|
@ -47,7 +47,6 @@ trait ReadWriteServiceFactory {
|
||||
}
|
||||
|
||||
class KeyValueReadWriteFactory(
|
||||
config: Config[_],
|
||||
metrics: Metrics,
|
||||
ledgerReader: LedgerReader,
|
||||
ledgerWriter: LedgerWriter,
|
||||
@ -56,7 +55,6 @@ class KeyValueReadWriteFactory(
|
||||
KeyValueParticipantStateReader(
|
||||
ledgerReader,
|
||||
metrics,
|
||||
config.enableSelfServiceErrorCodes,
|
||||
)
|
||||
}
|
||||
|
||||
@ -70,7 +68,6 @@ class KeyValueReadWriteFactory(
|
||||
|
||||
class KeyValueDeduplicationSupportFactory(
|
||||
delegate: ReadWriteServiceFactory,
|
||||
config: Config[_],
|
||||
completionsService: IndexCompletionsService,
|
||||
)(implicit materializer: Materializer, ec: ExecutionContext)
|
||||
extends ReadWriteServiceFactory {
|
||||
@ -78,7 +75,7 @@ class KeyValueDeduplicationSupportFactory(
|
||||
|
||||
override def writeService(): WriteService = {
|
||||
val writeServiceDelegate = delegate.writeService()
|
||||
val errorFactories = ErrorFactories(config.enableSelfServiceErrorCodes)
|
||||
val errorFactories = ErrorFactories()
|
||||
new WriteServiceWithDeduplicationSupport(
|
||||
writeServiceDelegate,
|
||||
new DeduplicationPeriodSupport(
|
||||
|
@ -9,7 +9,6 @@ import akka.stream.Materializer
|
||||
import com.codahale.metrics.InstrumentedExecutorService
|
||||
import com.daml.api.util.TimeProvider
|
||||
import com.daml.buildinfo.BuildInfo
|
||||
import com.daml.error.ErrorCodesVersionSwitcher
|
||||
import com.daml.ledger.api.auth.{
|
||||
AuthServiceJWT,
|
||||
AuthServiceNone,
|
||||
@ -65,9 +64,7 @@ final class Runner[T <: ReadWriteService, Extra](
|
||||
val config = configProvider.manipulateConfig(originalConfig)
|
||||
config.mode match {
|
||||
case Mode.DumpIndexMetadata(jdbcUrls) =>
|
||||
val errorFactories = ErrorFactories(
|
||||
new ErrorCodesVersionSwitcher(originalConfig.enableSelfServiceErrorCodes)
|
||||
)
|
||||
val errorFactories = ErrorFactories()
|
||||
DumpIndexMetadata(jdbcUrls, errorFactories, name)
|
||||
sys.exit(0)
|
||||
case Mode.Run =>
|
||||
@ -230,7 +227,6 @@ final class Runner[T <: ReadWriteService, Extra](
|
||||
).acquire()
|
||||
factory = new KeyValueDeduplicationSupportFactory(
|
||||
ledgerFactory,
|
||||
config,
|
||||
indexService,
|
||||
)(implicitly, servicesExecutionContext)
|
||||
writeService = new TimedWriteService(factory.writeService(), metrics)
|
||||
|
@ -6,7 +6,7 @@ package com.daml.ledger.participant.state.kvutils
|
||||
import java.io.StringWriter
|
||||
import java.time.{Duration, Instant}
|
||||
|
||||
import com.daml.error.{ContextualizedErrorLogger, ValueSwitch}
|
||||
import com.daml.error.ContextualizedErrorLogger
|
||||
import com.daml.ledger.api.DeduplicationPeriod
|
||||
import com.daml.ledger.offset.Offset
|
||||
import com.daml.ledger.participant.state.kvutils.committer.transaction.Rejection
|
||||
@ -465,62 +465,61 @@ object Conversions {
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def decodeTransactionRejectionEntry(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
entry: DamlTransactionRejectionEntry
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): FinalReason =
|
||||
FinalReason(entry.getReasonCase match {
|
||||
case DamlTransactionRejectionEntry.ReasonCase.INVALID_LEDGER_TIME =>
|
||||
val rejection = entry.getInvalidLedgerTime
|
||||
invalidLedgerTimeStatus(entry, rejection, errorVersionSwitch)
|
||||
invalidLedgerTimeStatus(rejection)
|
||||
case DamlTransactionRejectionEntry.ReasonCase.DISPUTED =>
|
||||
val rejection = entry.getDisputed
|
||||
disputedStatus(entry, rejection, errorVersionSwitch)
|
||||
disputedStatus(rejection)
|
||||
case DamlTransactionRejectionEntry.ReasonCase.SUBMITTER_CANNOT_ACT_VIA_PARTICIPANT =>
|
||||
val rejection = entry.getSubmitterCannotActViaParticipant
|
||||
submitterCannotActViaParticipantStatus(entry, rejection, errorVersionSwitch)
|
||||
submitterCannotActViaParticipantStatus(rejection)
|
||||
case DamlTransactionRejectionEntry.ReasonCase.INCONSISTENT =>
|
||||
val rejection = entry.getInconsistent
|
||||
inconsistentStatus(entry, rejection, errorVersionSwitch)
|
||||
inconsistentStatus(rejection)
|
||||
case DamlTransactionRejectionEntry.ReasonCase.RESOURCES_EXHAUSTED =>
|
||||
val rejection = entry.getResourcesExhausted
|
||||
resourceExhaustedStatus(entry, rejection, errorVersionSwitch)
|
||||
resourceExhaustedStatus(rejection)
|
||||
case DamlTransactionRejectionEntry.ReasonCase.DUPLICATE_COMMAND =>
|
||||
duplicateCommandStatus(entry, errorVersionSwitch)
|
||||
duplicateCommandStatus(entry)
|
||||
case DamlTransactionRejectionEntry.ReasonCase.PARTY_NOT_KNOWN_ON_LEDGER =>
|
||||
val rejection = entry.getPartyNotKnownOnLedger
|
||||
partyNotKnownOnLedgerStatus(entry, rejection, errorVersionSwitch)
|
||||
partyNotKnownOnLedgerStatus(rejection)
|
||||
case DamlTransactionRejectionEntry.ReasonCase.VALIDATION_FAILURE =>
|
||||
val rejection = entry.getValidationFailure
|
||||
validationFailureStatus(entry, rejection, errorVersionSwitch)
|
||||
validationFailureStatus(rejection)
|
||||
case DamlTransactionRejectionEntry.ReasonCase.INTERNALLY_DUPLICATE_KEYS =>
|
||||
internallyDuplicateKeysStatus(entry, errorVersionSwitch)
|
||||
internallyDuplicateKeysStatus()
|
||||
case DamlTransactionRejectionEntry.ReasonCase.INTERNALLY_INCONSISTENT_KEYS =>
|
||||
internallyInconsistentKeysStatus(entry, errorVersionSwitch)
|
||||
internallyInconsistentKeysStatus()
|
||||
case DamlTransactionRejectionEntry.ReasonCase.EXTERNALLY_INCONSISTENT_CONTRACTS =>
|
||||
externallyInconsistentContractsStatus(entry, errorVersionSwitch)
|
||||
externallyInconsistentContractsStatus()
|
||||
case DamlTransactionRejectionEntry.ReasonCase.EXTERNALLY_DUPLICATE_KEYS =>
|
||||
externallyDuplicateKeysStatus(entry, errorVersionSwitch)
|
||||
externallyDuplicateKeysStatus()
|
||||
case DamlTransactionRejectionEntry.ReasonCase.EXTERNALLY_INCONSISTENT_KEYS =>
|
||||
externallyInconsistentKeysStatus(entry, errorVersionSwitch)
|
||||
externallyInconsistentKeysStatus()
|
||||
case DamlTransactionRejectionEntry.ReasonCase.MISSING_INPUT_STATE =>
|
||||
val rejection = entry.getMissingInputState
|
||||
missingInputStateStatus(entry, rejection, errorVersionSwitch)
|
||||
missingInputStateStatus(rejection)
|
||||
case DamlTransactionRejectionEntry.ReasonCase.RECORD_TIME_OUT_OF_RANGE =>
|
||||
val rejection = entry.getRecordTimeOutOfRange
|
||||
recordTimeOutOfRangeStatus(entry, rejection, errorVersionSwitch)
|
||||
recordTimeOutOfRangeStatus(rejection)
|
||||
case DamlTransactionRejectionEntry.ReasonCase.CAUSAL_MONOTONICITY_VIOLATED =>
|
||||
causalMonotonicityViolatedStatus(entry, errorVersionSwitch)
|
||||
causalMonotonicityViolatedStatus()
|
||||
case DamlTransactionRejectionEntry.ReasonCase.SUBMITTING_PARTY_NOT_KNOWN_ON_LEDGER =>
|
||||
val rejection = entry.getSubmittingPartyNotKnownOnLedger
|
||||
submittingPartyNotKnownOnLedgerStatus(entry, rejection, errorVersionSwitch)
|
||||
submittingPartyNotKnownOnLedgerStatus(rejection)
|
||||
case DamlTransactionRejectionEntry.ReasonCase.PARTIES_NOT_KNOWN_ON_LEDGER =>
|
||||
val rejection = entry.getPartiesNotKnownOnLedger
|
||||
partiesNotKnownOnLedgerStatus(entry, rejection, errorVersionSwitch)
|
||||
partiesNotKnownOnLedgerStatus(rejection)
|
||||
case DamlTransactionRejectionEntry.ReasonCase.INVALID_PARTICIPANT_STATE =>
|
||||
val rejection = entry.getInvalidParticipantState
|
||||
invalidParticipantStateStatus(entry, rejection, errorVersionSwitch)
|
||||
invalidParticipantStateStatus(rejection)
|
||||
case DamlTransactionRejectionEntry.ReasonCase.REASON_NOT_SET =>
|
||||
rejectionReasonNotSetStatus(entry, errorVersionSwitch)
|
||||
rejectionReasonNotSetStatus()
|
||||
})
|
||||
|
||||
def objectToJsonString(obj: Object): String = {
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
package com.daml.ledger.participant.state.kvutils
|
||||
|
||||
import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger, ValueSwitch}
|
||||
import com.daml.error.{ContextualizedErrorLogger, DamlContextualizedErrorLogger}
|
||||
import com.daml.ledger.configuration.Configuration
|
||||
import com.daml.ledger.participant.state.kvutils.Conversions._
|
||||
import com.daml.ledger.participant.state.kvutils.store.events.PackageUpload.DamlPackageUploadRejectionEntry
|
||||
@ -52,7 +52,6 @@ object KeyValueConsumption {
|
||||
*
|
||||
* @param entryId: The log entry identifier.
|
||||
* @param entry: The log entry.
|
||||
* @param errorVersionSwitch: Decides between v1 and v2 (self-service) errors.
|
||||
* @return [[Update]]s constructed from log entry.
|
||||
*/
|
||||
// TODO(BH): add participantId to ensure participant id matches in DamlLogEntry
|
||||
@ -60,7 +59,6 @@ object KeyValueConsumption {
|
||||
def logEntryToUpdate(
|
||||
entryId: DamlLogEntryId,
|
||||
entry: DamlLogEntry,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
recordTimeForUpdate: Option[Timestamp] = None,
|
||||
)(loggingContext: LoggingContext): List[Update] = {
|
||||
implicit val logContext: LoggingContext = loggingContext
|
||||
@ -218,7 +216,6 @@ object KeyValueConsumption {
|
||||
transactionRejectionEntryToUpdate(
|
||||
recordTime,
|
||||
rejectionEntry,
|
||||
errorVersionSwitch,
|
||||
)(contextualizedErrorLogger(loggingContext, rejectionEntry))
|
||||
)
|
||||
|
||||
@ -226,7 +223,6 @@ object KeyValueConsumption {
|
||||
outOfTimeBoundsEntryToUpdate(
|
||||
recordTime,
|
||||
entry.getOutOfTimeBoundsEntry,
|
||||
errorVersionSwitch,
|
||||
).toList
|
||||
|
||||
case DamlLogEntry.PayloadCase.TIME_UPDATE_ENTRY =>
|
||||
@ -251,9 +247,8 @@ object KeyValueConsumption {
|
||||
private def transactionRejectionEntryToUpdate(
|
||||
recordTime: Timestamp,
|
||||
rejEntry: DamlTransactionRejectionEntry,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Update = {
|
||||
val reason = Conversions.decodeTransactionRejectionEntry(rejEntry, errorVersionSwitch)
|
||||
val reason = Conversions.decodeTransactionRejectionEntry(rejEntry)
|
||||
Update.CommandRejected(
|
||||
recordTime = recordTime,
|
||||
completionInfo = parseCompletionInfo(recordTime, rejEntry.getSubmitterInfo),
|
||||
@ -350,7 +345,6 @@ object KeyValueConsumption {
|
||||
private[kvutils] def outOfTimeBoundsEntryToUpdate(
|
||||
recordTime: Timestamp,
|
||||
outOfTimeBoundsEntry: DamlOutOfTimeBoundsEntry,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
)(implicit loggingContext: LoggingContext): Option[Update] = {
|
||||
val timeBounds = parseTimeBounds(outOfTimeBoundsEntry)
|
||||
val deduplicated = timeBounds.deduplicateUntil.exists(recordTime <= _)
|
||||
@ -366,7 +360,6 @@ object KeyValueConsumption {
|
||||
duplicateCommandsRejectionUpdate(
|
||||
recordTime,
|
||||
rejectionEntry,
|
||||
errorVersionSwitch,
|
||||
None, // Not available for historical entries
|
||||
)(contextualizedErrorLogger(loggingContext, rejectionEntry))
|
||||
)
|
||||
@ -383,7 +376,6 @@ object KeyValueConsumption {
|
||||
timeBounds.tooEarlyUntil,
|
||||
timeBounds.tooLateFrom,
|
||||
rejectionEntry,
|
||||
errorVersionSwitch,
|
||||
)(contextualizedErrorLogger(loggingContext, rejectionEntry))
|
||||
)
|
||||
|
||||
|
@ -5,7 +5,6 @@ package com.daml.ledger.participant.state.kvutils.api
|
||||
|
||||
import akka.NotUsed
|
||||
import akka.stream.scaladsl.Source
|
||||
import com.daml.error.ValueSwitch
|
||||
import com.daml.ledger.api.health.HealthStatus
|
||||
import com.daml.ledger.configuration.LedgerInitialConditions
|
||||
import com.daml.ledger.offset.Offset
|
||||
@ -29,11 +28,9 @@ import com.daml.metrics.{Metrics, Timed}
|
||||
class KeyValueParticipantStateReader private[api] (
|
||||
reader: LedgerReader,
|
||||
metrics: Metrics,
|
||||
enableSelfServiceErrorCodes: Boolean,
|
||||
logEntryToUpdate: (
|
||||
DamlLogEntryId,
|
||||
DamlLogEntry,
|
||||
ValueSwitch,
|
||||
Option[Timestamp],
|
||||
) => LoggingContext => List[Update],
|
||||
timeUpdatesProvider: TimeUpdatesProvider,
|
||||
@ -42,8 +39,6 @@ class KeyValueParticipantStateReader private[api] (
|
||||
|
||||
import KeyValueParticipantStateReader._
|
||||
|
||||
private val errorVersionSwitch = new ValueSwitch(enableSelfServiceErrorCodes)
|
||||
|
||||
override def ledgerInitialConditions(): Source[LedgerInitialConditions, NotUsed] =
|
||||
Source.single(createLedgerInitialConditions())
|
||||
|
||||
@ -65,7 +60,6 @@ class KeyValueParticipantStateReader private[api] (
|
||||
logEntryToUpdate(
|
||||
logEntryId,
|
||||
logEntry,
|
||||
errorVersionSwitch,
|
||||
timeUpdatesProvider(),
|
||||
)(loggingContext)
|
||||
val updatesWithOffsets =
|
||||
@ -102,14 +96,12 @@ object KeyValueParticipantStateReader {
|
||||
def apply(
|
||||
reader: LedgerReader,
|
||||
metrics: Metrics,
|
||||
enableSelfServiceErrorCodes: Boolean,
|
||||
timeUpdatesProvider: TimeUpdatesProvider = TimeUpdatesProvider.ReasonableDefault,
|
||||
failOnUnexpectedEvent: Boolean = true,
|
||||
): KeyValueParticipantStateReader =
|
||||
new KeyValueParticipantStateReader(
|
||||
reader,
|
||||
metrics,
|
||||
enableSelfServiceErrorCodes,
|
||||
KeyValueConsumption.logEntryToUpdate,
|
||||
timeUpdatesProvider,
|
||||
failOnUnexpectedEvent,
|
||||
|
@ -115,12 +115,11 @@ object WriteServiceWithDeduplicationSupport {
|
||||
def apply(
|
||||
delegate: WriteService,
|
||||
indexCompletionService: IndexCompletionsService,
|
||||
enableSelfServiceErrorCodes: Boolean,
|
||||
)(implicit
|
||||
materializer: Materializer,
|
||||
ec: ExecutionContext,
|
||||
): WriteServiceWithDeduplicationSupport = {
|
||||
val errorFactories = ErrorFactories(enableSelfServiceErrorCodes)
|
||||
val errorFactories = ErrorFactories()
|
||||
new WriteServiceWithDeduplicationSupport(
|
||||
delegate,
|
||||
new DeduplicationPeriodSupport(
|
||||
|
@ -60,9 +60,7 @@ class DeduplicationPeriodSupport(
|
||||
)
|
||||
Left(
|
||||
errorFactories.invalidDeduplicationPeriod(
|
||||
"deduplication_period",
|
||||
s"Cannot convert deduplication offset to duration because there is no completion at given offset $offset.",
|
||||
Some(false),
|
||||
None,
|
||||
)
|
||||
)
|
||||
|
@ -5,11 +5,9 @@ package com.daml.ledger.participant.state.kvutils.updates
|
||||
|
||||
import com.daml.error.definitions.LedgerApiErrors
|
||||
|
||||
import java.io.StringWriter
|
||||
import java.time.Instant
|
||||
import com.daml.error.{ContextualizedErrorLogger, ValueSwitch}
|
||||
import com.daml.error.ContextualizedErrorLogger
|
||||
import com.daml.grpc.GrpcStatus
|
||||
import com.daml.ledger.grpc.GrpcStatuses
|
||||
import com.daml.ledger.participant.state.kvutils.Conversions.parseCompletionInfo
|
||||
import com.daml.ledger.participant.state.kvutils.committer.transaction.Rejection.{
|
||||
ExternallyInconsistentTransaction,
|
||||
@ -20,27 +18,19 @@ import com.daml.ledger.participant.state.kvutils.store.events._
|
||||
import com.daml.ledger.participant.state.v2.Update
|
||||
import com.daml.ledger.participant.state.v2.Update.CommandRejected.FinalReason
|
||||
import com.daml.lf.data.Time.Timestamp
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.google.protobuf.ProtocolStringList
|
||||
import com.google.protobuf.any.{Any => AnyProto}
|
||||
import com.google.rpc.code.Code
|
||||
import com.google.rpc.error_details.ErrorInfo
|
||||
import com.google.rpc.status.Status
|
||||
|
||||
import scala.annotation.nowarn
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
/** Utilities for converting between rejection log entries and updates and/or gRPC statuses.
|
||||
*/
|
||||
private[kvutils] object TransactionRejections {
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def invalidRecordTimeRejectionUpdate(
|
||||
recordTime: Timestamp,
|
||||
tooEarlyUntil: Option[Timestamp],
|
||||
tooLateFrom: Option[Timestamp],
|
||||
rejectionEntry: DamlTransactionRejectionEntry,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Update.CommandRejected = {
|
||||
Update.CommandRejected(
|
||||
recordTime = recordTime,
|
||||
@ -49,28 +39,22 @@ private[kvutils] object TransactionRejections {
|
||||
rejectionEntry.getSubmitterInfo,
|
||||
),
|
||||
reasonTemplate = FinalReason(
|
||||
errorVersionSwitch.choose(
|
||||
V1.invalidRecordTimeRejectionStatus(
|
||||
rejectionEntry,
|
||||
invalidRecordTimeReason(recordTime, tooEarlyUntil, tooEarlyUntil),
|
||||
Code.ABORTED,
|
||||
),
|
||||
V2.invalidRecordTimeRejectionStatus(
|
||||
rejectionEntry,
|
||||
recordTime,
|
||||
tooEarlyUntil,
|
||||
tooLateFrom,
|
||||
),
|
||||
)
|
||||
KVErrors.Time.InvalidRecordTime
|
||||
.Reject(
|
||||
rejectionEntry.getDefiniteAnswer,
|
||||
invalidRecordTimeReason(recordTime, tooEarlyUntil, tooLateFrom),
|
||||
recordTime.toInstant,
|
||||
tooEarlyUntil.map(_.toInstant),
|
||||
tooLateFrom.map(_.toInstant),
|
||||
)
|
||||
.asStatus
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def duplicateCommandsRejectionUpdate(
|
||||
recordTime: Timestamp,
|
||||
rejectionEntry: DamlTransactionRejectionEntry,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
existingCommandSubmissionId: Option[String],
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Update.CommandRejected = {
|
||||
val definiteAnswer = rejectionEntry.getDefiniteAnswer
|
||||
@ -81,98 +65,57 @@ private[kvutils] object TransactionRejections {
|
||||
rejectionEntry.getSubmitterInfo,
|
||||
),
|
||||
reasonTemplate = FinalReason(
|
||||
errorVersionSwitch.choose(
|
||||
V1.duplicateCommandsRejectionStatus(definiteAnswer, Code.ALREADY_EXISTS),
|
||||
V2.duplicateCommandsRejectionStatus(definiteAnswer, existingCommandSubmissionId),
|
||||
)
|
||||
duplicateCommandsRejectionStatus(definiteAnswer, existingCommandSubmissionId)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def rejectionReasonNotSetStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
errorVersionSwitch.choose(
|
||||
V1.status(entry, Code.UNKNOWN, "No reason set for rejection"),
|
||||
V2.rejectionReasonNotSetStatus(),
|
||||
)
|
||||
def rejectionReasonNotSetStatus()(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
KVErrors.Internal.RejectionReasonNotSet
|
||||
.Reject()
|
||||
.asStatus
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def invalidParticipantStateStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
rejection: InvalidParticipantState,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
rejection: InvalidParticipantState
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status = {
|
||||
val details = rejection.getDetails
|
||||
val metadata = rejection.getMetadataMap.asScala.toMap
|
||||
errorVersionSwitch.choose(
|
||||
V1.status(
|
||||
entry,
|
||||
Code.INVALID_ARGUMENT,
|
||||
s"Disputed: $details",
|
||||
metadata,
|
||||
),
|
||||
V2.invalidParticipantStateStatus(details, metadata),
|
||||
)
|
||||
KVErrors.Internal.InvalidParticipantState
|
||||
.Reject(details, metadata)
|
||||
.asStatus
|
||||
}
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def partiesNotKnownOnLedgerStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
rejection: PartiesNotKnownOnLedger,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
rejection: PartiesNotKnownOnLedger
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status = {
|
||||
val parties = rejection.getPartiesList
|
||||
errorVersionSwitch.choose(
|
||||
V1.status(
|
||||
entry,
|
||||
Code.INVALID_ARGUMENT,
|
||||
s"Party not known on ledger: Parties not known on ledger ${parties.asScala.mkString("[", ",", "]")}",
|
||||
Map("parties" -> toJsonString(parties)),
|
||||
),
|
||||
V2.partiesNotKnownOnLedgerStatus(parties.asScala.toSeq),
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.PartyNotKnownOnLedger
|
||||
.Reject(parties.asScala.toSet)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
}
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def submittingPartyNotKnownOnLedgerStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
rejection: SubmittingPartyNotKnownOnLedger,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
rejection: SubmittingPartyNotKnownOnLedger
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status = {
|
||||
val submitter = rejection.getSubmitterParty
|
||||
errorVersionSwitch.choose(
|
||||
V1.status(
|
||||
entry,
|
||||
Code.INVALID_ARGUMENT,
|
||||
s"Party not known on ledger: Submitting party '$submitter' not known",
|
||||
Map("submitter_party" -> submitter),
|
||||
),
|
||||
V2.submittingPartyNotKnownOnLedgerStatus(submitter),
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.SubmittingPartyNotKnownOnLedger
|
||||
.Reject(submitter)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
}
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def causalMonotonicityViolatedStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
errorVersionSwitch.choose(
|
||||
V1.status(
|
||||
entry,
|
||||
Code.ABORTED,
|
||||
"Invalid ledger time: Causal monotonicity violated",
|
||||
),
|
||||
V2.causalMonotonicityViolatedStatus(),
|
||||
)
|
||||
def causalMonotonicityViolatedStatus()(implicit
|
||||
loggingContext: ContextualizedErrorLogger
|
||||
): Status =
|
||||
KVErrors.Time.CausalMonotonicityViolated
|
||||
.Reject()
|
||||
.asStatus
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def recordTimeOutOfRangeStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
rejection: RecordTimeOutOfRange,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
rejection: RecordTimeOutOfRange
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status = {
|
||||
val minRecordTime = Instant
|
||||
.ofEpochSecond(
|
||||
@ -184,530 +127,183 @@ private[kvutils] object TransactionRejections {
|
||||
rejection.getMaximumRecordTime.getSeconds,
|
||||
rejection.getMaximumRecordTime.getNanos.toLong,
|
||||
)
|
||||
errorVersionSwitch.choose(
|
||||
V1.status(
|
||||
entry,
|
||||
Code.ABORTED,
|
||||
s"Invalid ledger time: Record time is outside of valid range [${rejection.getMinimumRecordTime}, ${rejection.getMaximumRecordTime}]",
|
||||
Map(
|
||||
"minimum_record_time" -> minRecordTime.toString,
|
||||
"maximum_record_time" -> maxRecordTime.toString,
|
||||
),
|
||||
),
|
||||
V2.recordTimeOutOfRangeStatus(minRecordTime, maxRecordTime),
|
||||
)
|
||||
KVErrors.Time.RecordTimeOutOfRange
|
||||
.Reject(minRecordTime, maxRecordTime)
|
||||
.asStatus
|
||||
}
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def externallyDuplicateKeysStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
errorVersionSwitch.choose(
|
||||
V1.status(
|
||||
entry,
|
||||
Code.ABORTED,
|
||||
s"Inconsistent: ${ExternallyInconsistentTransaction.DuplicateKeys.description}",
|
||||
),
|
||||
V2.externallyDuplicateKeysStatus(),
|
||||
def externallyDuplicateKeysStatus()(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.DuplicateContractKey
|
||||
.Reject(ExternallyInconsistentTransaction.DuplicateKeys.description)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def externallyInconsistentKeysStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
errorVersionSwitch.choose(
|
||||
V1.status(
|
||||
entry,
|
||||
Code.ABORTED,
|
||||
s"Inconsistent: ${ExternallyInconsistentTransaction.InconsistentKeys.description}",
|
||||
),
|
||||
V2.externallyInconsistentKeysStatus(),
|
||||
def externallyInconsistentKeysStatus()(implicit
|
||||
loggingContext: ContextualizedErrorLogger
|
||||
): Status =
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.InconsistentContractKey
|
||||
.Reject(ExternallyInconsistentTransaction.InconsistentKeys.description)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def externallyInconsistentContractsStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
errorVersionSwitch.choose(
|
||||
V1.status(
|
||||
entry,
|
||||
Code.ABORTED,
|
||||
s"Inconsistent: ${ExternallyInconsistentTransaction.InconsistentContracts.description}",
|
||||
),
|
||||
V2.externallyInconsistentContractsStatus(),
|
||||
def externallyInconsistentContractsStatus()(implicit
|
||||
loggingContext: ContextualizedErrorLogger
|
||||
): Status =
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.InconsistentContracts
|
||||
.Reject(ExternallyInconsistentTransaction.InconsistentContracts.description)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def missingInputStateStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
rejection: MissingInputState,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
rejection: MissingInputState
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status = {
|
||||
val key = rejection.getKey.toString
|
||||
errorVersionSwitch.choose(
|
||||
V1.status(
|
||||
entry,
|
||||
Code.ABORTED,
|
||||
s"Inconsistent: Missing input state for key $key",
|
||||
Map("key" -> key),
|
||||
),
|
||||
V2.missingInputStateStatus(key),
|
||||
)
|
||||
KVErrors.Internal.MissingInputState
|
||||
.Reject(key)
|
||||
.asStatus
|
||||
}
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def internallyInconsistentKeysStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
errorVersionSwitch.choose(
|
||||
V1.status(
|
||||
entry,
|
||||
Code.INVALID_ARGUMENT,
|
||||
s"Disputed: ${InternallyInconsistentTransaction.InconsistentKeys.description}",
|
||||
),
|
||||
V2.internallyInconsistentKeysStatus(), // Code.INTERNAL as it should have been caught by the participant
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.Internal.InternallyInconsistentKeys
|
||||
.Reject(InternallyInconsistentTransaction.InconsistentKeys.description)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def internallyDuplicateKeysStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
errorVersionSwitch.choose(
|
||||
V1.status(
|
||||
entry,
|
||||
Code.INVALID_ARGUMENT,
|
||||
s"Disputed: ${InternallyInconsistentTransaction.DuplicateKeys.description}",
|
||||
),
|
||||
V2.internallyDuplicateKeysStatus(), // Code.INTERNAL as it should have been caught by the participant
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.Internal.InternallyDuplicateKeys
|
||||
.Reject(InternallyInconsistentTransaction.DuplicateKeys.description)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def validationFailureStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
rejection: ValidationFailure,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
rejection: ValidationFailure
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status = {
|
||||
val details = rejection.getDetails
|
||||
errorVersionSwitch.choose(
|
||||
V1.disputedStatus(entry, details, Code.INVALID_ARGUMENT),
|
||||
V2.validationFailureStatus(details),
|
||||
)
|
||||
KVErrors.Consistency.ValidationFailure
|
||||
.Reject(details)
|
||||
.asStatus
|
||||
}
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def duplicateCommandStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
entry: DamlTransactionRejectionEntry
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status = {
|
||||
val rejectionReason = entry.getDuplicateCommand
|
||||
errorVersionSwitch.choose(
|
||||
V1.status(
|
||||
entry,
|
||||
Code.ALREADY_EXISTS,
|
||||
"Duplicate commands",
|
||||
),
|
||||
V2.duplicateCommandsRejectionStatus(existingCommandSubmissionId =
|
||||
Some(rejectionReason.getSubmissionId).filter(_.nonEmpty)
|
||||
),
|
||||
duplicateCommandsRejectionStatus(existingCommandSubmissionId =
|
||||
Some(rejectionReason.getSubmissionId).filter(_.nonEmpty)
|
||||
)
|
||||
}
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def submitterCannotActViaParticipantStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
rejection: SubmitterCannotActViaParticipant,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
rejection: SubmitterCannotActViaParticipant
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status = {
|
||||
val submitter = rejection.getSubmitterParty
|
||||
val participantId = rejection.getParticipantId
|
||||
val details = rejection.getDetails
|
||||
errorVersionSwitch.choose(
|
||||
V1.status(
|
||||
entry,
|
||||
Code.PERMISSION_DENIED,
|
||||
s"Submitter cannot act via participant: $details",
|
||||
Map(
|
||||
"submitter_party" -> submitter,
|
||||
"participant_id" -> participantId,
|
||||
),
|
||||
),
|
||||
V2.submitterCannotActViaParticipantStatus(details, submitter, participantId),
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.SubmitterCannotActViaParticipant
|
||||
.RejectWithSubmitterAndParticipantId(
|
||||
details,
|
||||
submitter,
|
||||
participantId,
|
||||
)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
}
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def invalidLedgerTimeStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
rejection: InvalidLedgerTime,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
rejection: InvalidLedgerTime
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status = {
|
||||
val details = rejection.getDetails
|
||||
val ledgerTime = rejection.getLedgerTime
|
||||
val ledgerTimeLowerBound = rejection.getLowerBound
|
||||
val ledgerTimeUpperBound = rejection.getUpperBound
|
||||
|
||||
errorVersionSwitch.choose(
|
||||
V1.status(
|
||||
entry,
|
||||
Code.ABORTED,
|
||||
s"Invalid ledger time: $details",
|
||||
Map(
|
||||
"ledger_time" -> ledgerTime.toString,
|
||||
"lower_bound" -> ledgerTimeLowerBound.toString,
|
||||
"upper_bound" -> ledgerTimeUpperBound.toString,
|
||||
),
|
||||
),
|
||||
V2.invalidLedgerTimeStatus(
|
||||
details,
|
||||
ledgerTime = Instant
|
||||
.ofEpochSecond(
|
||||
ledgerTime.getSeconds,
|
||||
ledgerTime.getNanos.toLong,
|
||||
),
|
||||
ledgerTimeLowerBound = Instant
|
||||
.ofEpochSecond(
|
||||
ledgerTimeLowerBound.getSeconds,
|
||||
ledgerTimeLowerBound.getNanos.toLong,
|
||||
),
|
||||
ledgerTimeUpperBound = Instant
|
||||
.ofEpochSecond(
|
||||
ledgerTimeUpperBound.getSeconds,
|
||||
ledgerTimeUpperBound.getNanos.toLong,
|
||||
),
|
||||
),
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.InvalidLedgerTime
|
||||
.RejectEnriched(
|
||||
cause = details,
|
||||
ledger_time = Instant
|
||||
.ofEpochSecond(
|
||||
ledgerTime.getSeconds,
|
||||
ledgerTime.getNanos.toLong,
|
||||
),
|
||||
ledger_time_lower_bound = Instant
|
||||
.ofEpochSecond(
|
||||
ledgerTimeLowerBound.getSeconds,
|
||||
ledgerTimeLowerBound.getNanos.toLong,
|
||||
),
|
||||
ledger_time_upper_bound = Instant
|
||||
.ofEpochSecond(
|
||||
ledgerTimeUpperBound.getSeconds,
|
||||
ledgerTimeUpperBound.getNanos.toLong,
|
||||
),
|
||||
)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
}
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
def resourceExhaustedStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
rejection: ResourcesExhausted,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
rejection: ResourcesExhausted
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status = {
|
||||
val details = rejection.getDetails
|
||||
errorVersionSwitch.choose(
|
||||
V1.status(
|
||||
entry,
|
||||
Code.ABORTED,
|
||||
s"Resources exhausted: $details",
|
||||
),
|
||||
V2.resourceExhaustedStatus(details),
|
||||
)
|
||||
KVErrors.Resources.ResourceExhausted
|
||||
.Reject(details)
|
||||
.asStatus
|
||||
}
|
||||
|
||||
@deprecated
|
||||
def partyNotKnownOnLedgerStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
rejection: PartyNotKnownOnLedger,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
rejection: PartyNotKnownOnLedger
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status = {
|
||||
val details = rejection.getDetails
|
||||
errorVersionSwitch.choose(
|
||||
V1.status(
|
||||
entry,
|
||||
Code.INVALID_ARGUMENT,
|
||||
s"Party not known on ledger: $details",
|
||||
),
|
||||
V2.partyNotKnownOnLedgerStatus(details),
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.PartyNotKnownOnLedger
|
||||
.RejectDeprecated(details)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
}
|
||||
|
||||
@deprecated
|
||||
def inconsistentStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
rejection: Inconsistent,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
rejection: Inconsistent
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status = {
|
||||
val details = rejection.getDetails
|
||||
errorVersionSwitch.choose(
|
||||
V1.status(
|
||||
entry,
|
||||
Code.ABORTED,
|
||||
s"Inconsistent: $details",
|
||||
),
|
||||
V2.inconsistentStatus(details),
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.Inconsistent
|
||||
.Reject(details)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
}
|
||||
|
||||
@deprecated
|
||||
def disputedStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
rejection: Disputed,
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
rejection: Disputed
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status = {
|
||||
val details = rejection.getDetails
|
||||
errorVersionSwitch.choose(
|
||||
V1.disputedStatus(entry, details, Code.INVALID_ARGUMENT),
|
||||
V2.disputedStatus(details),
|
||||
)
|
||||
}
|
||||
|
||||
private object V2 {
|
||||
|
||||
def externallyDuplicateKeysStatus()(implicit
|
||||
loggingContext: ContextualizedErrorLogger
|
||||
): Status =
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.DuplicateContractKey
|
||||
.Reject(ExternallyInconsistentTransaction.DuplicateKeys.description)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
def externallyInconsistentKeysStatus(
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.InconsistentContractKey
|
||||
.Reject(ExternallyInconsistentTransaction.InconsistentKeys.description)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
def externallyInconsistentContractsStatus(
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.InconsistentContracts
|
||||
.Reject(ExternallyInconsistentTransaction.InconsistentContracts.description)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
def submitterCannotActViaParticipantStatus(
|
||||
details: String,
|
||||
submitter: String,
|
||||
participantId: String,
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.SubmitterCannotActViaParticipant
|
||||
.RejectWithSubmitterAndParticipantId(
|
||||
details,
|
||||
submitter,
|
||||
participantId,
|
||||
)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
def recordTimeOutOfRangeStatus(
|
||||
minimumRecordTime: Instant,
|
||||
maximumRecordTime: Instant,
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
KVErrors.Time.RecordTimeOutOfRange
|
||||
.Reject(minimumRecordTime, maximumRecordTime)
|
||||
.asStatus
|
||||
|
||||
def invalidRecordTimeRejectionStatus(
|
||||
rejectionEntry: DamlTransactionRejectionEntry,
|
||||
recordTime: Timestamp,
|
||||
tooEarlyUntil: Option[Timestamp],
|
||||
tooLateFrom: Option[Timestamp],
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
KVErrors.Time.InvalidRecordTime
|
||||
.Reject(
|
||||
rejectionEntry.getDefiniteAnswer,
|
||||
invalidRecordTimeReason(recordTime, tooEarlyUntil, tooLateFrom),
|
||||
recordTime.toInstant,
|
||||
tooEarlyUntil.map(_.toInstant),
|
||||
tooLateFrom.map(_.toInstant),
|
||||
)
|
||||
.asStatus
|
||||
|
||||
def causalMonotonicityViolatedStatus(
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
KVErrors.Time.CausalMonotonicityViolated
|
||||
.Reject()
|
||||
.asStatus
|
||||
|
||||
def duplicateCommandsRejectionStatus(
|
||||
definiteAnswer: Boolean = false,
|
||||
existingCommandSubmissionId: Option[String],
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.DuplicateCommand
|
||||
.Reject(definiteAnswer, existingCommandSubmissionId)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
def rejectionReasonNotSetStatus(
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
KVErrors.Internal.RejectionReasonNotSet
|
||||
.Reject()
|
||||
.asStatus
|
||||
|
||||
def invalidParticipantStateStatus(
|
||||
details: String,
|
||||
metadata: Map[String, String],
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
KVErrors.Internal.InvalidParticipantState
|
||||
.Reject(details, metadata)
|
||||
.asStatus
|
||||
|
||||
def validationFailureStatus(
|
||||
details: String
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
KVErrors.Consistency.ValidationFailure
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.Disputed
|
||||
.Reject(details)
|
||||
.asStatus
|
||||
|
||||
def missingInputStateStatus(
|
||||
key: String
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
KVErrors.Internal.MissingInputState
|
||||
.Reject(key)
|
||||
.asStatus
|
||||
|
||||
def internallyInconsistentKeysStatus(
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.Internal.InternallyInconsistentKeys
|
||||
.Reject(InternallyInconsistentTransaction.InconsistentKeys.description)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
def internallyDuplicateKeysStatus(
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.Internal.InternallyDuplicateKeys
|
||||
.Reject(InternallyInconsistentTransaction.DuplicateKeys.description)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
def submittingPartyNotKnownOnLedgerStatus(
|
||||
submitter: String
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.SubmittingPartyNotKnownOnLedger
|
||||
.Reject(submitter)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
def partiesNotKnownOnLedgerStatus(
|
||||
parties: Seq[String]
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.PartyNotKnownOnLedger
|
||||
.Reject(parties.toSet)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
def resourceExhaustedStatus(
|
||||
details: String
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
KVErrors.Resources.ResourceExhausted
|
||||
.Reject(details)
|
||||
.asStatus
|
||||
|
||||
@deprecated
|
||||
def invalidLedgerTimeStatus(
|
||||
details: String,
|
||||
ledgerTime: Instant,
|
||||
ledgerTimeLowerBound: Instant,
|
||||
ledgerTimeUpperBound: Instant,
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.InvalidLedgerTime
|
||||
.RejectEnriched(details, ledgerTime, ledgerTimeLowerBound, ledgerTimeUpperBound)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
@deprecated
|
||||
def partyNotKnownOnLedgerStatus(
|
||||
details: String
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.PartyNotKnownOnLedger
|
||||
.RejectDeprecated(details)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
@deprecated
|
||||
def inconsistentStatus(
|
||||
details: String
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.Inconsistent
|
||||
.Reject(details)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
@deprecated
|
||||
def disputedStatus(
|
||||
details: String
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.WriteServiceRejections.Disputed
|
||||
.Reject(details)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
}
|
||||
|
||||
@deprecated
|
||||
private object V1 {
|
||||
def status(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
code: Code,
|
||||
message: String,
|
||||
additionalMetadata: Map[String, String] = Map.empty,
|
||||
): Status = Status.of(
|
||||
code.value,
|
||||
message,
|
||||
Seq(
|
||||
AnyProto.pack[ErrorInfo](
|
||||
ErrorInfo(metadata =
|
||||
additionalMetadata + (GrpcStatuses.DefiniteAnswerKey -> entry.getDefiniteAnswer.toString)
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
def disputedStatus(
|
||||
entry: DamlTransactionRejectionEntry,
|
||||
rejectionString: String,
|
||||
code: Code,
|
||||
): Status = status(
|
||||
entry,
|
||||
code,
|
||||
s"Disputed: $rejectionString",
|
||||
)
|
||||
|
||||
def invalidRecordTimeRejectionStatus(
|
||||
rejectionEntry: DamlTransactionRejectionEntry,
|
||||
reason: String,
|
||||
errorCode: Code,
|
||||
): Status = Status.of(
|
||||
errorCode.value,
|
||||
reason,
|
||||
Seq(
|
||||
AnyProto.pack[ErrorInfo](
|
||||
ErrorInfo(metadata =
|
||||
Map(
|
||||
GrpcStatuses.DefiniteAnswerKey -> rejectionEntry.getDefiniteAnswer.toString
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
def duplicateCommandsRejectionStatus(
|
||||
definiteAnswer: Boolean,
|
||||
errorCode: Code,
|
||||
): Status = Status.of(
|
||||
errorCode.value,
|
||||
"Duplicate commands",
|
||||
Seq(
|
||||
AnyProto.pack[ErrorInfo](
|
||||
// the definite answer is false, as the rank-based deduplication is not yet implemented
|
||||
ErrorInfo(metadata =
|
||||
Map(
|
||||
GrpcStatuses.DefiniteAnswerKey -> definiteAnswer.toString
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
}
|
||||
|
||||
private def duplicateCommandsRejectionStatus(
|
||||
definiteAnswer: Boolean = false,
|
||||
existingCommandSubmissionId: Option[String],
|
||||
)(implicit loggingContext: ContextualizedErrorLogger): Status =
|
||||
GrpcStatus.toProto(
|
||||
LedgerApiErrors.ConsistencyErrors.DuplicateCommand
|
||||
.Reject(definiteAnswer, existingCommandSubmissionId)
|
||||
.asGrpcStatusFromContext
|
||||
)
|
||||
|
||||
private def invalidRecordTimeReason(
|
||||
recordTime: Timestamp,
|
||||
tooEarlyUntil: Option[Timestamp],
|
||||
@ -724,10 +320,4 @@ private[kvutils] object TransactionRejections {
|
||||
"Record time outside of valid range"
|
||||
}
|
||||
|
||||
private def toJsonString(parties: ProtocolStringList): String = {
|
||||
val stringWriter = new StringWriter
|
||||
val objectMapper = new ObjectMapper
|
||||
objectMapper.writeValue(stringWriter, parties)
|
||||
stringWriter.toString
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ package com.daml.ledger.participant.state.kvutils
|
||||
import java.time.Duration
|
||||
import com.codahale.metrics.MetricRegistry
|
||||
import com.daml.daml_lf_dev.DamlLf
|
||||
import com.daml.error.ValueSwitch
|
||||
import com.daml.ledger.api.DeduplicationPeriod
|
||||
import com.daml.ledger.configuration.{Configuration, LedgerTimeModel}
|
||||
import com.daml.ledger.participant.state.kvutils.KeyValueCommitting.PreExecutionResult
|
||||
@ -65,8 +64,6 @@ object KVTest {
|
||||
)
|
||||
|
||||
private[kvutils] val metrics = new Metrics(new MetricRegistry)
|
||||
private[kvutils] val errorVersionSwitch =
|
||||
new ValueSwitch(enableSelfServiceErrorCodes = true)
|
||||
|
||||
private def initialTestState: KVTestState = {
|
||||
val engine = Engine.DevEngine()
|
||||
@ -391,7 +388,6 @@ object KVTest {
|
||||
val _ = KeyValueConsumption.logEntryToUpdate(
|
||||
entryId,
|
||||
logEntry,
|
||||
errorVersionSwitch,
|
||||
)(loggingContext)
|
||||
|
||||
entryId -> logEntry
|
||||
@ -422,13 +418,11 @@ object KVTest {
|
||||
KeyValueConsumption.logEntryToUpdate(
|
||||
entryId,
|
||||
successfulLogEntry,
|
||||
errorVersionSwitch,
|
||||
recordTimeFromTimeUpdateLogEntry,
|
||||
)(loggingContext)
|
||||
KeyValueConsumption.logEntryToUpdate(
|
||||
entryId,
|
||||
outOfTimeBoundsLogEntry,
|
||||
errorVersionSwitch,
|
||||
recordTimeFromTimeUpdateLogEntry,
|
||||
)(loggingContext)
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
package com.daml.ledger.participant.state.kvutils
|
||||
|
||||
import com.daml.error.{DamlContextualizedErrorLogger, ErrorResource, ValueSwitch}
|
||||
import com.daml.error.{DamlContextualizedErrorLogger, ErrorResource}
|
||||
import com.daml.ledger.api.DeduplicationPeriod
|
||||
import com.daml.ledger.configuration.LedgerTimeModel
|
||||
import com.daml.ledger.participant.state.kvutils.Conversions._
|
||||
@ -17,13 +17,7 @@ import com.daml.ledger.participant.state.kvutils.store.events.{
|
||||
DamlSubmitterInfo,
|
||||
DamlTransactionBlindingInfo,
|
||||
DamlTransactionRejectionEntry,
|
||||
Disputed,
|
||||
Duplicate,
|
||||
Inconsistent,
|
||||
InvalidLedgerTime,
|
||||
PartyNotKnownOnLedger,
|
||||
ResourcesExhausted,
|
||||
SubmitterCannotActViaParticipant,
|
||||
}
|
||||
import com.daml.ledger.participant.state.v2.Update.CommandRejected
|
||||
import com.daml.lf.crypto
|
||||
@ -38,8 +32,6 @@ import com.daml.lf.transaction.{BlindingInfo, NodeId, TransactionOuterClass, Tra
|
||||
import com.daml.lf.value.Value.ContractId
|
||||
import com.daml.lf.value.ValueOuterClass
|
||||
import com.daml.logging.{ContextualizedLogger, LoggingContext}
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.google.protobuf.{TextFormat, Timestamp}
|
||||
import com.google.rpc.error_details.{ErrorInfo, ResourceInfo}
|
||||
import io.grpc.Status.Code
|
||||
import org.scalatest.OptionValues
|
||||
@ -50,7 +42,6 @@ import org.scalatest.wordspec.AnyWordSpec
|
||||
import java.time.Duration
|
||||
import scala.annotation.nowarn
|
||||
import scala.collection.immutable.{ListMap, ListSet}
|
||||
import scala.collection.mutable
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
@ -124,113 +115,6 @@ class ConversionsSpec extends AnyWordSpec with Matchers with OptionValues {
|
||||
"encode/decode rejections" should {
|
||||
|
||||
val submitterInfo = DamlSubmitterInfo.newBuilder().build()
|
||||
val now = LfTimestamp.now()
|
||||
|
||||
"convert rejection to proto models and back to expected grpc v1 code" in {
|
||||
forAll(
|
||||
Table[Rejection, Code, Map[String, String]](
|
||||
("Rejection", "Expected Code", "Expected Additional Details"),
|
||||
(
|
||||
Rejection.ValidationFailure(Error.Package(Error.Package.Internal("ERROR", "ERROR"))),
|
||||
Code.INVALID_ARGUMENT,
|
||||
Map.empty,
|
||||
),
|
||||
(
|
||||
Rejection.InternallyInconsistentTransaction.InconsistentKeys,
|
||||
Code.INVALID_ARGUMENT,
|
||||
Map.empty,
|
||||
),
|
||||
(
|
||||
Rejection.InternallyInconsistentTransaction.DuplicateKeys,
|
||||
Code.INVALID_ARGUMENT,
|
||||
Map.empty,
|
||||
),
|
||||
(
|
||||
Rejection.ExternallyInconsistentTransaction.InconsistentContracts,
|
||||
Code.ABORTED,
|
||||
Map.empty,
|
||||
),
|
||||
(
|
||||
Rejection.ExternallyInconsistentTransaction.InconsistentKeys,
|
||||
Code.ABORTED,
|
||||
Map.empty,
|
||||
),
|
||||
(
|
||||
Rejection.ExternallyInconsistentTransaction.DuplicateKeys,
|
||||
Code.ABORTED,
|
||||
Map.empty,
|
||||
),
|
||||
(
|
||||
Rejection.MissingInputState(DamlStateKey.getDefaultInstance),
|
||||
Code.ABORTED,
|
||||
Map.empty,
|
||||
),
|
||||
(
|
||||
Rejection.InvalidParticipantState(Err.InternalError("error")),
|
||||
Code.INVALID_ARGUMENT,
|
||||
Map.empty,
|
||||
),
|
||||
(
|
||||
Rejection.InvalidParticipantState(
|
||||
Err.ArchiveDecodingFailed(Ref.PackageId.assertFromString("id"), "reason")
|
||||
),
|
||||
Code.INVALID_ARGUMENT,
|
||||
Map("package_id" -> "id"),
|
||||
),
|
||||
(
|
||||
Rejection.InvalidParticipantState(Err.MissingDivulgedContractInstance("id")),
|
||||
Code.INVALID_ARGUMENT,
|
||||
Map("contract_id" -> "id"),
|
||||
),
|
||||
(
|
||||
Rejection.RecordTimeOutOfRange(LfTimestamp.Epoch, LfTimestamp.Epoch),
|
||||
Code.ABORTED,
|
||||
Map(
|
||||
"minimum_record_time" -> LfTimestamp.Epoch.toString,
|
||||
"maximum_record_time" -> LfTimestamp.Epoch.toString,
|
||||
),
|
||||
),
|
||||
(
|
||||
Rejection.LedgerTimeOutOfRange(LedgerTimeModel.OutOfRange(now, now, now)),
|
||||
Code.ABORTED,
|
||||
Map.empty,
|
||||
),
|
||||
(
|
||||
Rejection.CausalMonotonicityViolated,
|
||||
Code.ABORTED,
|
||||
Map.empty,
|
||||
),
|
||||
(
|
||||
Rejection.PartiesNotKnownOnLedger(Seq.empty),
|
||||
Code.INVALID_ARGUMENT,
|
||||
Map.empty,
|
||||
),
|
||||
(
|
||||
Rejection.MissingInputState(partyStateKey("party")),
|
||||
Code.ABORTED,
|
||||
Map("key" -> "party: \"party\"\n"),
|
||||
),
|
||||
(
|
||||
Rejection.SubmittingPartyNotKnownOnLedger(party0),
|
||||
Code.INVALID_ARGUMENT,
|
||||
Map("submitter_party" -> party0),
|
||||
),
|
||||
(
|
||||
Rejection.PartiesNotKnownOnLedger(Iterable(party0, party1)),
|
||||
Code.INVALID_ARGUMENT,
|
||||
Map("parties" -> s"""[\"$party0\",\"$party1\"]"""),
|
||||
),
|
||||
)
|
||||
) { (rejection, expectedCode, expectedAdditionalDetails) =>
|
||||
checkErrors(
|
||||
v1ErrorSwitch,
|
||||
submitterInfo,
|
||||
rejection,
|
||||
expectedCode,
|
||||
expectedAdditionalDetails,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
"convert rejection to proto models and back to expected grpc v2 code" in {
|
||||
forAll(
|
||||
@ -357,7 +241,6 @@ class ConversionsSpec extends AnyWordSpec with Matchers with OptionValues {
|
||||
)
|
||||
) { (rejection, expectedCode, expectedAdditionalDetails, expectedResources) =>
|
||||
checkErrors(
|
||||
v2ErrorSwitch,
|
||||
submitterInfo,
|
||||
rejection,
|
||||
expectedCode,
|
||||
@ -366,134 +249,6 @@ class ConversionsSpec extends AnyWordSpec with Matchers with OptionValues {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
"produce metadata that can be easily parsed" in {
|
||||
forAll(
|
||||
Table[Rejection, String, String => Any, Any](
|
||||
("rejection", "metadata key", "metadata parser", "expected parsed metadata"),
|
||||
(
|
||||
Rejection.MissingInputState(partyStateKey("party")),
|
||||
"key",
|
||||
TextFormat.parse(_, classOf[DamlStateKey]),
|
||||
partyStateKey("party"),
|
||||
),
|
||||
(
|
||||
Rejection.RecordTimeOutOfRange(LfTimestamp.Epoch, LfTimestamp.Epoch),
|
||||
"minimum_record_time",
|
||||
LfTimestamp.assertFromString,
|
||||
LfTimestamp.Epoch,
|
||||
),
|
||||
(
|
||||
Rejection.RecordTimeOutOfRange(LfTimestamp.Epoch, LfTimestamp.Epoch),
|
||||
"maximum_record_time",
|
||||
LfTimestamp.assertFromString,
|
||||
LfTimestamp.Epoch,
|
||||
),
|
||||
(
|
||||
Rejection.PartiesNotKnownOnLedger(Iterable(party0, party1)),
|
||||
"parties",
|
||||
jsonString => {
|
||||
val objectMapper = new ObjectMapper
|
||||
objectMapper.readValue(jsonString, classOf[java.util.List[_]]).asScala
|
||||
},
|
||||
mutable.Buffer(party0, party1),
|
||||
),
|
||||
)
|
||||
) { (rejection, metadataKey, metadataParser, expectedParsedMetadata) =>
|
||||
val encodedEntry = Conversions
|
||||
.encodeTransactionRejectionEntry(
|
||||
submitterInfo,
|
||||
rejection,
|
||||
)
|
||||
.build()
|
||||
val finalReason = Conversions
|
||||
.decodeTransactionRejectionEntry(encodedEntry, v1ErrorSwitch)
|
||||
finalReason.definiteAnswer shouldBe false
|
||||
val actualDetails = finalReasonDetails(finalReason).toMap
|
||||
metadataParser(actualDetails(metadataKey)) shouldBe expectedParsedMetadata
|
||||
}
|
||||
}
|
||||
|
||||
"convert v1 rejections" should {
|
||||
|
||||
"handle with expected status codes" in {
|
||||
forAll(
|
||||
Table[
|
||||
DamlTransactionRejectionEntry.Builder => DamlTransactionRejectionEntry.Builder,
|
||||
Code,
|
||||
Map[String, String],
|
||||
](
|
||||
("rejection builder", "code", "expected additional details"),
|
||||
(
|
||||
_.setInconsistent(Inconsistent.newBuilder()),
|
||||
Code.ABORTED,
|
||||
Map.empty,
|
||||
),
|
||||
(
|
||||
_.setDisputed(Disputed.newBuilder()),
|
||||
Code.INVALID_ARGUMENT,
|
||||
Map.empty,
|
||||
),
|
||||
(
|
||||
_.setResourcesExhausted(ResourcesExhausted.newBuilder()),
|
||||
Code.ABORTED,
|
||||
Map.empty,
|
||||
),
|
||||
(
|
||||
_.setPartyNotKnownOnLedger(PartyNotKnownOnLedger.newBuilder()),
|
||||
Code.INVALID_ARGUMENT,
|
||||
Map.empty,
|
||||
),
|
||||
(
|
||||
_.setDuplicateCommand(Duplicate.newBuilder().setSubmissionId("not_used")),
|
||||
Code.ALREADY_EXISTS,
|
||||
Map(),
|
||||
),
|
||||
(
|
||||
_.setSubmitterCannotActViaParticipant(
|
||||
SubmitterCannotActViaParticipant
|
||||
.newBuilder()
|
||||
.setSubmitterParty("party")
|
||||
.setParticipantId("id")
|
||||
),
|
||||
Code.PERMISSION_DENIED,
|
||||
Map(
|
||||
"submitter_party" -> "party",
|
||||
"participant_id" -> "id",
|
||||
),
|
||||
),
|
||||
(
|
||||
_.setInvalidLedgerTime(
|
||||
InvalidLedgerTime
|
||||
.newBuilder()
|
||||
.setLowerBound(Timestamp.newBuilder().setSeconds(1L))
|
||||
.setLedgerTime(Timestamp.newBuilder().setSeconds(2L))
|
||||
.setUpperBound(Timestamp.newBuilder().setSeconds(3L))
|
||||
),
|
||||
Code.ABORTED,
|
||||
Map(
|
||||
"lower_bound" -> "seconds: 1\n",
|
||||
"ledger_time" -> "seconds: 2\n",
|
||||
"upper_bound" -> "seconds: 3\n",
|
||||
),
|
||||
),
|
||||
)
|
||||
) { (rejectionBuilder, code, expectedAdditionalDetails) =>
|
||||
{
|
||||
val finalReason = Conversions
|
||||
.decodeTransactionRejectionEntry(
|
||||
rejectionBuilder(DamlTransactionRejectionEntry.newBuilder())
|
||||
.build(),
|
||||
v1ErrorSwitch,
|
||||
)
|
||||
finalReason.code shouldBe code.value()
|
||||
finalReason.definiteAnswer shouldBe false
|
||||
val actualDetails = finalReasonDetails(finalReason)
|
||||
actualDetails should contain allElementsOf expectedAdditionalDetails
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"decode duplicate command v2" in {
|
||||
@ -502,8 +257,7 @@ class ConversionsSpec extends AnyWordSpec with Matchers with OptionValues {
|
||||
DamlTransactionRejectionEntry
|
||||
.newBuilder()
|
||||
.setDuplicateCommand(Duplicate.newBuilder().setSubmissionId("submissionId"))
|
||||
.build(),
|
||||
v2ErrorSwitch,
|
||||
.build()
|
||||
)
|
||||
finalReason.code shouldBe Code.ALREADY_EXISTS.value()
|
||||
finalReason.definiteAnswer shouldBe false
|
||||
@ -666,12 +420,11 @@ class ConversionsSpec extends AnyWordSpec with Matchers with OptionValues {
|
||||
}
|
||||
|
||||
private def checkErrors(
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
submitterInfo: DamlSubmitterInfo,
|
||||
rejection: Rejection,
|
||||
expectedCode: Code,
|
||||
expectedAdditionalDetails: Map[String, String],
|
||||
expectedResources: Map[ErrorResource, String] = Map.empty,
|
||||
expectedResources: Map[ErrorResource, String],
|
||||
) = {
|
||||
val encodedEntry = Conversions
|
||||
.encodeTransactionRejectionEntry(
|
||||
@ -680,7 +433,7 @@ class ConversionsSpec extends AnyWordSpec with Matchers with OptionValues {
|
||||
)
|
||||
.build()
|
||||
val finalReason = Conversions
|
||||
.decodeTransactionRejectionEntry(encodedEntry, errorVersionSwitch)
|
||||
.decodeTransactionRejectionEntry(encodedEntry)
|
||||
finalReason.code shouldBe expectedCode.value()
|
||||
finalReason.definiteAnswer shouldBe false
|
||||
val actualDetails = finalReasonDetails(finalReason)
|
||||
@ -754,9 +507,6 @@ class ConversionsSpec extends AnyWordSpec with Matchers with OptionValues {
|
||||
)
|
||||
.build
|
||||
|
||||
private lazy val v1ErrorSwitch = new ValueSwitch(enableSelfServiceErrorCodes = false)
|
||||
private lazy val v2ErrorSwitch = new ValueSwitch(enableSelfServiceErrorCodes = true)
|
||||
|
||||
private[this] val txVersion = TransactionVersion.StableVersions.max
|
||||
|
||||
private def deduplicationKeyBytesFor(parties: List[String]): Array[Byte] = {
|
||||
|
@ -3,8 +3,6 @@
|
||||
|
||||
package com.daml.ledger.participant.state.kvutils
|
||||
|
||||
import com.daml.error.ValueSwitch
|
||||
|
||||
import java.time.Duration
|
||||
import com.daml.ledger.participant.state.kvutils.TestHelpers._
|
||||
import com.daml.ledger.participant.state.kvutils.store.events.DamlTransactionRejectionEntry
|
||||
@ -41,9 +39,6 @@ class KVUtilsTransactionSpec extends AnyWordSpec with Matchers with Inside {
|
||||
|
||||
private implicit val loggingContext: LoggingContext = LoggingContext.ForTesting
|
||||
|
||||
private val errorVersionSwitch =
|
||||
new ValueSwitch(enableSelfServiceErrorCodes = false)
|
||||
|
||||
private val alice = party("Alice")
|
||||
private val bob = party("Bob")
|
||||
private val eve = party("Eve")
|
||||
@ -101,7 +96,6 @@ class KVUtilsTransactionSpec extends AnyWordSpec with Matchers with Inside {
|
||||
KeyValueConsumption.logEntryToUpdate(
|
||||
entryId,
|
||||
logEntry,
|
||||
errorVersionSwitch,
|
||||
)(loggingContext)
|
||||
)
|
||||
|
||||
@ -226,7 +220,6 @@ class KVUtilsTransactionSpec extends AnyWordSpec with Matchers with Inside {
|
||||
KeyValueConsumption.logEntryToUpdate(
|
||||
entryId,
|
||||
preExecutionResult.successfulLogEntry,
|
||||
errorVersionSwitch,
|
||||
Some(recordTime),
|
||||
)(loggingContext)
|
||||
)
|
||||
@ -314,7 +307,6 @@ class KVUtilsTransactionSpec extends AnyWordSpec with Matchers with Inside {
|
||||
KeyValueConsumption.logEntryToUpdate(
|
||||
entryId,
|
||||
logEntry,
|
||||
errorVersionSwitch,
|
||||
)(loggingContext)
|
||||
)
|
||||
|
||||
@ -535,7 +527,6 @@ class KVUtilsTransactionSpec extends AnyWordSpec with Matchers with Inside {
|
||||
KeyValueConsumption.logEntryToUpdate(
|
||||
entryId,
|
||||
strippedEntry.build,
|
||||
errorVersionSwitch,
|
||||
)(loggingContext)
|
||||
inside(updates) { case Seq(txAccepted: Update.TransactionAccepted) =>
|
||||
txAccepted.optCompletionInfo should be(None)
|
||||
@ -591,7 +582,6 @@ class KVUtilsTransactionSpec extends AnyWordSpec with Matchers with Inside {
|
||||
KeyValueConsumption.logEntryToUpdate(
|
||||
entryId,
|
||||
preExecutionResult.successfulLogEntry,
|
||||
errorVersionSwitch,
|
||||
Some(recordTime),
|
||||
)(loggingContext)
|
||||
)
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
package com.daml.ledger.participant.state.kvutils
|
||||
|
||||
import com.daml.error.ValueSwitch
|
||||
import com.daml.ledger.configuration.Configuration
|
||||
import com.daml.ledger.grpc.GrpcStatuses
|
||||
import com.daml.ledger.participant.state.kvutils.Conversions.buildTimestamp
|
||||
@ -37,7 +36,7 @@ import org.scalatest.Inside.inside
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.prop.TableDrivenPropertyChecks._
|
||||
import org.scalatest.prop.Tables.Table
|
||||
import org.scalatest.prop.{TableFor1, TableFor4, TableFor5}
|
||||
import org.scalatest.prop.TableFor4
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
import java.time.Instant
|
||||
|
||||
@ -58,76 +57,50 @@ class KeyValueConsumptionSpec extends AnyWordSpec with Matchers {
|
||||
.setPackageUploadEntry(DamlPackageUploadEntry.getDefaultInstance)
|
||||
.build
|
||||
|
||||
private val errorVersionsTable: TableFor1[ValueSwitch] = Table[ValueSwitch](
|
||||
"Error Version",
|
||||
v1ErrorSwitch,
|
||||
v2ErrorSwitch,
|
||||
)
|
||||
|
||||
private implicit val loggingContext: LoggingContext = LoggingContext.ForTesting
|
||||
|
||||
"logEntryToUpdate" should {
|
||||
"throw in case no record time is available from the log entry or input argument" in {
|
||||
forAll(
|
||||
errorVersionsTable
|
||||
) { errorSwitch =>
|
||||
assertThrows[Err](
|
||||
logEntryToUpdate(
|
||||
aLogEntryId,
|
||||
aLogEntryWithoutRecordTime,
|
||||
errorSwitch,
|
||||
recordTimeForUpdate = None,
|
||||
)(loggingContext)
|
||||
)
|
||||
}
|
||||
assertThrows[Err](
|
||||
logEntryToUpdate(
|
||||
aLogEntryId,
|
||||
aLogEntryWithoutRecordTime,
|
||||
recordTimeForUpdate = None,
|
||||
)(loggingContext)
|
||||
)
|
||||
}
|
||||
|
||||
"use log entry's record time instead of one provided as input" in {
|
||||
forAll(
|
||||
errorVersionsTable
|
||||
) { errorSwitch =>
|
||||
val actual :: Nil = logEntryToUpdate(
|
||||
aLogEntryId,
|
||||
aLogEntryWithRecordTime,
|
||||
errorSwitch,
|
||||
recordTimeForUpdate = Some(aRecordTime),
|
||||
)(loggingContext)
|
||||
val actual :: Nil = logEntryToUpdate(
|
||||
aLogEntryId,
|
||||
aLogEntryWithRecordTime,
|
||||
recordTimeForUpdate = Some(aRecordTime),
|
||||
)(loggingContext)
|
||||
|
||||
actual.recordTime shouldBe aRecordTimeFromLogEntry
|
||||
}
|
||||
actual.recordTime shouldBe aRecordTimeFromLogEntry
|
||||
}
|
||||
|
||||
"use record time from log entry if not provided as input" in {
|
||||
forAll(
|
||||
errorVersionsTable
|
||||
) { errorSwitch =>
|
||||
val actual :: Nil =
|
||||
logEntryToUpdate(
|
||||
aLogEntryId,
|
||||
aLogEntryWithRecordTime,
|
||||
errorSwitch,
|
||||
recordTimeForUpdate = None,
|
||||
)(loggingContext)
|
||||
val actual :: Nil =
|
||||
logEntryToUpdate(
|
||||
aLogEntryId,
|
||||
aLogEntryWithRecordTime,
|
||||
recordTimeForUpdate = None,
|
||||
)(loggingContext)
|
||||
|
||||
actual.recordTime shouldBe Timestamp.assertFromInstant(Instant.ofEpochSecond(100))
|
||||
}
|
||||
actual.recordTime shouldBe Timestamp.assertFromInstant(Instant.ofEpochSecond(100))
|
||||
}
|
||||
|
||||
"not generate an update from a time update entry" in {
|
||||
forAll(
|
||||
errorVersionsTable
|
||||
) { errorSwitch =>
|
||||
val timeUpdateEntry = DamlLogEntry.newBuilder
|
||||
.setRecordTime(Conversions.buildTimestamp(aRecordTime))
|
||||
.setTimeUpdateEntry(Empty.getDefaultInstance)
|
||||
.build
|
||||
logEntryToUpdate(
|
||||
aLogEntryId,
|
||||
timeUpdateEntry,
|
||||
errorSwitch,
|
||||
recordTimeForUpdate = None,
|
||||
)(loggingContext) shouldBe Nil
|
||||
}
|
||||
val timeUpdateEntry = DamlLogEntry.newBuilder
|
||||
.setRecordTime(Conversions.buildTimestamp(aRecordTime))
|
||||
.setTimeUpdateEntry(Empty.getDefaultInstance)
|
||||
.build
|
||||
logEntryToUpdate(
|
||||
aLogEntryId,
|
||||
timeUpdateEntry,
|
||||
recordTimeForUpdate = None,
|
||||
)(loggingContext) shouldBe Nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,21 +163,17 @@ class KeyValueConsumptionSpec extends AnyWordSpec with Matchers {
|
||||
}
|
||||
|
||||
"generate update for deduplicated transaction with definite answer set to true" in {
|
||||
forAll(
|
||||
errorVersionsTable
|
||||
) { errorSwitch =>
|
||||
val inputEntry = buildOutOfTimeBoundsEntry(
|
||||
TimeBounds(deduplicateUntil = Some(aRecordTime)),
|
||||
TRANSACTION_REJECTION_ENTRY,
|
||||
definiteAnswer = Some(true),
|
||||
val inputEntry = buildOutOfTimeBoundsEntry(
|
||||
TimeBounds(deduplicateUntil = Some(aRecordTime)),
|
||||
TRANSACTION_REJECTION_ENTRY,
|
||||
definiteAnswer = Some(true),
|
||||
)
|
||||
val actual = outOfTimeBoundsEntryToUpdate(aRecordTime, inputEntry)
|
||||
inside(actual) { case Some(CommandRejected(_, _, FinalReason(status))) =>
|
||||
status.code shouldBe Code.ALREADY_EXISTS.value
|
||||
unpackErrorInfo(status.details) should contain allElementsOf Map(
|
||||
GrpcStatuses.DefiniteAnswerKey -> "true"
|
||||
)
|
||||
val actual = outOfTimeBoundsEntryToUpdate(aRecordTime, inputEntry, errorSwitch)
|
||||
inside(actual) { case Some(CommandRejected(_, _, FinalReason(status))) =>
|
||||
status.code shouldBe Code.ALREADY_EXISTS.value
|
||||
unpackErrorInfo(status.details) should contain allElementsOf Map(
|
||||
GrpcStatuses.DefiniteAnswerKey -> "true"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,57 +199,8 @@ class KeyValueConsumptionSpec extends AnyWordSpec with Matchers {
|
||||
case _ => fail()
|
||||
}
|
||||
val testCases = Table(
|
||||
("Error Version", "Time Bounds", "Record Time", "Log Entry Type", "Assertions"),
|
||||
("Time Bounds", "Record Time", "Log Entry Type", "Assertions"),
|
||||
(
|
||||
v1ErrorSwitch,
|
||||
TimeBounds(
|
||||
tooLateFrom = Some(Timestamp.assertFromInstant(aRecordTimeInstant.minusMillis(1)))
|
||||
),
|
||||
aRecordTime,
|
||||
TRANSACTION_REJECTION_ENTRY,
|
||||
Assertions(verify = { update =>
|
||||
verifyCommandRejection(update)
|
||||
verifyStatusCode(update, Code.ABORTED)
|
||||
}),
|
||||
),
|
||||
(
|
||||
v1ErrorSwitch,
|
||||
TimeBounds(
|
||||
tooEarlyUntil = Some(Timestamp.assertFromInstant(aRecordTimeInstant.plusMillis(1)))
|
||||
),
|
||||
aRecordTime,
|
||||
TRANSACTION_REJECTION_ENTRY,
|
||||
Assertions(verify = { update =>
|
||||
verifyCommandRejection(update)
|
||||
verifyStatusCode(update, Code.ABORTED)
|
||||
}),
|
||||
),
|
||||
(
|
||||
v1ErrorSwitch,
|
||||
TimeBounds(tooLateFrom = Some(aRecordTime)),
|
||||
aRecordTime,
|
||||
TRANSACTION_REJECTION_ENTRY,
|
||||
Assertions(verify = verifyNoUpdateIsGenerated),
|
||||
),
|
||||
(
|
||||
v1ErrorSwitch,
|
||||
TimeBounds(tooEarlyUntil = Some(aRecordTime)),
|
||||
aRecordTime,
|
||||
TRANSACTION_REJECTION_ENTRY,
|
||||
Assertions(verify = verifyNoUpdateIsGenerated),
|
||||
),
|
||||
(
|
||||
v1ErrorSwitch,
|
||||
TimeBounds(
|
||||
tooEarlyUntil = Some(Timestamp.assertFromInstant(aRecordTimeInstant.minusMillis(1))),
|
||||
tooLateFrom = Some(Timestamp.assertFromInstant(aRecordTimeInstant.plusMillis(1))),
|
||||
),
|
||||
aRecordTime,
|
||||
TRANSACTION_REJECTION_ENTRY,
|
||||
Assertions(verify = verifyNoUpdateIsGenerated),
|
||||
),
|
||||
(
|
||||
v2ErrorSwitch,
|
||||
TimeBounds(
|
||||
tooLateFrom = Some(Timestamp.assertFromInstant(aRecordTimeInstant.minusMillis(1)))
|
||||
),
|
||||
@ -292,7 +212,6 @@ class KeyValueConsumptionSpec extends AnyWordSpec with Matchers {
|
||||
}),
|
||||
),
|
||||
(
|
||||
v2ErrorSwitch,
|
||||
TimeBounds(
|
||||
tooEarlyUntil = Some(Timestamp.assertFromInstant(aRecordTimeInstant.plusMillis(1)))
|
||||
),
|
||||
@ -304,14 +223,12 @@ class KeyValueConsumptionSpec extends AnyWordSpec with Matchers {
|
||||
}),
|
||||
),
|
||||
(
|
||||
v2ErrorSwitch,
|
||||
TimeBounds(tooLateFrom = Some(aRecordTime)),
|
||||
aRecordTime,
|
||||
TRANSACTION_REJECTION_ENTRY,
|
||||
Assertions(verify = verifyNoUpdateIsGenerated),
|
||||
),
|
||||
(
|
||||
v2ErrorSwitch,
|
||||
TimeBounds(tooEarlyUntil = Some(aRecordTime)),
|
||||
aRecordTime,
|
||||
TRANSACTION_REJECTION_ENTRY,
|
||||
@ -319,7 +236,6 @@ class KeyValueConsumptionSpec extends AnyWordSpec with Matchers {
|
||||
),
|
||||
// Record time within time bounds.
|
||||
(
|
||||
v2ErrorSwitch,
|
||||
TimeBounds(
|
||||
tooEarlyUntil = Some(Timestamp.assertFromInstant(aRecordTimeInstant.minusMillis(1))),
|
||||
tooLateFrom = Some(Timestamp.assertFromInstant(aRecordTimeInstant.plusMillis(1))),
|
||||
@ -447,11 +363,10 @@ class KeyValueConsumptionSpec extends AnyWordSpec with Matchers {
|
||||
}
|
||||
|
||||
private def runAll(
|
||||
table: TableFor5[ValueSwitch, TimeBounds, Timestamp, DamlLogEntry.PayloadCase, Assertions]
|
||||
table: TableFor4[TimeBounds, Timestamp, DamlLogEntry.PayloadCase, Assertions]
|
||||
): Unit =
|
||||
forAll(table) {
|
||||
(
|
||||
errorVersionSwitch: ValueSwitch,
|
||||
timeBounds: TimeBounds,
|
||||
recordTime: Timestamp,
|
||||
logEntryType: DamlLogEntry.PayloadCase,
|
||||
@ -460,38 +375,15 @@ class KeyValueConsumptionSpec extends AnyWordSpec with Matchers {
|
||||
val inputEntry = buildOutOfTimeBoundsEntry(timeBounds, logEntryType)
|
||||
if (assertions.throwsInternalError) {
|
||||
assertThrows[Err.InternalError](
|
||||
outOfTimeBoundsEntryToUpdate(recordTime, inputEntry, errorVersionSwitch)
|
||||
outOfTimeBoundsEntryToUpdate(recordTime, inputEntry)
|
||||
)
|
||||
} else {
|
||||
val actual = outOfTimeBoundsEntryToUpdate(recordTime, inputEntry, errorVersionSwitch)
|
||||
val actual = outOfTimeBoundsEntryToUpdate(recordTime, inputEntry)
|
||||
assertions.verify(actual)
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
private def runAll(
|
||||
table: TableFor4[TimeBounds, Timestamp, DamlLogEntry.PayloadCase, Assertions]
|
||||
): Unit = {
|
||||
val (head1, head2, head3, head4) = table.heading
|
||||
runAll(
|
||||
Table(
|
||||
heading = ("Error Version", head1, head2, head3, head4),
|
||||
rows = table.flatMap {
|
||||
case (
|
||||
timeBounds: TimeBounds,
|
||||
recordTime: Timestamp,
|
||||
logEntryType: DamlLogEntry.PayloadCase,
|
||||
assertions: Assertions,
|
||||
) =>
|
||||
Seq(
|
||||
(v1ErrorSwitch, timeBounds, recordTime, logEntryType, assertions),
|
||||
(v2ErrorSwitch, timeBounds, recordTime, logEntryType, assertions),
|
||||
)
|
||||
}: _*,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@nowarn("msg=deprecated")
|
||||
private def buildOutOfTimeBoundsEntry(
|
||||
timeBounds: TimeBounds,
|
||||
@ -570,11 +462,4 @@ class KeyValueConsumptionSpec extends AnyWordSpec with Matchers {
|
||||
else
|
||||
Map.empty[String, String]
|
||||
}
|
||||
|
||||
private lazy val v1ErrorSwitch = new ValueSwitch(enableSelfServiceErrorCodes = false) {
|
||||
override def toString: String = "1"
|
||||
}
|
||||
private lazy val v2ErrorSwitch = new ValueSwitch(enableSelfServiceErrorCodes = true) {
|
||||
override def toString: String = "2"
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ package com.daml.ledger.participant.state.kvutils.api
|
||||
import akka.NotUsed
|
||||
import akka.stream.scaladsl.{Sink, Source}
|
||||
import com.codahale.metrics.MetricRegistry
|
||||
import com.daml.error.ValueSwitch
|
||||
import com.daml.ledger.api.testing.utils.AkkaBeforeAndAfterAll
|
||||
import com.daml.ledger.offset.Offset
|
||||
import com.daml.ledger.participant.state.kvutils.api.KeyValueParticipantStateReader.offsetForUpdate
|
||||
@ -247,18 +246,16 @@ object KeyValueParticipantStateReaderSpec {
|
||||
private val zeroUpdateGenerator: (
|
||||
DamlLogEntryId,
|
||||
DamlLogEntry,
|
||||
ValueSwitch,
|
||||
Option[Timestamp],
|
||||
) => LoggingContext => List[Update] =
|
||||
(_, _, _, _) => _ => List.empty
|
||||
(_, _, _) => _ => List.empty
|
||||
|
||||
private val singleUpdateGenerator: (
|
||||
DamlLogEntryId,
|
||||
DamlLogEntry,
|
||||
ValueSwitch,
|
||||
Option[Timestamp],
|
||||
) => LoggingContext => List[Update] =
|
||||
(_, _, _, _) =>
|
||||
(_, _, _) =>
|
||||
_ =>
|
||||
List(
|
||||
Update.PartyAddedToParticipant(
|
||||
@ -273,20 +270,17 @@ object KeyValueParticipantStateReaderSpec {
|
||||
private val twoUpdatesGenerator: (
|
||||
DamlLogEntryId,
|
||||
DamlLogEntry,
|
||||
ValueSwitch,
|
||||
Option[Timestamp],
|
||||
) => LoggingContext => List[Update] =
|
||||
(entryId, entry, errorVersionSwitch, recordTime) =>
|
||||
(entryId, entry, recordTime) =>
|
||||
loggingContext =>
|
||||
singleUpdateGenerator(
|
||||
entryId,
|
||||
entry,
|
||||
errorVersionSwitch,
|
||||
recordTime,
|
||||
)(loggingContext) ::: singleUpdateGenerator(
|
||||
entryId,
|
||||
entry,
|
||||
errorVersionSwitch,
|
||||
recordTime,
|
||||
)(loggingContext)
|
||||
|
||||
@ -309,7 +303,6 @@ object KeyValueParticipantStateReaderSpec {
|
||||
logEntryToUpdate: (
|
||||
DamlLogEntryId,
|
||||
DamlLogEntry,
|
||||
ValueSwitch,
|
||||
Option[Timestamp],
|
||||
) => LoggingContext => List[Update] = singleUpdateGenerator,
|
||||
failOnUnexpectedEvent: Boolean = true,
|
||||
@ -317,7 +310,6 @@ object KeyValueParticipantStateReaderSpec {
|
||||
new KeyValueParticipantStateReader(
|
||||
reader,
|
||||
new Metrics(new MetricRegistry),
|
||||
enableSelfServiceErrorCodes = true,
|
||||
logEntryToUpdate,
|
||||
() => None,
|
||||
failOnUnexpectedEvent,
|
||||
|
@ -142,8 +142,6 @@ class DeduplicationPeriodSupportSpec
|
||||
when(
|
||||
errorFactories.invalidDeduplicationPeriod(
|
||||
any[String],
|
||||
any[String],
|
||||
any[Option[Boolean]],
|
||||
any[Option[Duration]],
|
||||
)(any[ContextualizedErrorLogger])
|
||||
).thenReturn(statusRuntimeException)
|
||||
@ -159,8 +157,6 @@ class DeduplicationPeriodSupportSpec
|
||||
)
|
||||
verify(errorFactories).invalidDeduplicationPeriod(
|
||||
any[String],
|
||||
any[String],
|
||||
any[Option[Boolean]],
|
||||
any[Option[Duration]],
|
||||
)(any[ContextualizedErrorLogger])
|
||||
result shouldBe statusRuntimeException
|
||||
|
@ -66,7 +66,6 @@ final class LogAppendingReadServiceFactory(
|
||||
keyValueSource,
|
||||
metrics,
|
||||
failOnUnexpectedEvent = false,
|
||||
enableSelfServiceErrorCodes = true,
|
||||
)
|
||||
new ReplayingReadService {
|
||||
override def updateCount(): Long = recordedBlocksSnapshot.length.toLong
|
||||
|
@ -313,7 +313,6 @@ object RecoveringIndexerIntegrationSpec {
|
||||
KeyValueParticipantStateReader(
|
||||
reader = reader,
|
||||
metrics = metrics,
|
||||
enableSelfServiceErrorCodes = true,
|
||||
),
|
||||
new KeyValueParticipantStateWriter(
|
||||
writer = writer,
|
||||
|
@ -66,7 +66,6 @@ final case class SandboxConfig(
|
||||
managementServiceTimeout: Duration,
|
||||
sqlStartMode: Option[PostgresStartupMode],
|
||||
enableCompression: Boolean,
|
||||
enableSelfServiceErrorCodes: Boolean,
|
||||
userManagementConfig: UserManagementConfig,
|
||||
) {
|
||||
|
||||
@ -166,7 +165,6 @@ object SandboxConfig {
|
||||
managementServiceTimeout = DefaultManagementServiceTimeout,
|
||||
sqlStartMode = Some(DefaultSqlStartupMode),
|
||||
enableCompression = false,
|
||||
enableSelfServiceErrorCodes = true,
|
||||
userManagementConfig = UserManagementConfig.default(true),
|
||||
)
|
||||
|
||||
|
@ -67,7 +67,6 @@ object ConfigConverter {
|
||||
configurationLoadTimeout = sandboxConfig.configurationLoadTimeout,
|
||||
commandConfig = sandboxConfig.commandConfig,
|
||||
enableInMemoryFanOutForLedgerApi = false,
|
||||
enableSelfServiceErrorCodes = sandboxConfig.enableSelfServiceErrorCodes,
|
||||
eventsPageSize = sandboxConfig.eventsPageSize,
|
||||
eventsProcessingParallelism = sandboxConfig.eventsProcessingParallelism,
|
||||
extra = extraBridgeConfig,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user