mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-20 01:07:18 +03:00
Simplify kvutils contract keys validation (#9628)
changelog_begin changelog_end
This commit is contained in:
parent
010e2b1b27
commit
4e63299919
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user