Skip to content

Commit

Permalink
KV: introduce v2 error codes behind a CLI switch [KVL-1140] (#11224)
Browse files Browse the repository at this point in the history
* Propagate error version switch to KeyValueConsumption

CHANGELOG_BEGIN
CHANGELOG_END

* Introduce v2 (self-service) KV error codes behind the CLI switch

* Test v2 codes and fix them

* Keep newline separating methods in KeyValueConsumption

* TransactionRejections: don't wrap updates in `Some`

* Factor errorVersionsTable

* Reorder imports

* Split "convert rejection to proto models and back to expected grpc code"

* Remove unneeded Option returned by decodeTransactionRejectionEntry

* Formatting fix

* Fix 7537e93
  • Loading branch information
fabiotudone-da authored Oct 19, 2021
1 parent 46f6877 commit 98cf8d8
Show file tree
Hide file tree
Showing 20 changed files with 983 additions and 365 deletions.
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

0 comments on commit 98cf8d8

Please sign in to comment.