Simplify kvutils contract keys validation (#9628)

changelog_begin
changelog_end
This commit is contained in:
Moritz Kiefer 2021-05-17 13:38:27 +02:00 committed by GitHub
parent 010e2b1b27
commit 4e63299919
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 35 additions and 258 deletions

View File

@ -3,6 +3,7 @@
package com.daml.ledger.participant.state.kvutils.committer.transaction.keys
import com.daml.ledger.participant.state.kvutils.Conversions
import com.daml.ledger.participant.state.kvutils.DamlKvutils.{
DamlContractKey,
DamlStateKey,
@ -13,14 +14,10 @@ import com.daml.ledger.participant.state.kvutils.committer.transaction.{
Step,
TransactionCommitter,
}
import com.daml.ledger.participant.state.kvutils.committer.transaction.keys.KeyConsistencyValidation.checkNodeKeyConsistency
import com.daml.ledger.participant.state.kvutils.committer.transaction.keys.KeyMonotonicityValidation.checkContractKeysCausalMonotonicity
import com.daml.ledger.participant.state.kvutils.committer.transaction.keys.KeyUniquenessValidation.checkNodeKeyUniqueness
import com.daml.ledger.participant.state.kvutils.committer.{CommitContext, StepContinue, StepResult}
import com.daml.ledger.participant.state.v1.RejectionReason
import com.daml.lf.data.Time.Timestamp
import com.daml.lf.transaction.{Node, NodeId}
import com.daml.lf.value.Value.ContractId
import com.daml.logging.LoggingContext
private[transaction] object ContractKeysValidation {
@ -55,7 +52,6 @@ private[transaction] object ContractKeysValidation {
finalState <- performTraversalContractKeysChecks(
transactionCommitter,
commitContext.recordTime,
contractKeyDamlStateKeys,
contractKeysToContractIds,
stateAfterMonotonicityCheck,
)
@ -66,31 +62,43 @@ private[transaction] object ContractKeysValidation {
private def performTraversalContractKeysChecks(
transactionCommitter: TransactionCommitter,
recordTime: Option[Timestamp],
contractKeyDamlStateKeys: Set[DamlStateKey],
contractKeysToContractIds: Map[DamlContractKey, RawContractId],
transactionEntry: DamlTransactionEntrySummary,
)(implicit loggingContext: LoggingContext): StepResult[DamlTransactionEntrySummary] = {
type KeyValidationStackStatus = Either[KeyValidationError, List[KeyValidationState]]
import scalaz.std.either._
import scalaz.std.list._
import scalaz.syntax.foldable._
val keysValidationOutcome = transactionEntry.transaction
.foldInExecutionOrder[KeyValidationStackStatus](
Right(List(KeyValidationState(activeStateKeys = contractKeyDamlStateKeys)))
)(
exerciseBegin = (status, _, exerciseBeginNode) => {
val newStatus =
onCurrentStatus(status)(
checkNodeContractKey(exerciseBeginNode, contractKeysToContractIds, _)
)
(newStatus, true)
},
rollbackBegin = (status, _, _) =>
// Store state to restore after rollback
(status.map(stack => stack.head +: stack), true),
leaf = (status, _, leafNode) =>
onCurrentStatus(status)(checkNodeContractKey(leafNode, contractKeysToContractIds, _)),
exerciseEnd = (accum, _, _) => accum,
rollbackEnd = (status, _, _) => status.map(stack => stack.tail),
)
import com.daml.lf.transaction.Transaction.{
KeyActive,
KeyCreate,
NegativeKeyLookup,
DuplicateKeys,
InconsistentKeys,
}
val transaction = transactionEntry.transaction
val keysValidationOutcome = for {
keyInputs <- transaction.contractKeyInputs.left.map {
case DuplicateKeys(_) => Duplicate
case InconsistentKeys(_) => Inconsistent
}
_ <- keyInputs.toList.traverse_ { case (key, keyInput) =>
val submittedDamlContractKey = Conversions.encodeGlobalKey(key)
(contractKeysToContractIds.get(submittedDamlContractKey), keyInput) match {
case (Some(_), KeyCreate) => Left(Duplicate)
case (Some(_), NegativeKeyLookup) => Left(Inconsistent)
case (Some(cid), KeyActive(submitted)) =>
if (cid != submitted.coid)
Left(Inconsistent)
else
Right(())
case (None, KeyActive(_)) => Left(Inconsistent)
case (None, KeyCreate | NegativeKeyLookup) => Right(())
}
}
} yield ()
keysValidationOutcome match {
case Right(_) =>
@ -112,90 +120,9 @@ private[transaction] object ContractKeysValidation {
}
}
private def checkNodeContractKey(
node: Node.GenActionNode[NodeId, ContractId],
contractKeysToContractIds: Map[DamlContractKey, RawContractId],
initialState: KeyValidationState,
): KeyValidationStatus =
for {
stateAfterUniquenessCheck <- initialState.onActiveStateKeys(
checkNodeKeyUniqueness(
node,
_,
)
)
finalState <- stateAfterUniquenessCheck.onSubmittedContractKeysToContractIds(
checkNodeKeyConsistency(
contractKeysToContractIds,
node,
_,
)
)
} yield finalState
private[keys] type RawContractId = String
private[keys] sealed trait KeyValidationError
private[keys] sealed trait KeyValidationError extends Product with Serializable
private[keys] case object Duplicate extends KeyValidationError
private[keys] case object Inconsistent extends KeyValidationError
/** The state used during key validation.
*
* @param activeStateKeys The currently active contract keys for uniqueness
* checks starting with the keys active before the transaction.
* @param submittedContractKeysToContractIds Map of keys to their expected assignment at
* the beginning of the transaction starting with an empty map.
* E.g., a create (with nothing before) means the key must have been inactive
* at the beginning.
*/
private[keys] final class KeyValidationState private[ContractKeysValidation] (
private[keys] val activeStateKeys: Set[DamlStateKey],
private[keys] val submittedContractKeysToContractIds: KeyConsistencyValidation.State,
) {
def onSubmittedContractKeysToContractIds(
f: KeyConsistencyValidation.State => KeyConsistencyValidation.Status
): KeyValidationStatus =
f(submittedContractKeysToContractIds).map(newSubmitted =>
new KeyValidationState(
activeStateKeys = this.activeStateKeys,
submittedContractKeysToContractIds = newSubmitted,
)
)
def onActiveStateKeys(
f: Set[DamlStateKey] => Either[KeyValidationError, Set[DamlStateKey]]
): KeyValidationStatus =
f(activeStateKeys).map(newActive =>
new KeyValidationState(
activeStateKeys = newActive,
submittedContractKeysToContractIds = this.submittedContractKeysToContractIds,
)
)
}
private[keys] object KeyValidationState {
def apply(activeStateKeys: Set[DamlStateKey]): KeyValidationState =
new KeyValidationState(activeStateKeys, Map.empty)
def apply(
submittedContractKeysToContractIds: Map[DamlContractKey, Option[
RawContractId
]]
): KeyValidationState =
new KeyValidationState(Set.empty, submittedContractKeysToContractIds)
}
private[keys] type KeyValidationStatus =
Either[KeyValidationError, KeyValidationState]
def onCurrentStatus[E, A](
statusStack: Either[E, List[A]]
)(f: A => Either[E, A]): Either[E, List[A]] =
for {
statusStack <- statusStack
status <- f(statusStack.head)
} yield {
status +: statusStack.tail
}
}

View File

@ -1,109 +0,0 @@
// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.participant.state.kvutils.committer.transaction.keys
import com.daml.ledger.participant.state.kvutils.Conversions
import com.daml.ledger.participant.state.kvutils.DamlKvutils.DamlContractKey
import com.daml.ledger.participant.state.kvutils.committer.transaction.keys.ContractKeysValidation.{
Inconsistent,
KeyValidationError,
RawContractId,
}
import com.daml.lf.data.Ref.TypeConName
import com.daml.lf.transaction.{Node, NodeId}
import com.daml.lf.value.Value
import com.daml.lf.value.Value.ContractId
private[keys] object KeyConsistencyValidation {
type State = Map[DamlContractKey, Option[RawContractId]]
type Status =
Either[KeyValidationError, State]
def checkNodeKeyConsistency(
contractKeysToContractIds: Map[DamlContractKey, RawContractId],
node: Node.GenActionNode[NodeId, ContractId],
submittedContractKeysToContractIds: Map[DamlContractKey, Option[RawContractId]],
): Status =
node match {
case exercise: Node.NodeExercises[NodeId, ContractId] =>
checkKeyConsistency(
contractKeysToContractIds,
exercise.key,
Some(exercise.targetCoid),
exercise.templateId,
submittedContractKeysToContractIds,
)
case create: Node.NodeCreate[ContractId] =>
checkKeyConsistency(
contractKeysToContractIds,
create.key,
None,
create.templateId,
submittedContractKeysToContractIds,
)
case fetch: Node.NodeFetch[ContractId] =>
checkKeyConsistency(
contractKeysToContractIds,
fetch.key,
Some(fetch.coid),
fetch.templateId,
submittedContractKeysToContractIds,
)
case lookupByKey: Node.NodeLookupByKey[ContractId] =>
checkKeyConsistency(
contractKeysToContractIds,
Some(lookupByKey.key),
lookupByKey.result,
lookupByKey.templateId,
submittedContractKeysToContractIds,
)
}
private def checkKeyConsistency(
contractKeysToContractIds: Map[DamlContractKey, RawContractId],
key: Option[Node.KeyWithMaintainers[Value[ContractId]]],
targetContractId: Option[ContractId],
templateId: TypeConName,
submittedContractKeysToContractIds: Map[DamlContractKey, Option[RawContractId]],
): Status =
key match {
case None => Right(submittedContractKeysToContractIds)
case Some(submittedKeyWithMaintainers) =>
val submittedDamlContractKey =
Conversions.encodeContractKey(templateId, submittedKeyWithMaintainers.key)
val newSubmittedContractKeysToContractIds =
if (submittedContractKeysToContractIds.contains(submittedDamlContractKey)) {
submittedContractKeysToContractIds
} else {
submittedContractKeysToContractIds.updated(
submittedDamlContractKey,
targetContractId.map(_.coid),
)
}
checkKeyConsistency(
contractKeysToContractIds,
submittedDamlContractKey,
newSubmittedContractKeysToContractIds,
)
}
private def checkKeyConsistency(
contractKeysToContractIds: Map[DamlContractKey, RawContractId],
submittedDamlContractKey: DamlContractKey,
submittedContractKeysToContractIds: Map[DamlContractKey, Option[RawContractId]],
): Status =
if (
submittedContractKeysToContractIds
.get(
submittedDamlContractKey
)
.flatten != contractKeysToContractIds.get(submittedDamlContractKey)
)
Left(Inconsistent)
else
Right(submittedContractKeysToContractIds)
}

View File

@ -1,41 +0,0 @@
// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.participant.state.kvutils.committer.transaction.keys
import com.daml.ledger.participant.state.kvutils.Conversions
import com.daml.ledger.participant.state.kvutils.DamlKvutils.DamlStateKey
import com.daml.ledger.participant.state.kvutils.committer.transaction.keys.ContractKeysValidation.{
Duplicate,
KeyValidationError,
}
import com.daml.lf.transaction.{Node, NodeId}
import com.daml.lf.value.Value.ContractId
private[keys] object KeyUniquenessValidation {
def checkNodeKeyUniqueness(
node: Node.GenNode[NodeId, ContractId],
activeStateKeys: Set[DamlStateKey],
): Either[KeyValidationError, Set[DamlStateKey]] =
node match {
case exercise: Node.NodeExercises[NodeId, ContractId]
if exercise.key.isDefined && exercise.consuming =>
val stateKey =
Conversions.contractKeyToStateKey(exercise.templateId, exercise.key.get.key)
Right(
activeStateKeys - stateKey
)
case create: Node.NodeCreate[ContractId] if create.key.isDefined =>
val stateKey =
Conversions.contractKeyToStateKey(create.coinst.template, create.key.get.key)
if (activeStateKeys.contains(stateKey))
Left(Duplicate)
else
Right(activeStateKeys + stateKey)
case _ => Right(activeStateKeys)
}
}