Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KV: introduce v2 error codes behind a CLI switch [KVL-1140] #11224

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ object Main {
keyValueSource,
metrics,
failOnUnexpectedEvent = false,
enableSelfServiceErrorCodes = false,
)

// Note: this method is doing quite a lot of work to transform a sequence of write sets
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ private[memory] class InMemoryLedgerFactory(dispatcher: Dispatcher[Index], state
readerWriter,
readerWriter,
createMetrics(participantConfig, config),
config.enableSelfServiceErrorCodes,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,10 @@ class InMemoryLedgerReaderWriterIntegrationSpec
engine = Engine.DevEngine(),
committerExecutionContext = committerExecutionContext,
)
} yield new KeyValueParticipantState(readerWriter, readerWriter, metrics)
} yield new KeyValueParticipantState(
readerWriter,
readerWriter,
metrics,
enableSelfServiceErrorCodes = false,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,14 @@ object SqlLedgerFactory extends LedgerFactory[ReadWriteService, ExtraConfig] {
metrics = metrics.daml.kvutils.submission.validator.stateValueCache,
),
).acquire()
.map(readerWriter => new KeyValueParticipantState(readerWriter, readerWriter, metrics))
.map(readerWriter =>
new KeyValueParticipantState(
readerWriter,
readerWriter,
metrics,
enableSelfServiceErrorCodes = config.enableSelfServiceErrorCodes,
)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,12 @@ abstract class SqlLedgerReaderWriterIntegrationSpecBase(implementationName: Stri
resetOnStartup = false,
offsetVersion = offsetVersion,
logEntryIdAllocator = RandomLogEntryIdAllocator,
).map(readerWriter => new KeyValueParticipantState(readerWriter, readerWriter, metrics))
).map(readerWriter =>
new KeyValueParticipantState(
readerWriter,
readerWriter,
metrics,
enableSelfServiceErrorCodes = false,
)
)
}
3 changes: 3 additions & 0 deletions ledger/participant-state/kvutils/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ da_scala_library(
"//daml-lf/transaction:value_proto_java",
"//ledger-api/grpc-definitions:ledger_api_proto_scala",
"//ledger/caching",
"//ledger/error",
"//ledger/ledger-api-domain",
"//ledger/ledger-api-health",
"//ledger/ledger-configuration",
Expand Down Expand Up @@ -118,6 +119,7 @@ da_scala_library(
"//ledger-api/grpc-definitions:ledger_api_proto_scala",
"//ledger-api/rs-grpc-bridge",
"//ledger-api/testing-utils",
"//ledger/error",
"//ledger/ledger-api-common",
"//ledger/ledger-api-domain",
"//ledger/ledger-api-health",
Expand Down Expand Up @@ -187,6 +189,7 @@ da_scala_test_suite(
"//ledger-api/rs-grpc-bridge",
"//ledger-api/testing-utils",
"//ledger/caching",
"//ledger/error",
"//ledger/ledger-api-domain",
"//ledger/ledger-api-health",
"//ledger/ledger-configuration",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ object LedgerFactory {
readerWriter,
readerWriter,
createMetrics(participantConfig, config),
config.enableSelfServiceErrorCodes,
)

def owner(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@

package com.daml.ledger.participant.state.kvutils

import java.io.StringWriter
import java.time.{Duration, Instant}

import com.daml.error.ValueSwitch
import com.daml.ledger.api.DeduplicationPeriod
import com.daml.ledger.grpc.GrpcStatuses
import com.daml.ledger.offset.Offset
import com.daml.ledger.participant.state.kvutils.committer.transaction.Rejection
import com.daml.ledger.participant.state.kvutils.committer.transaction.Rejection.{
Expand Down Expand Up @@ -42,6 +39,7 @@ import com.daml.ledger.participant.state.kvutils.store.{
DamlStateKey,
DamlSubmissionDedupKey,
}
import com.daml.ledger.participant.state.kvutils.updates.TransactionRejections._
import com.daml.ledger.participant.state.v2.Update.CommandRejected.FinalReason
import com.daml.ledger.participant.state.v2.{CompletionInfo, SubmitterInfo}
import com.daml.lf.data.Relation.Relation
Expand All @@ -50,13 +48,10 @@ import com.daml.lf.transaction._
import com.daml.lf.value.Value.{ContractId, VersionedValue}
import com.daml.lf.value.{Value, ValueCoder, ValueOuterClass}
import com.daml.lf.{crypto, data}
import com.fasterxml.jackson.databind.ObjectMapper
import com.google.protobuf.Empty
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 java.time.{Duration, Instant}
import scala.annotation.nowarn
import scala.collection.mutable
import scala.jdk.CollectionConverters._
Expand Down Expand Up @@ -219,8 +214,8 @@ private[state] object Conversions {
Ref.SubmissionId.assertFromString
),
)

}

def buildTimestamp(ts: Time.Timestamp): com.google.protobuf.Timestamp =
buildTimestamp(ts.toInstant)

Expand Down Expand Up @@ -472,215 +467,63 @@ private[state] object Conversions {

@nowarn("msg=deprecated")
def decodeTransactionRejectionEntry(
entry: DamlTransactionRejectionEntry
): Option[FinalReason] = {
def buildStatus(
code: Code,
message: String,
additionalMetadata: Map[String, String] = Map.empty,
) = Status.of(
code.value,
message,
Seq(
AnyProto.pack[ErrorInfo](
ErrorInfo(metadata =
additionalMetadata + (GrpcStatuses.DefiniteAnswerKey -> entry.getDefiniteAnswer.toString)
)
)
),
)

val status = entry.getReasonCase match {
entry: DamlTransactionRejectionEntry,
errorVersionSwitch: ValueSwitch[Status],
): FinalReason =
FinalReason(entry.getReasonCase match {
case DamlTransactionRejectionEntry.ReasonCase.INVALID_LEDGER_TIME =>
val rejection = entry.getInvalidLedgerTime
Some(
buildStatus(
Code.ABORTED,
s"Invalid ledger time: ${rejection.getDetails}",
Map(
"ledger_time" -> rejection.getLedgerTime.toString,
"lower_bound" -> rejection.getLowerBound.toString,
"upper_bound" -> rejection.getUpperBound.toString,
),
)
)
invalidLedgerTimeStatus(entry, rejection, errorVersionSwitch)
case DamlTransactionRejectionEntry.ReasonCase.DISPUTED =>
val rejection = entry.getDisputed
Some(
buildStatus(
Code.INVALID_ARGUMENT,
s"Disputed: ${rejection.getDetails}",
)
)
disputedStatus(entry, rejection, errorVersionSwitch)
case DamlTransactionRejectionEntry.ReasonCase.SUBMITTER_CANNOT_ACT_VIA_PARTICIPANT =>
val rejection = entry.getSubmitterCannotActViaParticipant
Some(
buildStatus(
Code.PERMISSION_DENIED,
s"Submitter cannot act via participant: ${rejection.getDetails}",
Map(
"submitter_party" -> rejection.getSubmitterParty,
"participant_id" -> rejection.getParticipantId,
),
)
)
submitterCannotActViaParticipantStatus(entry, rejection)
case DamlTransactionRejectionEntry.ReasonCase.INCONSISTENT =>
val rejection = entry.getInconsistent
Some(
buildStatus(
Code.ABORTED,
s"Inconsistent: ${rejection.getDetails}",
)
)
inconsistentStatus(entry, rejection, errorVersionSwitch)
case DamlTransactionRejectionEntry.ReasonCase.RESOURCES_EXHAUSTED =>
val rejection = entry.getResourcesExhausted
Some(
buildStatus(
Code.ABORTED,
s"Resources exhausted: ${rejection.getDetails}",
)
)
resourceExhaustedStatus(entry, rejection)
case DamlTransactionRejectionEntry.ReasonCase.DUPLICATE_COMMAND =>
Some(
buildStatus(
Code.ALREADY_EXISTS,
"Duplicate commands",
)
)
duplicateCommandStatus(entry)
case DamlTransactionRejectionEntry.ReasonCase.PARTY_NOT_KNOWN_ON_LEDGER =>
val rejection = entry.getPartyNotKnownOnLedger
Some(
buildStatus(
Code.INVALID_ARGUMENT,
s"Party not known on ledger: ${rejection.getDetails}",
)
)
partyNotKnownOnLedgerStatus(entry, rejection, errorVersionSwitch)
case DamlTransactionRejectionEntry.ReasonCase.VALIDATION_FAILURE =>
val rejection = entry.getValidationFailure
Some(
buildStatus(
Code.INVALID_ARGUMENT,
s"Disputed: ${rejection.getDetails}",
)
)
validationFailureStatus(entry, rejection, errorVersionSwitch)
case DamlTransactionRejectionEntry.ReasonCase.INTERNALLY_DUPLICATE_KEYS =>
Some(
buildStatus(
Code.INVALID_ARGUMENT,
s"Disputed: ${InternallyInconsistentTransaction.DuplicateKeys.description}",
)
)
internallyDuplicateKeysStatus(entry, errorVersionSwitch)
case DamlTransactionRejectionEntry.ReasonCase.INTERNALLY_INCONSISTENT_KEYS =>
Some(
buildStatus(
Code.INVALID_ARGUMENT,
s"Disputed: ${InternallyInconsistentTransaction.InconsistentKeys.description}",
)
)
internallyInconsistentKeysStatus(entry, errorVersionSwitch)
case DamlTransactionRejectionEntry.ReasonCase.EXTERNALLY_INCONSISTENT_CONTRACTS =>
Some(
buildStatus(
Code.ABORTED,
s"Inconsistent: ${ExternallyInconsistentTransaction.InconsistentContracts.description}",
)
)
externallyInconsistentContractsStatus(entry, errorVersionSwitch)
case DamlTransactionRejectionEntry.ReasonCase.EXTERNALLY_DUPLICATE_KEYS =>
Some(
buildStatus(
Code.ABORTED,
s"Inconsistent: ${ExternallyInconsistentTransaction.DuplicateKeys.description}",
)
)
externallyDuplicateKeysStatus(entry, errorVersionSwitch)
case DamlTransactionRejectionEntry.ReasonCase.EXTERNALLY_INCONSISTENT_KEYS =>
Some(
buildStatus(
Code.ABORTED,
s"Inconsistent: ${ExternallyInconsistentTransaction.InconsistentKeys.description}",
)
)
externallyInconsistentKeysStatus(entry, errorVersionSwitch)
case DamlTransactionRejectionEntry.ReasonCase.MISSING_INPUT_STATE =>
val rejection = entry.getMissingInputState
Some(
buildStatus(
Code.ABORTED,
s"Inconsistent: Missing input state for key ${rejection.getKey.toString}",
Map("key" -> rejection.getKey.toString),
)
)
missingInputStateStatus(entry, rejection, errorVersionSwitch)
case DamlTransactionRejectionEntry.ReasonCase.RECORD_TIME_OUT_OF_RANGE =>
val rejection = entry.getRecordTimeOutOfRange
Some(
buildStatus(
Code.ABORTED,
s"Invalid ledger time: Record time is outside of valid range [${rejection.getMinimumRecordTime}, ${rejection.getMaximumRecordTime}]",
Map(
"minimum_record_time" -> Instant
.ofEpochSecond(
rejection.getMinimumRecordTime.getSeconds,
rejection.getMinimumRecordTime.getNanos.toLong,
)
.toString,
"maximum_record_time" -> Instant
.ofEpochSecond(
rejection.getMaximumRecordTime.getSeconds,
rejection.getMaximumRecordTime.getNanos.toLong,
)
.toString,
),
)
)
recordTimeOutOfRangeStatus(entry, rejection, errorVersionSwitch)
case DamlTransactionRejectionEntry.ReasonCase.CAUSAL_MONOTONICITY_VIOLATED =>
Some(
buildStatus(
Code.ABORTED,
"Invalid ledger time: Causal monotonicity violated",
)
)
causalMonotonicityViolatedStatus(entry, errorVersionSwitch)
case DamlTransactionRejectionEntry.ReasonCase.SUBMITTING_PARTY_NOT_KNOWN_ON_LEDGER =>
val rejection = entry.getSubmittingPartyNotKnownOnLedger
Some(
buildStatus(
Code.INVALID_ARGUMENT,
s"Party not known on ledger: Submitting party '${rejection.getSubmitterParty}' not known",
Map("submitter_party" -> rejection.getSubmitterParty),
)
)
submittingPartyNotKnownOnLedgerStatus(entry, rejection, errorVersionSwitch)
case DamlTransactionRejectionEntry.ReasonCase.PARTIES_NOT_KNOWN_ON_LEDGER =>
val rejection = entry.getPartiesNotKnownOnLedger
val parties = rejection.getPartiesList
Some(
buildStatus(
Code.INVALID_ARGUMENT,
s"Party not known on ledger: Parties not known on ledger ${parties.asScala.mkString("[", ",", "]")}",
Map("parties" -> objectToJsonString(parties)),
)
)
partiesNotKnownOnLedgerStatus(entry, rejection, errorVersionSwitch)
case DamlTransactionRejectionEntry.ReasonCase.INVALID_PARTICIPANT_STATE =>
val rejection = entry.getInvalidParticipantState
Some(
buildStatus(
Code.INVALID_ARGUMENT,
s"Disputed: ${rejection.getDetails}",
rejection.getMetadataMap.asScala.toMap,
)
)
invalidParticipantStateStatus(entry, rejection, errorVersionSwitch)
case DamlTransactionRejectionEntry.ReasonCase.REASON_NOT_SET =>
Some(
buildStatus(
Code.UNKNOWN,
"No reason set for rejection",
)
)
}
status.map(FinalReason)
}

private def objectToJsonString(obj: Object): String = {
val stringWriter = new StringWriter
val objectMapper = new ObjectMapper
objectMapper.writeValue(stringWriter, obj)
stringWriter.toString
}
reasonNotSetStatus(entry, errorVersionSwitch)
})

private def encodeParties(parties: Set[Ref.Party]): List[String] =
(parties.toList: List[String]).sorted
Expand Down
Loading