mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-20 01:07:18 +03:00
[LF] Drop kv-support (#16927)
This commit is contained in:
parent
4845ddf48d
commit
198a81f9c3
@ -1,61 +0,0 @@
|
|||||||
# Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
load(
|
|
||||||
"//bazel_tools:scala.bzl",
|
|
||||||
"da_scala_library",
|
|
||||||
"da_scala_test_suite",
|
|
||||||
"lf_scalacopts",
|
|
||||||
"lf_scalacopts_stricter",
|
|
||||||
)
|
|
||||||
|
|
||||||
da_scala_library(
|
|
||||||
name = "kv-support",
|
|
||||||
srcs = glob(["src/main/**/*.scala"]),
|
|
||||||
scala_deps = [
|
|
||||||
"@maven//:org_scalaz_scalaz_core",
|
|
||||||
],
|
|
||||||
scalacopts = lf_scalacopts_stricter,
|
|
||||||
tags = ["maven_coordinates=com.daml:daml-lf-kv-support:__VERSION__"],
|
|
||||||
deps = [
|
|
||||||
"//daml-lf/archive:daml_lf_1.dev_archive_proto_java",
|
|
||||||
"//daml-lf/archive:daml_lf_archive_reader",
|
|
||||||
"//daml-lf/data",
|
|
||||||
"//daml-lf/language",
|
|
||||||
"//daml-lf/transaction",
|
|
||||||
"//daml-lf/transaction:transaction_proto_java",
|
|
||||||
"//daml-lf/transaction:value_proto_java",
|
|
||||||
"//libs-scala/safe-proto",
|
|
||||||
"@maven//:com_google_protobuf_protobuf_java",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
da_scala_test_suite(
|
|
||||||
name = "test",
|
|
||||||
srcs = glob(["src/test/**/*.scala"]),
|
|
||||||
scala_deps = [
|
|
||||||
"@maven//:org_scalaz_scalaz_core",
|
|
||||||
"@maven//:org_scalacheck_scalacheck",
|
|
||||||
"@maven//:org_scalatest_scalatest_core",
|
|
||||||
"@maven//:org_scalatest_scalatest_matchers_core",
|
|
||||||
"@maven//:org_scalatest_scalatest_shouldmatchers",
|
|
||||||
"@maven//:org_scalatest_scalatest_wordspec",
|
|
||||||
"@maven//:org_scalatestplus_scalacheck_1_15",
|
|
||||||
],
|
|
||||||
scalacopts = lf_scalacopts,
|
|
||||||
deps = [
|
|
||||||
":kv-support",
|
|
||||||
"//daml-lf/archive:daml_lf_1.dev_archive_proto_java",
|
|
||||||
"//daml-lf/archive:daml_lf_archive_reader",
|
|
||||||
"//daml-lf/data",
|
|
||||||
"//daml-lf/encoder",
|
|
||||||
"//daml-lf/language",
|
|
||||||
"//daml-lf/parser",
|
|
||||||
"//daml-lf/transaction",
|
|
||||||
"//daml-lf/transaction:transaction_proto_java",
|
|
||||||
"//daml-lf/transaction:value_proto_java",
|
|
||||||
"//daml-lf/transaction-test-lib",
|
|
||||||
"@maven//:com_google_protobuf_protobuf_java",
|
|
||||||
"@maven//:org_scalatest_scalatest_compatible",
|
|
||||||
],
|
|
||||||
)
|
|
@ -1,7 +0,0 @@
|
|||||||
The KV Daml-LF library
|
|
||||||
======================
|
|
||||||
|
|
||||||
This package contains LF utilities specifically tailored for the KV
|
|
||||||
integration kit. This library does not define a stable public API, and
|
|
||||||
should not be used outside of the KV integration kit.
|
|
||||||
Breaking changes to this library will be made without notice.
|
|
@ -1,20 +0,0 @@
|
|||||||
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package com.daml.lf.kv
|
|
||||||
|
|
||||||
import com.daml.lf.value.ValueCoder
|
|
||||||
|
|
||||||
sealed abstract class ConversionError(val errorMessage: String)
|
|
||||||
extends RuntimeException(errorMessage)
|
|
||||||
|
|
||||||
object ConversionError {
|
|
||||||
final case class ParseError(override val errorMessage: String)
|
|
||||||
extends ConversionError(errorMessage)
|
|
||||||
final case class DecodeError(cause: ValueCoder.DecodeError)
|
|
||||||
extends ConversionError(cause.errorMessage)
|
|
||||||
final case class EncodeError(cause: ValueCoder.EncodeError)
|
|
||||||
extends ConversionError(cause.errorMessage)
|
|
||||||
final case class InternalError(override val errorMessage: String)
|
|
||||||
extends ConversionError(errorMessage)
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package com.daml.lf.kv.archives
|
|
||||||
|
|
||||||
import com.daml.SafeProto
|
|
||||||
import com.daml.lf.archive.{ArchiveParser, Decode, Error => ArchiveError}
|
|
||||||
import com.daml.lf.data.Ref
|
|
||||||
import com.daml.lf.data.Ref.PackageId
|
|
||||||
import com.daml.lf.language.Ast
|
|
||||||
|
|
||||||
object ArchiveConversions {
|
|
||||||
|
|
||||||
def parsePackageId(rawArchive: RawArchive): Either[ArchiveError, Ref.PackageId] =
|
|
||||||
for {
|
|
||||||
archive <- ArchiveParser.fromByteString(rawArchive.byteString)
|
|
||||||
packageId <- Ref.PackageId
|
|
||||||
.fromString(archive.getHash)
|
|
||||||
.left
|
|
||||||
.map(ArchiveError.Parsing)
|
|
||||||
} yield packageId
|
|
||||||
|
|
||||||
def parsePackageIdsAndRawArchives(
|
|
||||||
archives: List[com.daml.daml_lf_dev.DamlLf.Archive]
|
|
||||||
): Either[ArchiveError, Map[Ref.PackageId, RawArchive]] =
|
|
||||||
archives.partitionMap { archive =>
|
|
||||||
for {
|
|
||||||
pkgId <- Ref.PackageId.fromString(archive.getHash).left.map(ArchiveError.Parsing)
|
|
||||||
bytes <- SafeProto.toByteString(archive).left.map(ArchiveError.Encoding)
|
|
||||||
} yield pkgId -> RawArchive(bytes)
|
|
||||||
} match {
|
|
||||||
case (Nil, hashesAndRawArchives) => Right(hashesAndRawArchives.toMap)
|
|
||||||
case (errors, _) => Left(errors.head)
|
|
||||||
}
|
|
||||||
|
|
||||||
def decodePackages(
|
|
||||||
hashesAndArchives: Iterable[RawArchive]
|
|
||||||
): Either[ArchiveError, Map[Ref.PackageId, Ast.Package]] = {
|
|
||||||
type Result = Either[ArchiveError, Map[Ref.PackageId, Ast.Package]]
|
|
||||||
hashesAndArchives
|
|
||||||
.foldLeft[Result](Right(Map.empty)) { (acc, rawArchive) =>
|
|
||||||
for {
|
|
||||||
result <- acc
|
|
||||||
packageAst <- decodePackage(rawArchive)
|
|
||||||
} yield result + packageAst
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def decodePackage(
|
|
||||||
rawArchive: RawArchive
|
|
||||||
): Either[ArchiveError, (PackageId, Ast.Package)] =
|
|
||||||
ArchiveParser
|
|
||||||
.fromByteString(rawArchive.byteString)
|
|
||||||
.flatMap(archive => Decode.decodeArchive(archive))
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package com.daml.lf.kv.archives
|
|
||||||
|
|
||||||
import com.google.protobuf.ByteString
|
|
||||||
|
|
||||||
/** Stores [[com.daml.daml_lf_dev.DamlLf.Archive]] as a [[ByteString]]. */
|
|
||||||
case class RawArchive(byteString: ByteString) extends AnyVal
|
|
@ -1,34 +0,0 @@
|
|||||||
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package com.daml.lf.kv.contracts
|
|
||||||
|
|
||||||
import com.daml.SafeProto
|
|
||||||
import com.daml.lf.kv.ConversionError
|
|
||||||
import com.daml.lf.transaction.{TransactionCoder, TransactionOuterClass, Versioned}
|
|
||||||
import com.daml.lf.value.{Value, ValueCoder}
|
|
||||||
|
|
||||||
import scala.util.{Failure, Success, Try}
|
|
||||||
|
|
||||||
object ContractConversions {
|
|
||||||
|
|
||||||
def encodeContractInstance(
|
|
||||||
coinst: Versioned[Value.ContractInstanceWithAgreement]
|
|
||||||
): Either[ValueCoder.EncodeError, RawContractInstance] =
|
|
||||||
for {
|
|
||||||
message <- TransactionCoder.encodeContractInstance(ValueCoder.CidEncoder, coinst)
|
|
||||||
bytes <- SafeProto.toByteString(message).left.map(ValueCoder.EncodeError(_))
|
|
||||||
} yield RawContractInstance(bytes)
|
|
||||||
|
|
||||||
def decodeContractInstance(
|
|
||||||
rawContractInstance: RawContractInstance
|
|
||||||
): Either[ConversionError, Versioned[Value.ContractInstanceWithAgreement]] =
|
|
||||||
Try(TransactionOuterClass.ContractInstance.parseFrom(rawContractInstance.byteString)) match {
|
|
||||||
case Success(contractInstance) =>
|
|
||||||
TransactionCoder
|
|
||||||
.decodeVersionedContractInstance(ValueCoder.CidDecoder, contractInstance)
|
|
||||||
.left
|
|
||||||
.map(ConversionError.DecodeError)
|
|
||||||
case Failure(throwable) => Left(ConversionError.ParseError(throwable.getMessage))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package com.daml.lf.kv.contracts
|
|
||||||
|
|
||||||
import com.google.protobuf.ByteString
|
|
||||||
|
|
||||||
/** Stores [[com.daml.lf.transaction.TransactionOuterClass.ContractInstance]] as a [[ByteString]]. */
|
|
||||||
case class RawContractInstance(byteString: ByteString) extends AnyVal
|
|
@ -1,18 +0,0 @@
|
|||||||
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package com.daml.lf.kv.transactions
|
|
||||||
|
|
||||||
import com.google.protobuf.ByteString
|
|
||||||
|
|
||||||
/** Stores [[com.daml.lf.transaction.TransactionOuterClass.Transaction]] as a [[ByteString]]. */
|
|
||||||
case class RawTransaction(byteString: ByteString) extends AnyVal
|
|
||||||
|
|
||||||
object RawTransaction {
|
|
||||||
|
|
||||||
/** Stores [[com.daml.lf.transaction.TransactionOuterClass.Node]] as a [[ByteString]]. */
|
|
||||||
case class Node(byteString: ByteString) extends AnyVal
|
|
||||||
|
|
||||||
/** We store node IDs as strings (see [[com.daml.ledger.participant.state.kvutils.store.events.DamlTransactionBlindingInfo.DisclosureEntry]]). */
|
|
||||||
case class NodeId(value: String) extends AnyVal
|
|
||||||
}
|
|
@ -1,273 +0,0 @@
|
|||||||
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package com.daml.lf.kv.transactions
|
|
||||||
|
|
||||||
import com.daml.SafeProto
|
|
||||||
import com.daml.lf.data.{FrontStack, ImmArray}
|
|
||||||
import com.daml.lf.kv.ConversionError
|
|
||||||
import com.daml.lf.transaction.TransactionOuterClass.Node.NodeTypeCase
|
|
||||||
import com.daml.lf.transaction.{
|
|
||||||
GlobalKey,
|
|
||||||
NodeId,
|
|
||||||
TransactionCoder,
|
|
||||||
TransactionOuterClass,
|
|
||||||
VersionedTransaction,
|
|
||||||
}
|
|
||||||
import com.daml.lf.value.{Value, ValueCoder}
|
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
|
||||||
import scala.collection.mutable
|
|
||||||
import scala.jdk.CollectionConverters._
|
|
||||||
import scala.util.{Failure, Success, Try}
|
|
||||||
|
|
||||||
object TransactionConversions {
|
|
||||||
|
|
||||||
def encodeTransaction(
|
|
||||||
tx: VersionedTransaction
|
|
||||||
): Either[ValueCoder.EncodeError, RawTransaction] =
|
|
||||||
for {
|
|
||||||
msg <-
|
|
||||||
TransactionCoder.encodeTransaction(TransactionCoder.NidEncoder, ValueCoder.CidEncoder, tx)
|
|
||||||
bytes <- SafeProto.toByteString(msg).left.map(ValueCoder.EncodeError(_))
|
|
||||||
} yield RawTransaction(bytes)
|
|
||||||
|
|
||||||
def decodeTransaction(
|
|
||||||
rawTx: RawTransaction
|
|
||||||
): Either[ConversionError, VersionedTransaction] =
|
|
||||||
Try(TransactionOuterClass.Transaction.parseFrom(rawTx.byteString)) match {
|
|
||||||
case Success(transaction) =>
|
|
||||||
TransactionCoder
|
|
||||||
.decodeTransaction(
|
|
||||||
TransactionCoder.NidDecoder,
|
|
||||||
ValueCoder.CidDecoder,
|
|
||||||
transaction,
|
|
||||||
)
|
|
||||||
.left
|
|
||||||
.map(ConversionError.DecodeError)
|
|
||||||
case Failure(throwable) => Left(ConversionError.ParseError(throwable.getMessage))
|
|
||||||
}
|
|
||||||
|
|
||||||
def encodeTransactionNodeId(nodeId: NodeId): RawTransaction.NodeId =
|
|
||||||
RawTransaction.NodeId(nodeId.index.toString)
|
|
||||||
|
|
||||||
def decodeTransactionNodeId(transactionNodeId: RawTransaction.NodeId): NodeId =
|
|
||||||
NodeId(transactionNodeId.value.toInt)
|
|
||||||
|
|
||||||
def extractTransactionVersion(rawTransaction: RawTransaction): String =
|
|
||||||
TransactionOuterClass.Transaction.parseFrom(rawTransaction.byteString).getVersion
|
|
||||||
|
|
||||||
def extractNodeId(rawTransactionNode: RawTransaction.Node): RawTransaction.NodeId =
|
|
||||||
RawTransaction.NodeId(
|
|
||||||
TransactionOuterClass.Node.parseFrom(rawTransactionNode.byteString).getNodeId
|
|
||||||
)
|
|
||||||
|
|
||||||
@SuppressWarnings(Array("org.wartremover.warts.NonUnitStatements"))
|
|
||||||
def reconstructTransaction(
|
|
||||||
transactionVersion: String,
|
|
||||||
nodesWithIds: Seq[TransactionNodeIdWithNode],
|
|
||||||
): Either[ConversionError, RawTransaction] = {
|
|
||||||
import scalaz.std.either._
|
|
||||||
import scalaz.std.list._
|
|
||||||
import scalaz.syntax.traverse._
|
|
||||||
|
|
||||||
// Reconstruct roots by considering the transaction nodes in order and
|
|
||||||
// marking all child nodes as non-roots and skipping over them.
|
|
||||||
val nonRoots = mutable.HashSet.empty[RawTransaction.NodeId]
|
|
||||||
val transactionBuilder =
|
|
||||||
TransactionOuterClass.Transaction.newBuilder.setVersion(transactionVersion)
|
|
||||||
|
|
||||||
nodesWithIds
|
|
||||||
.map { case TransactionNodeIdWithNode(rawNodeId, rawNode) =>
|
|
||||||
Try(TransactionOuterClass.Node.parseFrom(rawNode.byteString))
|
|
||||||
.map { node =>
|
|
||||||
transactionBuilder.addNodes(node)
|
|
||||||
if (!nonRoots.contains(rawNodeId)) {
|
|
||||||
transactionBuilder.addRoots(rawNodeId.value)
|
|
||||||
}
|
|
||||||
if (node.hasExercise) {
|
|
||||||
val children =
|
|
||||||
node.getExercise.getChildrenList.asScala.map(RawTransaction.NodeId).toSet
|
|
||||||
nonRoots ++= children
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.toEither
|
|
||||||
.left
|
|
||||||
.map(throwable => ConversionError.ParseError(throwable.getMessage))
|
|
||||||
}
|
|
||||||
.toList
|
|
||||||
.sequence_
|
|
||||||
.flatMap(_ =>
|
|
||||||
SafeProto.toByteString(transactionBuilder.build()) match {
|
|
||||||
case Right(bytes) =>
|
|
||||||
Right(RawTransaction(bytes))
|
|
||||||
case Left(msg) =>
|
|
||||||
Left(ConversionError.EncodeError(ValueCoder.EncodeError(msg)))
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Decodes and extracts outputs of a submitted transaction, that is the IDs and keys of contracts created or updated
|
|
||||||
* by processing a submission.
|
|
||||||
*
|
|
||||||
* The results, among the others, may include contract IDs of transient contracts (created and archived within the same transaction),
|
|
||||||
* contract IDs that may cause divulgence and keys that are not modified. Actual outputs must be a subset of,
|
|
||||||
* or the same as, computed outputs and we currently relax this check by widening the latter set,
|
|
||||||
* treating a node the same regardless of whether it was a child of a rollback node or not, for example.
|
|
||||||
* Computed outputs that are not actual outputs can be safely trimmed.
|
|
||||||
*/
|
|
||||||
def extractTransactionOutputs(
|
|
||||||
rawTransaction: RawTransaction
|
|
||||||
): Either[ConversionError, Set[ContractIdOrKey]] =
|
|
||||||
Try(TransactionOuterClass.Transaction.parseFrom(rawTransaction.byteString)) match {
|
|
||||||
case Failure(throwable) =>
|
|
||||||
Left(ConversionError.ParseError(throwable.getMessage))
|
|
||||||
case Success(transaction) =>
|
|
||||||
TransactionCoder
|
|
||||||
.decodeVersion(transaction.getVersion)
|
|
||||||
.flatMap { txVersion =>
|
|
||||||
transaction.getNodesList.asScala
|
|
||||||
.foldLeft[Either[ValueCoder.DecodeError, Set[ContractIdOrKey]]](Right(Set.empty)) {
|
|
||||||
case (Right(contractIdsOrKeys), node) =>
|
|
||||||
TransactionCoder.decodeNodeVersion(txVersion, node).flatMap { nodeVersion =>
|
|
||||||
node.getNodeTypeCase match {
|
|
||||||
case NodeTypeCase.ROLLBACK =>
|
|
||||||
// Nodes under a rollback node may potentially produce outputs such as divulgence.
|
|
||||||
Right(contractIdsOrKeys)
|
|
||||||
|
|
||||||
case NodeTypeCase.CREATE =>
|
|
||||||
val protoCreate = node.getCreate
|
|
||||||
for {
|
|
||||||
newContractIdsOrKeys <- TransactionCoder
|
|
||||||
.nodeKey(nodeVersion, protoCreate)
|
|
||||||
.map {
|
|
||||||
case Some(key) => contractIdsOrKeys + ContractIdOrKey.Key(key)
|
|
||||||
case None => contractIdsOrKeys
|
|
||||||
}
|
|
||||||
contractId <- ValueCoder.CidDecoder
|
|
||||||
.decode(protoCreate.getContractIdStruct)
|
|
||||||
} yield newContractIdsOrKeys + ContractIdOrKey.Id(contractId)
|
|
||||||
|
|
||||||
case NodeTypeCase.EXERCISE =>
|
|
||||||
val protoExercise = node.getExercise
|
|
||||||
for {
|
|
||||||
newContractIdsOrKeys <- TransactionCoder
|
|
||||||
.nodeKey(nodeVersion, protoExercise)
|
|
||||||
.map {
|
|
||||||
case Some(key) => contractIdsOrKeys + ContractIdOrKey.Key(key)
|
|
||||||
case None => contractIdsOrKeys
|
|
||||||
}
|
|
||||||
contractId <- ValueCoder.CidDecoder
|
|
||||||
.decode(protoExercise.getContractIdStruct)
|
|
||||||
} yield newContractIdsOrKeys + ContractIdOrKey.Id(contractId)
|
|
||||||
|
|
||||||
case NodeTypeCase.FETCH =>
|
|
||||||
// A fetch may cause divulgence, which is why the target contract is a potential output.
|
|
||||||
ValueCoder.CidDecoder.decode(node.getFetch.getContractIdStruct).map {
|
|
||||||
contractId => contractIdsOrKeys + ContractIdOrKey.Id(contractId)
|
|
||||||
}
|
|
||||||
|
|
||||||
case NodeTypeCase.LOOKUP_BY_KEY =>
|
|
||||||
// Contract state only modified on divulgence, in which case we'll have a fetch node,
|
|
||||||
// so no outputs from lookup node.
|
|
||||||
Right(contractIdsOrKeys)
|
|
||||||
|
|
||||||
case NodeTypeCase.NODETYPE_NOT_SET =>
|
|
||||||
Left(ValueCoder.DecodeError("NODETYPE_NOT_SET not supported"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case (Left(error), _) => Left(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.left
|
|
||||||
.map(ConversionError.DecodeError)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Removes `Fetch`, `LookupByKey` and `Rollback` nodes (including their children) from a transaction tree. */
|
|
||||||
def keepCreateAndExerciseNodes(
|
|
||||||
rawTransaction: RawTransaction
|
|
||||||
): Either[ConversionError, RawTransaction] =
|
|
||||||
Try(TransactionOuterClass.Transaction.parseFrom(rawTransaction.byteString)) match {
|
|
||||||
case Failure(throwable) => Left(ConversionError.ParseError(throwable.getMessage))
|
|
||||||
case Success(transaction) =>
|
|
||||||
val nodes = transaction.getNodesList.asScala
|
|
||||||
val nodeMap: Map[String, TransactionOuterClass.Node] =
|
|
||||||
nodes.view.map(n => n.getNodeId -> n).toMap
|
|
||||||
|
|
||||||
@tailrec
|
|
||||||
def goNodesToKeep(
|
|
||||||
toVisit: FrontStack[String],
|
|
||||||
result: Set[String],
|
|
||||||
): Either[ConversionError, Set[String]] = toVisit.pop match {
|
|
||||||
case None => Right(result)
|
|
||||||
case Some((nodeId, previousToVisit)) =>
|
|
||||||
nodeMap.get(nodeId) match {
|
|
||||||
case Some(node) =>
|
|
||||||
node.getNodeTypeCase match {
|
|
||||||
case NodeTypeCase.CREATE =>
|
|
||||||
goNodesToKeep(previousToVisit, result + nodeId)
|
|
||||||
case NodeTypeCase.EXERCISE =>
|
|
||||||
goNodesToKeep(
|
|
||||||
node.getExercise.getChildrenList.asScala.to(ImmArray) ++: previousToVisit,
|
|
||||||
result + nodeId,
|
|
||||||
)
|
|
||||||
case NodeTypeCase.ROLLBACK | NodeTypeCase.FETCH | NodeTypeCase.LOOKUP_BY_KEY |
|
|
||||||
NodeTypeCase.NODETYPE_NOT_SET =>
|
|
||||||
goNodesToKeep(previousToVisit, result)
|
|
||||||
}
|
|
||||||
case None =>
|
|
||||||
Left(ConversionError.InternalError(s"Invalid transaction node id $nodeId"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
goNodesToKeep(transaction.getRootsList.asScala.to(FrontStack), Set.empty).flatMap {
|
|
||||||
nodesToKeep =>
|
|
||||||
val filteredRoots = transaction.getRootsList.asScala.filter(nodesToKeep)
|
|
||||||
|
|
||||||
val filteredNodes = nodes.collect {
|
|
||||||
case node if nodesToKeep(node.getNodeId) =>
|
|
||||||
if (node.hasExercise) {
|
|
||||||
val exerciseNode = node.getExercise
|
|
||||||
val keptChildren =
|
|
||||||
exerciseNode.getChildrenList.asScala.filter(nodesToKeep)
|
|
||||||
val newExerciseNode = exerciseNode.toBuilder
|
|
||||||
.clearChildren()
|
|
||||||
.addAllChildren(keptChildren.asJavaCollection)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
node.toBuilder
|
|
||||||
.setExercise(newExerciseNode)
|
|
||||||
.build()
|
|
||||||
} else {
|
|
||||||
node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val newTransaction = transaction
|
|
||||||
.newBuilderForType()
|
|
||||||
.addAllRoots(filteredRoots.asJavaCollection)
|
|
||||||
.addAllNodes(filteredNodes.asJavaCollection)
|
|
||||||
.setVersion(transaction.getVersion)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
SafeProto.toByteString(newTransaction) match {
|
|
||||||
case Right(bytes) =>
|
|
||||||
Right(RawTransaction(bytes))
|
|
||||||
case Left(msg) =>
|
|
||||||
// Should not happen as removing nodes should results into a smaller transaction.
|
|
||||||
Left(ConversionError.InternalError(msg))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final case class TransactionNodeIdWithNode(
|
|
||||||
nodeId: RawTransaction.NodeId,
|
|
||||||
node: RawTransaction.Node,
|
|
||||||
)
|
|
||||||
|
|
||||||
sealed abstract class ContractIdOrKey extends Product with Serializable
|
|
||||||
object ContractIdOrKey {
|
|
||||||
final case class Id(id: Value.ContractId) extends ContractIdOrKey
|
|
||||||
final case class Key(key: GlobalKey) extends ContractIdOrKey
|
|
||||||
}
|
|
@ -1,50 +0,0 @@
|
|||||||
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package com.daml.lf.kv.transactions
|
|
||||||
|
|
||||||
import com.daml.lf.transaction.Transaction.ChildrenRecursion
|
|
||||||
import com.daml.lf.transaction.{CommittedTransaction, Node, NodeId, VersionedTransaction}
|
|
||||||
|
|
||||||
object TransactionNormalizer {
|
|
||||||
|
|
||||||
// KV specific normalization.
|
|
||||||
// Drop Fetch, Lookup and Rollback nodes from a transaction, keeping Create and Exercise.
|
|
||||||
// Also drop everything contained with a rollback node
|
|
||||||
// Ignore Authority node, but traverse them
|
|
||||||
def normalize(
|
|
||||||
tx: CommittedTransaction
|
|
||||||
): CommittedTransaction = {
|
|
||||||
|
|
||||||
val keepNids: Set[NodeId] =
|
|
||||||
tx.foldInExecutionOrder[Set[NodeId]](Set.empty)(
|
|
||||||
exerciseBegin = (acc, nid, _) => (acc + nid, ChildrenRecursion.DoRecurse),
|
|
||||||
rollbackBegin = (acc, _, _) => (acc, ChildrenRecursion.DoNotRecurse),
|
|
||||||
leaf = (acc, nid, node) =>
|
|
||||||
node match {
|
|
||||||
case _: Node.Create => acc + nid
|
|
||||||
case _: Node.Fetch => acc
|
|
||||||
case _: Node.LookupByKey => acc
|
|
||||||
},
|
|
||||||
exerciseEnd = (acc, _, _) => acc,
|
|
||||||
rollbackEnd = (acc, _, _) => acc,
|
|
||||||
)
|
|
||||||
val filteredNodes =
|
|
||||||
tx.nodes
|
|
||||||
.filter { case (nid, _) => keepNids.contains(nid) }
|
|
||||||
.transform {
|
|
||||||
case (_, node: Node.Exercise) =>
|
|
||||||
node.copy(children = node.children.filter(keepNids.contains))
|
|
||||||
case (_, node: Node.Rollback) =>
|
|
||||||
node.copy(children = node.children.filter(keepNids.contains))
|
|
||||||
case (_, keep) =>
|
|
||||||
keep
|
|
||||||
}
|
|
||||||
|
|
||||||
val filteredRoots = tx.roots.filter(keepNids.contains)
|
|
||||||
CommittedTransaction(
|
|
||||||
VersionedTransaction(tx.version, filteredNodes, filteredRoots)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,210 +0,0 @@
|
|||||||
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package com.daml.lf.kv.transactions
|
|
||||||
|
|
||||||
import com.daml.lf.data.Ref.Party
|
|
||||||
import com.daml.lf.data.{FrontStack, ImmArray, Ref}
|
|
||||||
import com.daml.lf.kv.ConversionError
|
|
||||||
import com.daml.lf.transaction.TransactionOuterClass.Node
|
|
||||||
import com.daml.lf.transaction.{TransactionCoder, TransactionOuterClass, TransactionVersion}
|
|
||||||
import com.daml.lf.value.ValueOuterClass
|
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
|
||||||
import scala.jdk.CollectionConverters._
|
|
||||||
import scala.util.Try
|
|
||||||
import com.daml.lf.value.ValueCoder.DecodeError
|
|
||||||
|
|
||||||
object TransactionTraversal {
|
|
||||||
|
|
||||||
// Helper to traverse the transaction, top-down, while keeping track of the
|
|
||||||
// witnessing parties of each node.
|
|
||||||
def traverseTransactionWithWitnesses(rawTx: RawTransaction)(
|
|
||||||
f: (RawTransaction.NodeId, RawTransaction.Node, Set[Ref.Party]) => Unit
|
|
||||||
): Either[ConversionError, Unit] =
|
|
||||||
for {
|
|
||||||
parsedTransaction <- parseTransaction(rawTx)
|
|
||||||
(txVersion, nodes, initialToVisit) = parsedTransaction
|
|
||||||
_ <- traverseWitnesses(f, txVersion, nodes, initialToVisit)
|
|
||||||
} yield ()
|
|
||||||
|
|
||||||
// Helper to traverse the transaction, top-down, while keeping track of the
|
|
||||||
// witnessing parties of each package.
|
|
||||||
def extractPerPackageWitnesses(
|
|
||||||
rawTx: RawTransaction
|
|
||||||
): Either[ConversionError, Map[String, Set[Ref.Party]]] =
|
|
||||||
for {
|
|
||||||
parsedTransaction <- parseTransaction(rawTx)
|
|
||||||
(txVersion, nodes, initialToVisit) = parsedTransaction
|
|
||||||
result <- traverseWitnessesWithPackages(txVersion, nodes, initialToVisit)
|
|
||||||
} yield result
|
|
||||||
|
|
||||||
private def parseTransaction(rawTx: RawTransaction): Either[
|
|
||||||
ConversionError,
|
|
||||||
(TransactionVersion, Map[String, Node], FrontStack[(RawTransaction.NodeId, Set[Party])]),
|
|
||||||
] = {
|
|
||||||
for {
|
|
||||||
tx <- Try(TransactionOuterClass.Transaction.parseFrom(rawTx.byteString)).toEither.left.map(
|
|
||||||
throwable => ConversionError.ParseError(throwable.getMessage)
|
|
||||||
)
|
|
||||||
txVersion <- TransactionVersion.fromString(tx.getVersion).left.map(ConversionError.ParseError)
|
|
||||||
nodes = tx.getNodesList.iterator.asScala.map(node => node.getNodeId -> node).toMap
|
|
||||||
initialToVisit = tx.getRootsList.asScala.view
|
|
||||||
.map(RawTransaction.NodeId(_) -> Set.empty[Ref.Party])
|
|
||||||
.to(FrontStack)
|
|
||||||
} yield { (txVersion, nodes, initialToVisit) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@tailrec
|
|
||||||
private def traverseWitnessesWithPackages(
|
|
||||||
txVersion: TransactionVersion,
|
|
||||||
nodes: Map[String, Node],
|
|
||||||
toVisit: FrontStack[(RawTransaction.NodeId, Set[Ref.Party])],
|
|
||||||
packagesToParties: Map[String, Set[Ref.Party]] = Map.empty,
|
|
||||||
): Either[ConversionError, Map[String, Set[Ref.Party]]] = {
|
|
||||||
toVisit.pop match {
|
|
||||||
case None => Right(packagesToParties)
|
|
||||||
case Some(((nodeId, parentWitnesses), toVisit)) =>
|
|
||||||
val node = nodes(nodeId.value)
|
|
||||||
lazy val witnesses = informeesOfNode(txVersion, node).map(_ ++ parentWitnesses)
|
|
||||||
node.getNodeTypeCase match {
|
|
||||||
case Node.NodeTypeCase.EXERCISE =>
|
|
||||||
witnesses match {
|
|
||||||
case Left(value) => Left(value)
|
|
||||||
case Right(witnesses) =>
|
|
||||||
val exercise = node.getExercise
|
|
||||||
// Recurse into children (if any).
|
|
||||||
val next = exercise.getChildrenList.asScala.view
|
|
||||||
.map(RawTransaction.NodeId(_) -> witnesses)
|
|
||||||
.to(ImmArray)
|
|
||||||
val packagesToPartiesWithTemplateParties =
|
|
||||||
addWitnessesToPackage(packagesToParties, exercise.getTemplateId, witnesses)
|
|
||||||
val currentNodePackagesWithWitnesses =
|
|
||||||
Option
|
|
||||||
.when(exercise.hasInterfaceId)(
|
|
||||||
exercise.getInterfaceId
|
|
||||||
)
|
|
||||||
.map(addWitnessesToPackage(packagesToPartiesWithTemplateParties, _, witnesses))
|
|
||||||
.getOrElse(packagesToPartiesWithTemplateParties)
|
|
||||||
traverseWitnessesWithPackages(
|
|
||||||
txVersion,
|
|
||||||
nodes,
|
|
||||||
next ++: toVisit,
|
|
||||||
currentNodePackagesWithWitnesses,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case Node.NodeTypeCase.FETCH =>
|
|
||||||
val templateId = node.getFetch.getTemplateId
|
|
||||||
witnesses match {
|
|
||||||
case Left(error) => Left(error)
|
|
||||||
case Right(witnesses) =>
|
|
||||||
traverseWitnessesWithPackages(
|
|
||||||
txVersion,
|
|
||||||
nodes,
|
|
||||||
toVisit,
|
|
||||||
addWitnessesToPackage(packagesToParties, templateId, witnesses),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case Node.NodeTypeCase.CREATE =>
|
|
||||||
val templateId = node.getCreate.getTemplateId
|
|
||||||
witnesses match {
|
|
||||||
case Left(error) => Left(error)
|
|
||||||
case Right(witnesses) =>
|
|
||||||
traverseWitnessesWithPackages(
|
|
||||||
txVersion,
|
|
||||||
nodes,
|
|
||||||
toVisit,
|
|
||||||
addWitnessesToPackage(packagesToParties, templateId, witnesses),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case Node.NodeTypeCase.LOOKUP_BY_KEY =>
|
|
||||||
val templateId = node.getLookupByKey.getTemplateId
|
|
||||||
witnesses match {
|
|
||||||
case Left(error) => Left(error)
|
|
||||||
case Right(witnesses) =>
|
|
||||||
traverseWitnessesWithPackages(
|
|
||||||
txVersion,
|
|
||||||
nodes,
|
|
||||||
toVisit,
|
|
||||||
addWitnessesToPackage(packagesToParties, templateId, witnesses),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case Node.NodeTypeCase.ROLLBACK =>
|
|
||||||
// Rollback nodes have only the parent witnesses.
|
|
||||||
val rollback = node.getRollback
|
|
||||||
// Recurse into children (if any).
|
|
||||||
val next = rollback.getChildrenList.asScala.view
|
|
||||||
.map(RawTransaction.NodeId(_) -> parentWitnesses)
|
|
||||||
.to(ImmArray)
|
|
||||||
traverseWitnessesWithPackages(
|
|
||||||
txVersion,
|
|
||||||
nodes,
|
|
||||||
next ++: toVisit,
|
|
||||||
packagesToParties,
|
|
||||||
)
|
|
||||||
case Node.NodeTypeCase.NODETYPE_NOT_SET =>
|
|
||||||
Left(ConversionError.DecodeError(DecodeError("NodeType not set.")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def addWitnessesToPackage(
|
|
||||||
packagesToParties: Map[String, Set[Ref.Party]],
|
|
||||||
templateId: ValueOuterClass.Identifier,
|
|
||||||
witnesses: Set[Party],
|
|
||||||
) = {
|
|
||||||
val packageExistingParties = packagesToParties.get(templateId.getPackageId)
|
|
||||||
val packageWithAddedParties = packageExistingParties.getOrElse(Set.empty) ++ witnesses
|
|
||||||
packagesToParties + (templateId.getPackageId -> packageWithAddedParties)
|
|
||||||
}
|
|
||||||
|
|
||||||
@tailrec
|
|
||||||
private def traverseWitnesses(
|
|
||||||
f: (RawTransaction.NodeId, RawTransaction.Node, Set[Ref.Party]) => Unit,
|
|
||||||
txVersion: TransactionVersion,
|
|
||||||
nodes: Map[String, Node],
|
|
||||||
toVisit: FrontStack[(RawTransaction.NodeId, Set[Ref.Party])],
|
|
||||||
): Either[ConversionError, Unit] = {
|
|
||||||
toVisit.pop match {
|
|
||||||
case None => Right(())
|
|
||||||
case Some(((nodeId, parentWitnesses), toVisit)) =>
|
|
||||||
val node = nodes(nodeId.value)
|
|
||||||
informeesOfNode(txVersion, node) match {
|
|
||||||
case Left(error) => Left(error)
|
|
||||||
case Right(nodeWitnesses) =>
|
|
||||||
val witnesses = parentWitnesses union nodeWitnesses
|
|
||||||
// Here node.toByteString is safe.
|
|
||||||
// Indeed node is a submessage of the transaction `rawTx` we got serialized
|
|
||||||
// as input of `traverseTransactionWithWitnesses` and successfully decoded, i.e.
|
|
||||||
// `rawTx` requires less than 2GB to be serialized, so does <node`.
|
|
||||||
// See com.daml.SafeProto for more details about issues with the toByteString method.
|
|
||||||
f(nodeId, RawTransaction.Node(node.toByteString), witnesses)
|
|
||||||
// Recurse into children (if any).
|
|
||||||
node.getNodeTypeCase match {
|
|
||||||
case Node.NodeTypeCase.EXERCISE =>
|
|
||||||
val next = node.getExercise.getChildrenList.asScala.view
|
|
||||||
.map(RawTransaction.NodeId(_) -> witnesses)
|
|
||||||
.to(ImmArray)
|
|
||||||
traverseWitnesses(f, txVersion, nodes, next ++: toVisit)
|
|
||||||
case Node.NodeTypeCase.NODETYPE_NOT_SET =>
|
|
||||||
Left(ConversionError.DecodeError(DecodeError("NodeType not set.")))
|
|
||||||
case Node.NodeTypeCase.ROLLBACK =>
|
|
||||||
Left(ConversionError.DecodeError(DecodeError("Cannot traverse rollback nodes.")))
|
|
||||||
case _ =>
|
|
||||||
traverseWitnesses(f, txVersion, nodes, toVisit)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private[this] def informeesOfNode(
|
|
||||||
txVersion: TransactionVersion,
|
|
||||||
node: TransactionOuterClass.Node,
|
|
||||||
): Either[ConversionError.DecodeError, Set[Party]] =
|
|
||||||
TransactionCoder
|
|
||||||
.protoActionNodeInfo(txVersion, node)
|
|
||||||
.map(_.informeesOfNode)
|
|
||||||
.left
|
|
||||||
.map(ConversionError.DecodeError)
|
|
||||||
|
|
||||||
}
|
|
@ -1,114 +0,0 @@
|
|||||||
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package com.daml.lf.kv.archives
|
|
||||||
|
|
||||||
import com.daml.daml_lf_dev.DamlLf.Archive
|
|
||||||
import com.daml.lf.archive.testing.Encode
|
|
||||||
import com.daml.lf.archive.{Error => ArchiveError}
|
|
||||||
import com.daml.lf.data.Ref
|
|
||||||
import com.daml.lf.language.Ast
|
|
||||||
import com.daml.lf.testing.parser.Implicits._
|
|
||||||
import com.daml.lf.testing.parser.Implicits.defaultParserParameters
|
|
||||||
import com.google.protobuf.ByteString
|
|
||||||
import org.scalatest.Inside
|
|
||||||
import org.scalatest.matchers.should.Matchers
|
|
||||||
import org.scalatest.wordspec.AnyWordSpec
|
|
||||||
|
|
||||||
class ArchiveConversionsSpec extends AnyWordSpec with Matchers with Inside {
|
|
||||||
|
|
||||||
import ArchiveConversionsSpec._
|
|
||||||
|
|
||||||
"parsePackageId" should {
|
|
||||||
"successfully parse a package ID" in {
|
|
||||||
ArchiveConversions.parsePackageId(
|
|
||||||
RawArchive(Archive.newBuilder().setHash("aPackageId").build().toByteString)
|
|
||||||
) shouldBe Right(Ref.PackageId.assertFromString("aPackageId"))
|
|
||||||
}
|
|
||||||
|
|
||||||
"fail on a broken archive" in {
|
|
||||||
inside(ArchiveConversions.parsePackageId(RawArchive(ByteString.copyFromUtf8("wrong")))) {
|
|
||||||
case Left(error: ArchiveError.IO) =>
|
|
||||||
error.msg shouldBe "IO error: com.google.protobuf.InvalidProtocolBufferException$InvalidWireTypeException: Protocol message tag had invalid wire type."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"fail on a broken package ID" in {
|
|
||||||
ArchiveConversions.parsePackageId(
|
|
||||||
RawArchive(Archive.newBuilder().setHash("???").build().toByteString)
|
|
||||||
) shouldBe Left(
|
|
||||||
ArchiveError.Parsing("non expected character 0x3f in Daml-LF Package ID \"???\"")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"parsePackageIdsAndRawArchives" should {
|
|
||||||
"successfully parse package IDs and raw archives" in {
|
|
||||||
val packageId1 = Ref.PackageId.assertFromString("packageId1")
|
|
||||||
val archive1 = Archive.newBuilder().setHash(packageId1).build()
|
|
||||||
val packageId2 = Ref.PackageId.assertFromString("packageId2")
|
|
||||||
val archive2 = Archive.newBuilder().setHash(packageId2).build()
|
|
||||||
|
|
||||||
ArchiveConversions.parsePackageIdsAndRawArchives(List(archive1, archive2)) shouldBe Right(
|
|
||||||
Map(
|
|
||||||
packageId1 -> RawArchive(archive1.toByteString),
|
|
||||||
packageId2 -> RawArchive(archive2.toByteString),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
"fail on a broken package ID" in {
|
|
||||||
val archive = Archive.newBuilder().setHash("???").build()
|
|
||||||
|
|
||||||
ArchiveConversions.parsePackageIdsAndRawArchives(List(archive)) shouldBe Left(
|
|
||||||
ArchiveError.Parsing("non expected character 0x3f in Daml-LF Package ID \"???\"")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"decodePackages" should {
|
|
||||||
"successfully decode packages" in {
|
|
||||||
val List(archive1, archive2) = for {
|
|
||||||
differentiator <- List(1, 2)
|
|
||||||
archive = encodePackage(
|
|
||||||
p"""
|
|
||||||
metadata ( 'Package$differentiator' : '0.0.1' )
|
|
||||||
|
|
||||||
module Mod$differentiator {
|
|
||||||
record Record$differentiator = {};
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
} yield archive
|
|
||||||
|
|
||||||
inside(
|
|
||||||
ArchiveConversions.decodePackages(
|
|
||||||
Iterable(
|
|
||||||
RawArchive(archive1.toByteString),
|
|
||||||
RawArchive(archive2.toByteString),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) { case Right(map) =>
|
|
||||||
map should have size 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"fail on a broken package ID" in {
|
|
||||||
val archive = RawArchive(Archive.newBuilder().setHash("???").build().toByteString)
|
|
||||||
|
|
||||||
ArchiveConversions.decodePackages(Iterable(archive)) shouldBe Left(
|
|
||||||
ArchiveError.Parsing(
|
|
||||||
"Invalid hash: non expected character 0x3f in Daml-LF Package ID \"???\""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object ArchiveConversionsSpec {
|
|
||||||
def encodePackage[P](pkg: Ast.Package): Archive =
|
|
||||||
Encode.encodeArchive(
|
|
||||||
defaultParserParameters.defaultPackageId -> pkg,
|
|
||||||
defaultParserParameters.languageVersion,
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package com.daml.lf.kv.contracts
|
|
||||||
|
|
||||||
import com.daml.lf.data.Ref
|
|
||||||
import com.daml.lf.kv.ConversionError
|
|
||||||
import com.daml.lf.transaction.{TransactionOuterClass, TransactionVersion, Versioned}
|
|
||||||
import com.daml.lf.value.{Value, ValueOuterClass}
|
|
||||||
import com.google.protobuf
|
|
||||||
import com.google.protobuf.ByteString
|
|
||||||
import org.scalatest.matchers.should.Matchers
|
|
||||||
import org.scalatest.wordspec.AnyWordSpec
|
|
||||||
|
|
||||||
class ContractConversionsSpec extends AnyWordSpec with Matchers {
|
|
||||||
import ContractConversionsSpec._
|
|
||||||
|
|
||||||
"encodeContractInstance" should {
|
|
||||||
"successfully encode a contract instance" in {
|
|
||||||
ContractConversions.encodeContractInstance(aContractInstance) shouldBe Right(
|
|
||||||
aRawContractInstance
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"decodeContractInstance" should {
|
|
||||||
"successfully decode a contract instance" in {
|
|
||||||
ContractConversions.decodeContractInstance(aRawContractInstance) shouldBe Right(
|
|
||||||
aContractInstance
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
"fail on a broken contract instance" in {
|
|
||||||
ContractConversions.decodeContractInstance(
|
|
||||||
RawContractInstance(ByteString.copyFromUtf8("wrong"))
|
|
||||||
) shouldBe Left(ConversionError.ParseError("Protocol message tag had invalid wire type."))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object ContractConversionsSpec {
|
|
||||||
private val aDummyName = "dummyName"
|
|
||||||
private val aModuleName = "DummyModule"
|
|
||||||
private val aPackageId = "-dummyPkg-"
|
|
||||||
private val aAgreement = "agreement"
|
|
||||||
|
|
||||||
private val aContractInstance = Versioned(
|
|
||||||
version = TransactionVersion.VDev,
|
|
||||||
Value.ContractInstanceWithAgreement(
|
|
||||||
Value.ContractInstance(
|
|
||||||
template = Ref.Identifier(
|
|
||||||
Ref.PackageId.assertFromString(aPackageId),
|
|
||||||
Ref.QualifiedName.assertFromString(s"$aModuleName:$aDummyName"),
|
|
||||||
),
|
|
||||||
arg = Value.ValueUnit,
|
|
||||||
),
|
|
||||||
agreementText = aAgreement,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
private val aRawContractInstance = RawContractInstance(
|
|
||||||
TransactionOuterClass.ContractInstance
|
|
||||||
.newBuilder()
|
|
||||||
.setTemplateId(
|
|
||||||
ValueOuterClass.Identifier
|
|
||||||
.newBuilder()
|
|
||||||
.setPackageId(aPackageId)
|
|
||||||
.addModuleName(aModuleName)
|
|
||||||
.addName(aDummyName)
|
|
||||||
)
|
|
||||||
.setAgreement(aAgreement)
|
|
||||||
.setArgVersioned(
|
|
||||||
ValueOuterClass.VersionedValue
|
|
||||||
.newBuilder()
|
|
||||||
.setValue(
|
|
||||||
ValueOuterClass.Value
|
|
||||||
.newBuilder()
|
|
||||||
.setUnit(protobuf.Empty.newBuilder())
|
|
||||||
.build()
|
|
||||||
.toByteString
|
|
||||||
)
|
|
||||||
.setVersion(TransactionVersion.VDev.protoValue)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
.toByteString
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,574 +0,0 @@
|
|||||||
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package com.daml.lf.kv.transactions
|
|
||||||
|
|
||||||
import com.daml.lf.transaction.test.TestNodeBuilder.CreateKey.{NoKey, SignatoryMaintainerKey}
|
|
||||||
import com.daml.lf.data.{ImmArray, Ref}
|
|
||||||
import com.daml.lf.kv.ConversionError
|
|
||||||
import com.daml.lf.kv.transactions.TransactionConversions.{
|
|
||||||
extractNodeId,
|
|
||||||
extractTransactionVersion,
|
|
||||||
reconstructTransaction,
|
|
||||||
}
|
|
||||||
import com.daml.lf.transaction.test.TreeTransactionBuilder
|
|
||||||
import com.daml.lf.transaction._
|
|
||||||
import com.daml.lf.value.{Value, ValueCoder, ValueOuterClass}
|
|
||||||
import com.daml.lf.transaction.test.TestNodeBuilder
|
|
||||||
import com.daml.lf.crypto
|
|
||||||
import com.google.protobuf
|
|
||||||
import com.google.protobuf.ByteString
|
|
||||||
import org.scalatest.matchers.should.Matchers
|
|
||||||
import org.scalatest.wordspec.AnyWordSpec
|
|
||||||
|
|
||||||
import scala.jdk.CollectionConverters._
|
|
||||||
|
|
||||||
class TransactionConversionsSpec extends AnyWordSpec with Matchers {
|
|
||||||
|
|
||||||
import TransactionConversionsSpec._
|
|
||||||
import TreeTransactionBuilder._
|
|
||||||
|
|
||||||
private val cid = Value.ContractId.V1(crypto.Hash.hashPrivateKey("#id"))
|
|
||||||
|
|
||||||
"TransactionUtilsSpec" should {
|
|
||||||
"encodeTransaction" in {
|
|
||||||
TransactionConversions.encodeTransaction(aVersionedTransaction) shouldBe Right(
|
|
||||||
aRawTransaction
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
"extractTransactionVersion" in {
|
|
||||||
val actualVersion = extractTransactionVersion(aRawTransaction)
|
|
||||||
actualVersion shouldBe TransactionVersion.VDev.protoValue
|
|
||||||
}
|
|
||||||
|
|
||||||
"extractNodeId" in {
|
|
||||||
val actualNodeId = extractNodeId(aRawRootNode)
|
|
||||||
actualNodeId shouldBe RawTransaction.NodeId("1")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"decodeTransaction" should {
|
|
||||||
"successfully decode a transaction" in {
|
|
||||||
TransactionConversions.decodeTransaction(aRawTransaction) shouldBe Right(
|
|
||||||
aVersionedTransaction
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
"fail to decode a non-parsable transaction" in {
|
|
||||||
TransactionConversions.decodeTransaction(
|
|
||||||
RawTransaction(ByteString.copyFromUtf8("wrong"))
|
|
||||||
) shouldBe Left(
|
|
||||||
ConversionError.ParseError("Protocol message tag had invalid wire type.")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"reconstructTransaction" should {
|
|
||||||
"successfully reconstruct a transaction" in {
|
|
||||||
val reconstructedTransaction = reconstructTransaction(
|
|
||||||
TransactionVersion.VDev.protoValue,
|
|
||||||
Seq(
|
|
||||||
TransactionNodeIdWithNode(aRawRootNodeId, aRawRootNode),
|
|
||||||
TransactionNodeIdWithNode(aRawChildNodeId, aRawChildNode),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
reconstructedTransaction shouldBe Right(aRawTransaction)
|
|
||||||
}
|
|
||||||
|
|
||||||
"fail to reconstruct a non-parsable transaction" in {
|
|
||||||
val reconstructedTransaction = reconstructTransaction(
|
|
||||||
TransactionVersion.VDev.protoValue,
|
|
||||||
Seq(
|
|
||||||
TransactionNodeIdWithNode(
|
|
||||||
aRawRootNodeId,
|
|
||||||
RawTransaction.Node(ByteString.copyFromUtf8("wrong")),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
reconstructedTransaction shouldBe Left(
|
|
||||||
ConversionError.ParseError("Protocol message tag had invalid wire type.")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"extractTransactionOutputs" should {
|
|
||||||
"return a single output for a create without a key" in {
|
|
||||||
val builder = TestNodeBuilder
|
|
||||||
val createNode = create(builder, cid)
|
|
||||||
val transaction = toVersionedTransaction(createNode)
|
|
||||||
val result = TransactionConversions.extractTransactionOutputs(
|
|
||||||
TransactionConversions
|
|
||||||
.encodeTransaction(transaction)
|
|
||||||
.fold(error => fail(error.errorMessage), identity)
|
|
||||||
)
|
|
||||||
result shouldBe Right(Set(ContractIdOrKey.Id(createNode.coid)))
|
|
||||||
}
|
|
||||||
|
|
||||||
"return two outputs for a create with a key" in {
|
|
||||||
val builder = TestNodeBuilder
|
|
||||||
val createNode = create(builder, cid, hasKey = true)
|
|
||||||
val transaction = toVersionedTransaction(createNode)
|
|
||||||
val result = TransactionConversions.extractTransactionOutputs(
|
|
||||||
TransactionConversions
|
|
||||||
.encodeTransaction(transaction)
|
|
||||||
.fold(error => fail(error.errorMessage), identity)
|
|
||||||
)
|
|
||||||
result shouldBe Right(
|
|
||||||
Set(
|
|
||||||
ContractIdOrKey.Id(createNode.coid),
|
|
||||||
ContractIdOrKey.Key(aGlobalKey),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
"return a single output for a transient contract" in {
|
|
||||||
val builder = TestNodeBuilder
|
|
||||||
val createNode = create(builder, cid, hasKey = true)
|
|
||||||
val transaction = toVersionedTransaction(
|
|
||||||
createNode,
|
|
||||||
exercise(builder, cid),
|
|
||||||
)
|
|
||||||
val result = TransactionConversions.extractTransactionOutputs(
|
|
||||||
TransactionConversions
|
|
||||||
.encodeTransaction(transaction)
|
|
||||||
.fold(error => fail(error.errorMessage), identity)
|
|
||||||
)
|
|
||||||
result shouldBe Right(
|
|
||||||
Set(
|
|
||||||
ContractIdOrKey.Id(createNode.coid),
|
|
||||||
ContractIdOrKey.Key(aGlobalKey),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
"return a single output for an exercise without a key" in {
|
|
||||||
val builder = TestNodeBuilder
|
|
||||||
val exerciseNode = exercise(builder, cid)
|
|
||||||
val transaction = toVersionedTransaction(exerciseNode)
|
|
||||||
val result = TransactionConversions.extractTransactionOutputs(
|
|
||||||
TransactionConversions
|
|
||||||
.encodeTransaction(transaction)
|
|
||||||
.fold(error => fail(error.errorMessage), identity)
|
|
||||||
)
|
|
||||||
result shouldBe Right(Set(ContractIdOrKey.Id(exerciseNode.targetCoid)))
|
|
||||||
}
|
|
||||||
|
|
||||||
"return two outputs for a consuming exercise with a key" in {
|
|
||||||
val builder = TestNodeBuilder
|
|
||||||
val exerciseNode = exercise(builder, cid, hasKey = true)
|
|
||||||
val transaction = toVersionedTransaction(exerciseNode)
|
|
||||||
val result = TransactionConversions.extractTransactionOutputs(
|
|
||||||
TransactionConversions
|
|
||||||
.encodeTransaction(transaction)
|
|
||||||
.fold(error => fail(error.errorMessage), identity)
|
|
||||||
)
|
|
||||||
result shouldBe Right(
|
|
||||||
Set(
|
|
||||||
ContractIdOrKey.Id(exerciseNode.targetCoid),
|
|
||||||
ContractIdOrKey.Key(aGlobalKey),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
"return two outputs for a non-consuming exercise with a key" in {
|
|
||||||
val builder = TestNodeBuilder
|
|
||||||
val exerciseNode = exercise(builder, cid, hasKey = true, consuming = false)
|
|
||||||
val transaction = toVersionedTransaction(exerciseNode)
|
|
||||||
val result = TransactionConversions.extractTransactionOutputs(
|
|
||||||
TransactionConversions
|
|
||||||
.encodeTransaction(transaction)
|
|
||||||
.fold(error => fail(error.errorMessage), identity)
|
|
||||||
)
|
|
||||||
result shouldBe Right(
|
|
||||||
Set(
|
|
||||||
ContractIdOrKey.Id(exerciseNode.targetCoid),
|
|
||||||
ContractIdOrKey.Key(aGlobalKey),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
"return one output per fetch and fetch-by-key" in {
|
|
||||||
val builder = TestNodeBuilder
|
|
||||||
val fetchNode1 =
|
|
||||||
fetch(builder, Value.ContractId.V1(crypto.Hash.hashPrivateKey("#id1")), byKey = true)
|
|
||||||
val fetchNode2 =
|
|
||||||
fetch(builder, Value.ContractId.V1(crypto.Hash.hashPrivateKey("#id2")), byKey = false)
|
|
||||||
val transaction = toVersionedTransaction(fetchNode1, fetchNode2)
|
|
||||||
val result = TransactionConversions.extractTransactionOutputs(
|
|
||||||
TransactionConversions
|
|
||||||
.encodeTransaction(transaction)
|
|
||||||
.fold(error => fail(error.errorMessage), identity)
|
|
||||||
)
|
|
||||||
result shouldBe Right(
|
|
||||||
Set(
|
|
||||||
ContractIdOrKey.Id(fetchNode1.coid),
|
|
||||||
ContractIdOrKey.Id(fetchNode2.coid),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
"return no output for a failing lookup-by-key" in {
|
|
||||||
val builder = TestNodeBuilder
|
|
||||||
val transaction = toVersionedTransaction(lookup(builder, cid, found = false))
|
|
||||||
val result = TransactionConversions.extractTransactionOutputs(
|
|
||||||
TransactionConversions
|
|
||||||
.encodeTransaction(transaction)
|
|
||||||
.fold(error => fail(error.errorMessage), identity)
|
|
||||||
)
|
|
||||||
result shouldBe Right(Set.empty)
|
|
||||||
}
|
|
||||||
|
|
||||||
"return no output for a successful lookup-by-key" in {
|
|
||||||
val builder = TestNodeBuilder
|
|
||||||
val transaction = toVersionedTransaction(lookup(builder, cid, found = true))
|
|
||||||
val result = TransactionConversions.extractTransactionOutputs(
|
|
||||||
TransactionConversions
|
|
||||||
.encodeTransaction(transaction)
|
|
||||||
.fold(error => fail(error.errorMessage), identity)
|
|
||||||
)
|
|
||||||
result shouldBe Right(Set.empty)
|
|
||||||
}
|
|
||||||
|
|
||||||
"return outputs for nodes under a rollback node" in {
|
|
||||||
val builder = TestNodeBuilder
|
|
||||||
val createNode =
|
|
||||||
create(builder, Value.ContractId.V1(crypto.Hash.hashPrivateKey("#id1")), hasKey = true)
|
|
||||||
val exerciseNode =
|
|
||||||
exercise(builder, Value.ContractId.V1(crypto.Hash.hashPrivateKey("#id2")), hasKey = true)
|
|
||||||
val fetchNode =
|
|
||||||
fetch(builder, Value.ContractId.V1(crypto.Hash.hashPrivateKey("#id3")), byKey = true)
|
|
||||||
|
|
||||||
val tx = toVersionedTransaction(
|
|
||||||
builder
|
|
||||||
.rollback()
|
|
||||||
.withChildren(
|
|
||||||
create(builder, Value.ContractId.V1(crypto.Hash.hashPrivateKey("#id1")), hasKey = true),
|
|
||||||
exercise(
|
|
||||||
builder,
|
|
||||||
Value.ContractId.V1(crypto.Hash.hashPrivateKey("#id2")),
|
|
||||||
hasKey = true,
|
|
||||||
),
|
|
||||||
fetch(builder, Value.ContractId.V1(crypto.Hash.hashPrivateKey("#id3")), byKey = true),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val result = TransactionConversions.extractTransactionOutputs(
|
|
||||||
TransactionConversions
|
|
||||||
.encodeTransaction(tx)
|
|
||||||
.fold(error => fail(error.errorMessage), identity)
|
|
||||||
)
|
|
||||||
result shouldBe Right(
|
|
||||||
Set(
|
|
||||||
ContractIdOrKey.Id(createNode.coid),
|
|
||||||
ContractIdOrKey.Key(aGlobalKey),
|
|
||||||
ContractIdOrKey.Id(exerciseNode.targetCoid),
|
|
||||||
ContractIdOrKey.Id(fetchNode.coid),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
"fail on a non-parsable transaction" in {
|
|
||||||
val result = TransactionConversions.extractTransactionOutputs(
|
|
||||||
RawTransaction(ByteString.copyFromUtf8("wrong"))
|
|
||||||
)
|
|
||||||
result shouldBe Left(
|
|
||||||
ConversionError.ParseError("Protocol message tag had invalid wire type.")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
"fail on a broken transaction version" in {
|
|
||||||
val result = TransactionConversions.extractTransactionOutputs(
|
|
||||||
RawTransaction(
|
|
||||||
TransactionOuterClass.Transaction.newBuilder().setVersion("???").build().toByteString
|
|
||||||
)
|
|
||||||
)
|
|
||||||
result shouldBe Left(
|
|
||||||
ConversionError.DecodeError(ValueCoder.DecodeError("Unsupported transaction version '???'"))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
"fail on a broken transaction node version" in {
|
|
||||||
val result = TransactionConversions.extractTransactionOutputs(
|
|
||||||
RawTransaction(
|
|
||||||
TransactionOuterClass.Transaction
|
|
||||||
.newBuilder()
|
|
||||||
.setVersion(TransactionVersion.VDev.protoValue)
|
|
||||||
.addRoots(aRawRootNodeId.value)
|
|
||||||
.addNodes(buildProtoNode(aRawRootNodeId.value)(_.setVersion("???")))
|
|
||||||
.build()
|
|
||||||
.toByteString
|
|
||||||
)
|
|
||||||
)
|
|
||||||
result shouldBe Left(
|
|
||||||
ConversionError.DecodeError(ValueCoder.DecodeError("Unsupported transaction version '???'"))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
"fail on a broken contract ID" in {
|
|
||||||
val result = TransactionConversions.extractTransactionOutputs(
|
|
||||||
RawTransaction(
|
|
||||||
TransactionOuterClass.Transaction
|
|
||||||
.newBuilder()
|
|
||||||
.setVersion(TransactionVersion.VDev.protoValue)
|
|
||||||
.addRoots(aRawRootNodeId.value)
|
|
||||||
.addNodes(
|
|
||||||
buildProtoNode(aRawRootNodeId.value) { builder =>
|
|
||||||
builder.setVersion(TransactionVersion.VDev.protoValue)
|
|
||||||
builder.setExercise(
|
|
||||||
exerciseBuilder()
|
|
||||||
.setContractIdStruct(
|
|
||||||
ValueOuterClass.ContractId.newBuilder().setContractId("???")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
.toByteString
|
|
||||||
)
|
|
||||||
)
|
|
||||||
result shouldBe Left(
|
|
||||||
ConversionError.DecodeError(ValueCoder.DecodeError("cannot parse contractId \"???\""))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"keepCreateAndExerciseNodes" should {
|
|
||||||
"remove `Fetch`, `LookupByKey`, and `Rollback` nodes from the transaction tree" in {
|
|
||||||
val actual = TransactionConversions.keepCreateAndExerciseNodes(
|
|
||||||
RawTransaction(aRichNodeTreeTransaction.toByteString)
|
|
||||||
)
|
|
||||||
|
|
||||||
actual match {
|
|
||||||
case Right(rawTransaction) =>
|
|
||||||
val transaction = TransactionOuterClass.Transaction.parseFrom(rawTransaction.byteString)
|
|
||||||
transaction.getRootsList.asScala should contain theSameElementsInOrderAs Seq(
|
|
||||||
"Exercise-1",
|
|
||||||
"Create-1",
|
|
||||||
)
|
|
||||||
val nodes = transaction.getNodesList.asScala
|
|
||||||
nodes.map(_.getNodeId) should contain theSameElementsInOrderAs Seq(
|
|
||||||
"Create-1",
|
|
||||||
"Create-2",
|
|
||||||
"Create-3",
|
|
||||||
"Exercise-2",
|
|
||||||
"Exercise-1",
|
|
||||||
)
|
|
||||||
nodes(3).getExercise.getChildrenList.asScala should contain theSameElementsInOrderAs Seq(
|
|
||||||
"Create-3"
|
|
||||||
)
|
|
||||||
nodes(4).getExercise.getChildrenList.asScala should contain theSameElementsInOrderAs Seq(
|
|
||||||
"Create-2",
|
|
||||||
"Exercise-2",
|
|
||||||
)
|
|
||||||
case Left(_) => fail("should be Right")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
"fail on a non-parsable transaction" in {
|
|
||||||
val result = TransactionConversions.keepCreateAndExerciseNodes(
|
|
||||||
RawTransaction(ByteString.copyFromUtf8("wrong"))
|
|
||||||
)
|
|
||||||
|
|
||||||
result shouldBe Left(
|
|
||||||
ConversionError.ParseError("Protocol message tag had invalid wire type.")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
"fail on a transaction with invalid roots" in {
|
|
||||||
val result = TransactionConversions.keepCreateAndExerciseNodes(
|
|
||||||
RawTransaction(
|
|
||||||
aRichNodeTreeTransaction.toBuilder.addRoots("non-existent").build().toByteString
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
result shouldBe Left(
|
|
||||||
ConversionError.InternalError("Invalid transaction node id non-existent")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object TransactionConversionsSpec {
|
|
||||||
private val aChildNodeId = NodeId(2)
|
|
||||||
private val aRootNodeId = NodeId(1)
|
|
||||||
private val aRawChildNodeId = TransactionConversions.encodeTransactionNodeId(aChildNodeId)
|
|
||||||
private val aRawRootNodeId = TransactionConversions.encodeTransactionNodeId(aRootNodeId)
|
|
||||||
|
|
||||||
private val aChildObserver = Ref.Party.assertFromString("childObserver")
|
|
||||||
private val aChoiceId = Ref.Name.assertFromString("dummyChoice")
|
|
||||||
private val aDummyName = "dummyName"
|
|
||||||
private val aModuleName = "DummyModule"
|
|
||||||
private val aPackageId = "-dummyPkg-"
|
|
||||||
private val aProtoTemplateId = ValueOuterClass.Identifier
|
|
||||||
.newBuilder()
|
|
||||||
.setPackageId(aPackageId)
|
|
||||||
.addModuleName(aModuleName)
|
|
||||||
.addName(aDummyName)
|
|
||||||
private val aRootObserver = Ref.Party.assertFromString("rootObserver")
|
|
||||||
private val aTargetContractId = Value.ContractId.V1(crypto.Hash.hashPrivateKey("dummyCoid"))
|
|
||||||
private val aTemplateId = Ref.Identifier(
|
|
||||||
Ref.PackageId.assertFromString(aPackageId),
|
|
||||||
Ref.QualifiedName.assertFromString(s"$aModuleName:$aDummyName"),
|
|
||||||
)
|
|
||||||
private val aUnitArg =
|
|
||||||
ValueOuterClass.Value.newBuilder().setUnit(protobuf.Empty.newBuilder()).build().toByteString
|
|
||||||
private val aUnitValue = Value.ValueUnit
|
|
||||||
|
|
||||||
private val aChildNode = buildProtoNode(aRawChildNodeId.value) { builder =>
|
|
||||||
builder.setVersion(TransactionVersion.VDev.protoValue)
|
|
||||||
builder.setExercise(
|
|
||||||
exerciseBuilder()
|
|
||||||
.addObservers(aChildObserver)
|
|
||||||
.setArgUnversioned(aUnitArg)
|
|
||||||
.setChoice(aChoiceId)
|
|
||||||
.setConsuming(true)
|
|
||||||
.setContractIdStruct(
|
|
||||||
ValueOuterClass.ContractId.newBuilder().setContractId(aTargetContractId.coid)
|
|
||||||
)
|
|
||||||
.setTemplateId(aProtoTemplateId)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
private val aChildExerciseNode = exercise(Set(aChildObserver), ImmArray.empty)
|
|
||||||
private val aGlobalKey = GlobalKey.assertBuild(aTemplateId, aUnitValue)
|
|
||||||
private val aRawChildNode = RawTransaction.Node(aChildNode.toByteString)
|
|
||||||
private val aRootExerciseNode = exercise(Set(aRootObserver), ImmArray(aChildNodeId))
|
|
||||||
private val aRootNode = buildProtoNode(aRawRootNodeId.value) { builder =>
|
|
||||||
builder.setVersion(TransactionVersion.VDev.protoValue)
|
|
||||||
builder.setExercise(
|
|
||||||
exerciseBuilder()
|
|
||||||
.addObservers(aRootObserver)
|
|
||||||
.setArgUnversioned(aUnitArg)
|
|
||||||
.addChildren(aRawChildNodeId.value)
|
|
||||||
.setChoice(aChoiceId)
|
|
||||||
.setConsuming(true)
|
|
||||||
.setContractIdStruct(
|
|
||||||
ValueOuterClass.ContractId.newBuilder().setContractId(aTargetContractId.coid)
|
|
||||||
)
|
|
||||||
.setTemplateId(aProtoTemplateId)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
private val aRawRootNode = RawTransaction.Node(aRootNode.toByteString)
|
|
||||||
private val aRawTransaction = RawTransaction(
|
|
||||||
TransactionOuterClass.Transaction
|
|
||||||
.newBuilder()
|
|
||||||
.addRoots(aRawRootNodeId.value)
|
|
||||||
.addNodes(aRootNode)
|
|
||||||
.addNodes(aChildNode)
|
|
||||||
.setVersion(TransactionVersion.VDev.protoValue)
|
|
||||||
.build()
|
|
||||||
.toByteString
|
|
||||||
)
|
|
||||||
private val aRichNodeTreeTransaction = {
|
|
||||||
val roots = Seq("Exercise-1", "Fetch-1", "LookupByKey-1", "Create-1")
|
|
||||||
val nodes: Seq[TransactionOuterClass.Node] = Seq(
|
|
||||||
buildProtoNode("Fetch-1")(_.setFetch(fetchBuilder())),
|
|
||||||
buildProtoNode("LookupByKey-1")(_.setLookupByKey(lookupByKeyBuilder())),
|
|
||||||
buildProtoNode("Create-1")(_.setCreate(createBuilder())),
|
|
||||||
buildProtoNode("LookupByKey-2")(_.setLookupByKey(lookupByKeyBuilder())),
|
|
||||||
buildProtoNode("Fetch-2")(_.setFetch(fetchBuilder())),
|
|
||||||
buildProtoNode("Create-2")(_.setCreate(createBuilder())),
|
|
||||||
buildProtoNode("Fetch-3")(_.setFetch(fetchBuilder())),
|
|
||||||
buildProtoNode("Create-3")(_.setCreate(createBuilder())),
|
|
||||||
buildProtoNode("LookupByKey-3")(_.setLookupByKey(lookupByKeyBuilder())),
|
|
||||||
buildProtoNode("Exercise-2")(
|
|
||||||
_.setExercise(
|
|
||||||
exerciseBuilder().addAllChildren(
|
|
||||||
Seq("Fetch-3", "Create-3", "LookupByKey-3").asJava
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
buildProtoNode("Exercise-1")(
|
|
||||||
_.setExercise(
|
|
||||||
exerciseBuilder().addAllChildren(
|
|
||||||
Seq("LookupByKey-2", "Fetch-2", "Create-2", "Exercise-2").asJava
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
buildProtoNode("Rollback-1")(
|
|
||||||
_.setRollback(
|
|
||||||
rollbackBuilder().addAllChildren(Seq("RollbackChild-1", "RollbackChild-2").asJava)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
buildProtoNode("RollbackChild-1")(_.setCreate(createBuilder())),
|
|
||||||
buildProtoNode("RollbackChild-2")(_.setFetch(fetchBuilder())),
|
|
||||||
)
|
|
||||||
|
|
||||||
TransactionOuterClass.Transaction
|
|
||||||
.newBuilder()
|
|
||||||
.addAllRoots(roots.asJava)
|
|
||||||
.addAllNodes(nodes.asJava)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
private val aVersionedTransaction = VersionedTransaction(
|
|
||||||
TransactionVersion.VDev,
|
|
||||||
Map(aRootNodeId -> aRootExerciseNode, aChildNodeId -> aChildExerciseNode),
|
|
||||||
ImmArray(aRootNodeId),
|
|
||||||
)
|
|
||||||
|
|
||||||
private def create(builder: TestNodeBuilder, id: Value.ContractId, hasKey: Boolean = false) =
|
|
||||||
builder.create(
|
|
||||||
id = id,
|
|
||||||
templateId = aTemplateId,
|
|
||||||
argument = Value.ValueRecord(None, ImmArray.Empty),
|
|
||||||
signatories = Set.empty,
|
|
||||||
key = if (hasKey) SignatoryMaintainerKey(aUnitValue) else NoKey,
|
|
||||||
)
|
|
||||||
|
|
||||||
private def exercise(
|
|
||||||
builder: TestNodeBuilder,
|
|
||||||
id: Value.ContractId,
|
|
||||||
hasKey: Boolean = false,
|
|
||||||
consuming: Boolean = true,
|
|
||||||
) =
|
|
||||||
builder.exercise(
|
|
||||||
contract = create(builder, id, hasKey),
|
|
||||||
choice = aChoiceId,
|
|
||||||
argument = Value.ValueRecord(None, ImmArray.Empty),
|
|
||||||
actingParties = Set.empty,
|
|
||||||
consuming = consuming,
|
|
||||||
result = Some(aUnitValue),
|
|
||||||
byKey = false,
|
|
||||||
)
|
|
||||||
|
|
||||||
private def exercise(choiceObservers: Set[Ref.Party], children: ImmArray[NodeId]) =
|
|
||||||
Node.Exercise(
|
|
||||||
targetCoid = aTargetContractId,
|
|
||||||
templateId = aTemplateId,
|
|
||||||
interfaceId = None,
|
|
||||||
choiceId = aChoiceId,
|
|
||||||
consuming = true,
|
|
||||||
actingParties = Set.empty,
|
|
||||||
chosenValue = aUnitValue,
|
|
||||||
stakeholders = Set.empty,
|
|
||||||
signatories = Set.empty,
|
|
||||||
choiceObservers = choiceObservers,
|
|
||||||
choiceAuthorizers = None,
|
|
||||||
children = children,
|
|
||||||
exerciseResult = None,
|
|
||||||
keyOpt = None,
|
|
||||||
byKey = false,
|
|
||||||
version = TransactionVersion.VDev,
|
|
||||||
)
|
|
||||||
|
|
||||||
private def fetch(builder: TestNodeBuilder, id: Value.ContractId, byKey: Boolean) =
|
|
||||||
builder.fetch(contract = create(builder, id, hasKey = true), byKey = byKey)
|
|
||||||
|
|
||||||
private def lookup(builder: TestNodeBuilder, id: Value.ContractId, found: Boolean) =
|
|
||||||
builder.lookupByKey(contract = create(builder, id, hasKey = true), found = found)
|
|
||||||
|
|
||||||
private def buildProtoNode(nodeId: String)(
|
|
||||||
nodeImpl: TransactionOuterClass.Node.Builder => TransactionOuterClass.Node.Builder
|
|
||||||
) =
|
|
||||||
nodeImpl(TransactionOuterClass.Node.newBuilder().setNodeId(nodeId)).build()
|
|
||||||
|
|
||||||
private def fetchBuilder() = TransactionOuterClass.NodeFetch.newBuilder()
|
|
||||||
|
|
||||||
private def exerciseBuilder() = TransactionOuterClass.NodeExercise.newBuilder()
|
|
||||||
|
|
||||||
private def rollbackBuilder() = TransactionOuterClass.NodeRollback.newBuilder()
|
|
||||||
|
|
||||||
private def createBuilder() = TransactionOuterClass.NodeCreate.newBuilder()
|
|
||||||
|
|
||||||
private def lookupByKeyBuilder() = TransactionOuterClass.NodeLookupByKey.newBuilder()
|
|
||||||
}
|
|
@ -1,106 +0,0 @@
|
|||||||
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package com.daml.lf.kv.transactions
|
|
||||||
|
|
||||||
import com.daml.lf.data.ImmArray
|
|
||||||
import com.daml.lf.transaction._
|
|
||||||
import com.daml.lf.value.test.ValueGenerators._
|
|
||||||
import org.scalatest.matchers.should.Matchers
|
|
||||||
import org.scalatest.wordspec.AnyWordSpec
|
|
||||||
import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks
|
|
||||||
|
|
||||||
class TransactionNormalizerSpec
|
|
||||||
extends AnyWordSpec
|
|
||||||
with Matchers
|
|
||||||
with ScalaCheckDrivenPropertyChecks {
|
|
||||||
|
|
||||||
"normalizerTransaction" should {
|
|
||||||
|
|
||||||
"only keeps Create and Exercise nodes" in {
|
|
||||||
forAll(noDanglingRefGenVersionedTransaction) { tx =>
|
|
||||||
val normalized = TransactionNormalizer.normalize(CommittedTransaction(tx))
|
|
||||||
|
|
||||||
val nidsBefore: Set[NodeId] = tx.nodes.keySet
|
|
||||||
val nidsAfter: Set[NodeId] = normalized.nodes.keySet
|
|
||||||
|
|
||||||
// Every kept nid existed before
|
|
||||||
assert(nidsAfter.forall(nid => nidsBefore.contains(nid)))
|
|
||||||
|
|
||||||
// The normalized nodes mapping does not contain anything unreachanble
|
|
||||||
nidsAfter shouldBe normalized.reachableNodeIds
|
|
||||||
|
|
||||||
// Only create/exercise nodes are kept
|
|
||||||
assert(
|
|
||||||
nidsAfter
|
|
||||||
.map(normalized.nodes(_))
|
|
||||||
.forall(isCreateOrExercise)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Everything kept is unchanged (except children may be dropped)
|
|
||||||
def unchangedByNormalization(nid: NodeId): Boolean = {
|
|
||||||
val before = tx.nodes(nid)
|
|
||||||
val after = normalized.nodes(nid)
|
|
||||||
// the node is unchanged when disregarding the children
|
|
||||||
nodeSansChildren(after) == nodeSansChildren(before)
|
|
||||||
// children can be lost, but nothing else
|
|
||||||
val beforeChildren = nodeChildren(before)
|
|
||||||
val afterChildren = nodeChildren(after)
|
|
||||||
afterChildren.forall(beforeChildren.contains(_))
|
|
||||||
}
|
|
||||||
assert(
|
|
||||||
nidsAfter.forall(unchangedByNormalization)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Does a Nid reference a create/exercise node (in the before tx) ?
|
|
||||||
def isNidCE(nid: NodeId): Boolean = {
|
|
||||||
isCreateOrExercise(tx.nodes(nid))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is a Nid kept in the after transaction ?
|
|
||||||
def isKept(nid: NodeId): Boolean = {
|
|
||||||
nidsAfter.contains(nid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create/exercise root nodes are kept
|
|
||||||
assert(
|
|
||||||
tx.roots.toList
|
|
||||||
.filter(isNidCE)
|
|
||||||
.forall(isKept)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create/exercise children of kept nodes should also be kept
|
|
||||||
assert(
|
|
||||||
nidsAfter
|
|
||||||
.flatMap(nid => nodeChildren(tx.nodes(nid)))
|
|
||||||
.filter(isNidCE)
|
|
||||||
.forall(isKept)
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def isCreateOrExercise(node: Node): Boolean = {
|
|
||||||
node match {
|
|
||||||
case _: Node.Exercise => true
|
|
||||||
case _: Node.Create => true
|
|
||||||
case _ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def nodeSansChildren(node: Node): Node = {
|
|
||||||
node match {
|
|
||||||
case exe: Node.Exercise => exe.copy(children = ImmArray.Empty)
|
|
||||||
case _ => node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def nodeChildren(node: Node): List[NodeId] = {
|
|
||||||
node match {
|
|
||||||
case exe: Node.Exercise => exe.children.toList
|
|
||||||
case _ => List()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,289 +0,0 @@
|
|||||||
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package com.daml.lf.kv.transactions
|
|
||||||
|
|
||||||
import com.daml.lf.kv.ConversionError
|
|
||||||
import com.daml.lf.transaction.TransactionOuterClass.{
|
|
||||||
KeyWithMaintainers,
|
|
||||||
Node,
|
|
||||||
NodeLookupByKey,
|
|
||||||
NodeRollback,
|
|
||||||
Transaction,
|
|
||||||
}
|
|
||||||
import com.daml.lf.transaction.TransactionVersion
|
|
||||||
import com.daml.lf.value.{ValueCoder, ValueOuterClass}
|
|
||||||
import com.google.protobuf.ByteString
|
|
||||||
import org.scalatest.funsuite.AnyFunSuite
|
|
||||||
import org.scalatest.matchers.should.Matchers
|
|
||||||
|
|
||||||
import scala.jdk.CollectionConverters._
|
|
||||||
|
|
||||||
class TransactionTraversalSpec extends AnyFunSuite with Matchers {
|
|
||||||
|
|
||||||
test("traverseTransactionWithWitnesses - consuming nested exercises") {
|
|
||||||
val testTransaction = RawTransactionForTest()
|
|
||||||
TransactionTraversal.traverseTransactionWithWitnesses(testTransaction.rawTx) {
|
|
||||||
case (RawTransaction.NodeId(testTransaction.`createNid`), _, witnesses) =>
|
|
||||||
witnesses should contain.only("Alice", "Bob", "Charlie")
|
|
||||||
()
|
|
||||||
case (RawTransaction.NodeId(testTransaction.`exeNid`), _, witnesses) =>
|
|
||||||
witnesses should contain.only("Alice", "Charlie")
|
|
||||||
()
|
|
||||||
case (RawTransaction.NodeId(testTransaction.`nonConsumingExeNid`), _, witnesses) =>
|
|
||||||
// Non-consuming exercises are only witnessed by signatories.
|
|
||||||
witnesses should contain only "Alice"
|
|
||||||
()
|
|
||||||
case (RawTransaction.NodeId(testTransaction.`rootNid`), _, witnesses) =>
|
|
||||||
witnesses should contain only "Alice"
|
|
||||||
()
|
|
||||||
case (RawTransaction.NodeId(testTransaction.`fetchNid`), _, witnesses) =>
|
|
||||||
// This is of course ill-authorized, but we check that parent witnesses are included.
|
|
||||||
witnesses should contain.only("Alice", "Bob")
|
|
||||||
()
|
|
||||||
case what =>
|
|
||||||
fail(s"Traversed to unknown node: $what")
|
|
||||||
} shouldBe Right(())
|
|
||||||
}
|
|
||||||
|
|
||||||
test("extractPerPackageWitnesses - extract package witness mapping as expected") {
|
|
||||||
val testTransaction = RawTransactionForTest()
|
|
||||||
val result = TransactionTraversal.extractPerPackageWitnesses(testTransaction.rawTx)
|
|
||||||
result shouldBe
|
|
||||||
Right(
|
|
||||||
Map(
|
|
||||||
"template_exercise" -> Set("Alice", "Charlie"),
|
|
||||||
"interface_exercise" -> Set("Alice", "Charlie"),
|
|
||||||
"template_create" -> Set("Alice", "Charlie", "Bob"),
|
|
||||||
"template_fetch" -> Set("Alice", "Bob"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
test(
|
|
||||||
"extractPerPackageWitnesses - extract package witness mapping as expected including rollback node"
|
|
||||||
) {
|
|
||||||
val testTransaction = RawTransactionForTest()
|
|
||||||
def lookupByKeyNode(index: Int) = withNodeBuilder { builder =>
|
|
||||||
builder.setLookupByKey(
|
|
||||||
NodeLookupByKey.newBuilder
|
|
||||||
.setTemplateId(identifierForTemplateId(s"template_lookup_by_key$index"))
|
|
||||||
.setKeyWithMaintainers(
|
|
||||||
KeyWithMaintainers.newBuilder().addMaintainers(s"LookupByKey$index Party")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val transactionBuilder = testTransaction.builder
|
|
||||||
transactionBuilder.addRoot(
|
|
||||||
exerciseNode(
|
|
||||||
signatories = Seq("Exercise with Rollback Node Party"),
|
|
||||||
stakeholders = Seq.empty,
|
|
||||||
consuming = false,
|
|
||||||
children = transactionBuilder.addNode(
|
|
||||||
buildRollbackNodeWithChild(
|
|
||||||
transactionBuilder.addNode(lookupByKeyNode(0)),
|
|
||||||
transactionBuilder.addNode(
|
|
||||||
buildRollbackNodeWithChild(transactionBuilder.addNode(lookupByKeyNode(1)))
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
val result = TransactionTraversal.extractPerPackageWitnesses(testTransaction.rawTx)
|
|
||||||
result shouldBe
|
|
||||||
Right(
|
|
||||||
Map(
|
|
||||||
"template_exercise" -> Set("Alice", "Charlie", "Exercise with Rollback Node Party"),
|
|
||||||
"interface_exercise" -> Set("Alice", "Charlie", "Exercise with Rollback Node Party"),
|
|
||||||
"template_create" -> Set("Alice", "Charlie", "Bob"),
|
|
||||||
"template_fetch" -> Set("Alice", "Bob"),
|
|
||||||
"template_lookup_by_key0" -> Set(
|
|
||||||
"LookupByKey0 Party",
|
|
||||||
"Exercise with Rollback Node Party",
|
|
||||||
),
|
|
||||||
"template_lookup_by_key1" -> Set(
|
|
||||||
"LookupByKey1 Party",
|
|
||||||
"Exercise with Rollback Node Party",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def buildRollbackNodeWithChild(children: String*) = {
|
|
||||||
withNodeBuilder { builder =>
|
|
||||||
builder.setRollback(
|
|
||||||
NodeRollback.newBuilder.addAllChildren(children.asJava)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
test("traversal - transaction parsing error") {
|
|
||||||
val rawTx = RawTransaction(ByteString.copyFromUtf8("wrong"))
|
|
||||||
TransactionTraversal.traverseTransactionWithWitnesses(rawTx)((_, _, _) => ()) shouldBe Left(
|
|
||||||
ConversionError.ParseError("Protocol message tag had invalid wire type.")
|
|
||||||
)
|
|
||||||
TransactionTraversal.extractPerPackageWitnesses(rawTx) shouldBe Left(
|
|
||||||
ConversionError.ParseError("Protocol message tag had invalid wire type.")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
test("traversal - transaction version parsing error") {
|
|
||||||
val rawTx = RawTransaction(Transaction.newBuilder().setVersion("wrong").build.toByteString)
|
|
||||||
val actual = TransactionTraversal.traverseTransactionWithWitnesses(rawTx)((_, _, _) => ())
|
|
||||||
actual shouldBe Left(ConversionError.ParseError("Unsupported transaction version 'wrong'"))
|
|
||||||
}
|
|
||||||
|
|
||||||
test("traversal - decode error on rollback nodes") {
|
|
||||||
val rootNodeId = "1"
|
|
||||||
val rawTx = RawTransaction(
|
|
||||||
Transaction
|
|
||||||
.newBuilder()
|
|
||||||
.setVersion(TransactionVersion.VDev.protoValue)
|
|
||||||
.addNodes(
|
|
||||||
Node.newBuilder().setNodeId(rootNodeId).setRollback(NodeRollback.getDefaultInstance)
|
|
||||||
)
|
|
||||||
.addRoots(rootNodeId)
|
|
||||||
.build
|
|
||||||
.toByteString
|
|
||||||
)
|
|
||||||
TransactionTraversal.traverseTransactionWithWitnesses(rawTx)((_, _, _) => ()) shouldBe Left(
|
|
||||||
ConversionError.DecodeError(
|
|
||||||
ValueCoder.DecodeError(
|
|
||||||
"protoActionNodeInfo only supports action nodes but was applied to a rollback node"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------------
|
|
||||||
// Helpers for constructing transactions.
|
|
||||||
case class RawTransactionForTest() {
|
|
||||||
val builder: TransactionBuilder = TransactionBuilder()
|
|
||||||
|
|
||||||
// Creation of a contract with Alice as signatory, and Bob is controller of one of the choices.
|
|
||||||
val createNid: String = builder.addNode(
|
|
||||||
createNode(List("Alice"), List("Alice", "Bob"))
|
|
||||||
)
|
|
||||||
|
|
||||||
// Exercise of a contract where Alice as signatory, Charlie has a choice or is an observer.
|
|
||||||
val exeNid: String = builder.addNode(
|
|
||||||
exerciseNode(
|
|
||||||
signatories = List("Alice"),
|
|
||||||
stakeholders = List("Alice", "Charlie"),
|
|
||||||
consuming = true,
|
|
||||||
createNid,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Non-consuming exercise of a contract where Alice as signatory, Charlie has a choice or is an observer.
|
|
||||||
val nonConsumingExeNid: String = builder.addNode(
|
|
||||||
exerciseNode(
|
|
||||||
signatories = List("Alice"),
|
|
||||||
stakeholders = List("Alice", "Charlie"),
|
|
||||||
consuming = false,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
// A fetch of some contract created by Bob.
|
|
||||||
val fetchNid: String = builder.addNode(
|
|
||||||
fetchNode(
|
|
||||||
signatories = List("Bob"),
|
|
||||||
stakeholders = List("Bob"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Root node exercising a contract only known to Alice.
|
|
||||||
val rootNid: String = builder.addRoot(
|
|
||||||
exerciseNode(
|
|
||||||
signatories = List("Alice"),
|
|
||||||
stakeholders = List("Alice"),
|
|
||||||
consuming = true,
|
|
||||||
fetchNid,
|
|
||||||
nonConsumingExeNid,
|
|
||||||
exeNid,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def rawTx: RawTransaction = RawTransaction(builder.build.toByteString)
|
|
||||||
}
|
|
||||||
|
|
||||||
case class TransactionBuilder() {
|
|
||||||
private var roots = List.empty[String]
|
|
||||||
private var nodes = Map.empty[String, Node]
|
|
||||||
private var nextNodeId = -1
|
|
||||||
|
|
||||||
def addNode(node: Node.Builder): String = {
|
|
||||||
nextNodeId += 1
|
|
||||||
val nodeId = nextNodeId.toString
|
|
||||||
nodes += nodeId -> node.setNodeId(nodeId).build
|
|
||||||
nodeId
|
|
||||||
}
|
|
||||||
|
|
||||||
def addRoot(node: Node.Builder): String = {
|
|
||||||
// Check that children actually exist.
|
|
||||||
assert(
|
|
||||||
!node.hasExercise ||
|
|
||||||
node.getExercise.getChildrenList.asScala.map(nodes.contains).forall(identity)
|
|
||||||
)
|
|
||||||
val nodeId = addNode(node)
|
|
||||||
roots ::= nodeId
|
|
||||||
nodeId
|
|
||||||
}
|
|
||||||
|
|
||||||
def build: Transaction =
|
|
||||||
Transaction.newBuilder
|
|
||||||
.setVersion(TransactionVersion.minVersion.protoValue)
|
|
||||||
.addAllNodes(nodes.values.asJava)
|
|
||||||
.addAllRoots(roots.reverse.asJava)
|
|
||||||
.build
|
|
||||||
}
|
|
||||||
|
|
||||||
private def withNodeBuilder[A](build: Node.Builder => A): Node.Builder = {
|
|
||||||
val b = Node.newBuilder
|
|
||||||
build(b)
|
|
||||||
b
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct a create node. Signatories are the parties whose signature is required to create the
|
|
||||||
* contract. Stakeholders of a contract are the signatories and observers.
|
|
||||||
*/
|
|
||||||
private def createNode(signatories: Iterable[String], stakeholders: Iterable[String]) =
|
|
||||||
withNodeBuilder {
|
|
||||||
_.getCreateBuilder
|
|
||||||
.setTemplateId(identifierForTemplateId("template_create"))
|
|
||||||
.addAllSignatories(signatories.asJava)
|
|
||||||
.addAllStakeholders(stakeholders.asJava)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct a fetch node. Signatories are the signatories of the contract we're fetching.
|
|
||||||
* Stakeholders of a contract are the signatories and observers.
|
|
||||||
*/
|
|
||||||
private def fetchNode(signatories: Iterable[String], stakeholders: Iterable[String]) =
|
|
||||||
withNodeBuilder {
|
|
||||||
_.getFetchBuilder
|
|
||||||
.setTemplateId(identifierForTemplateId("template_fetch"))
|
|
||||||
.addAllSignatories(signatories.asJava)
|
|
||||||
.addAllStakeholders(stakeholders.asJava)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Construct an exercise node. Signatories are the signatories of the contract we're exercising
|
|
||||||
* on.
|
|
||||||
*/
|
|
||||||
private def exerciseNode(
|
|
||||||
signatories: Seq[String],
|
|
||||||
stakeholders: Seq[String],
|
|
||||||
consuming: Boolean,
|
|
||||||
children: String*
|
|
||||||
) =
|
|
||||||
withNodeBuilder {
|
|
||||||
_.getExerciseBuilder
|
|
||||||
.setConsuming(consuming)
|
|
||||||
.setTemplateId(identifierForTemplateId("template_exercise"))
|
|
||||||
.setInterfaceId(identifierForTemplateId("interface_exercise"))
|
|
||||||
.addAllSignatories(signatories.asJava)
|
|
||||||
.addAllStakeholders(stakeholders.asJava)
|
|
||||||
/* NOTE(JM): Actors are no longer included in exercises by the compiler, hence we don't set them */
|
|
||||||
.addAllChildren(children.asJava)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def identifierForTemplateId = ValueOuterClass.Identifier.newBuilder().setPackageId _
|
|
||||||
}
|
|
@ -25,7 +25,7 @@ sealed abstract class Node extends Product with Serializable with CidContainer[N
|
|||||||
object Node {
|
object Node {
|
||||||
|
|
||||||
/** action nodes parametrized over identifier type */
|
/** action nodes parametrized over identifier type */
|
||||||
sealed abstract class Action extends Node with ActionNodeInfo with CidContainer[Action] {
|
sealed abstract class Action extends Node with CidContainer[Action] {
|
||||||
|
|
||||||
def version: TransactionVersion
|
def version: TransactionVersion
|
||||||
|
|
||||||
@ -46,6 +46,12 @@ object Node {
|
|||||||
|
|
||||||
final override protected def self: this.type = this
|
final override protected def self: this.type = this
|
||||||
|
|
||||||
|
/** Compute the informees of a node based on the ledger model definition.
|
||||||
|
*
|
||||||
|
* Refer to https://docs.daml.com/concepts/ledger-model/ledger-privacy.html#projections
|
||||||
|
*/
|
||||||
|
def informeesOfNode: Set[Party]
|
||||||
|
|
||||||
/** Required authorizers (see ledger model); UNSAFE TO USE on fetch nodes of transaction with versions < 5
|
/** Required authorizers (see ledger model); UNSAFE TO USE on fetch nodes of transaction with versions < 5
|
||||||
*
|
*
|
||||||
* The ledger model defines the fetch node actors as the nodes' required authorizers.
|
* The ledger model defines the fetch node actors as the nodes' required authorizers.
|
||||||
@ -78,8 +84,7 @@ object Node {
|
|||||||
keyOpt: Option[GlobalKeyWithMaintainers],
|
keyOpt: Option[GlobalKeyWithMaintainers],
|
||||||
// For the sake of consistency between types with a version field, keep this field the last.
|
// For the sake of consistency between types with a version field, keep this field the last.
|
||||||
override val version: TransactionVersion,
|
override val version: TransactionVersion,
|
||||||
) extends LeafOnlyAction
|
) extends LeafOnlyAction {
|
||||||
with ActionNodeInfo.Create {
|
|
||||||
|
|
||||||
@deprecated("use keyOpt", since = "2.6.0")
|
@deprecated("use keyOpt", since = "2.6.0")
|
||||||
def key: Option[GlobalKeyWithMaintainers] = keyOpt
|
def key: Option[GlobalKeyWithMaintainers] = keyOpt
|
||||||
@ -102,6 +107,9 @@ object Node {
|
|||||||
def versionedCoinst: Value.VersionedContractInstance = versioned(coinst)
|
def versionedCoinst: Value.VersionedContractInstance = versioned(coinst)
|
||||||
|
|
||||||
def versionedKey: Option[Versioned[GlobalKeyWithMaintainers]] = keyOpt.map(versioned(_))
|
def versionedKey: Option[Versioned[GlobalKeyWithMaintainers]] = keyOpt.map(versioned(_))
|
||||||
|
|
||||||
|
override def informeesOfNode: Set[Party] = stakeholders
|
||||||
|
override def requiredAuthorizers: Set[Party] = signatories
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Denotes that the contract identifier `coid` needs to be active for the transaction to be valid. */
|
/** Denotes that the contract identifier `coid` needs to be active for the transaction to be valid. */
|
||||||
@ -115,9 +123,7 @@ object Node {
|
|||||||
override val byKey: Boolean,
|
override val byKey: Boolean,
|
||||||
// For the sake of consistency between types with a version field, keep this field the last.
|
// For the sake of consistency between types with a version field, keep this field the last.
|
||||||
override val version: TransactionVersion,
|
override val version: TransactionVersion,
|
||||||
) extends LeafOnlyAction
|
) extends LeafOnlyAction {
|
||||||
with ActionNodeInfo.Fetch {
|
|
||||||
|
|
||||||
@deprecated("use keyOpt", since = "2.6.0")
|
@deprecated("use keyOpt", since = "2.6.0")
|
||||||
def key: Option[GlobalKeyWithMaintainers] = keyOpt
|
def key: Option[GlobalKeyWithMaintainers] = keyOpt
|
||||||
|
|
||||||
@ -128,6 +134,9 @@ object Node {
|
|||||||
copy(coid = f(coid))
|
copy(coid = f(coid))
|
||||||
|
|
||||||
override def packageIds: Iterable[PackageId] = Iterable(templateId.packageId)
|
override def packageIds: Iterable[PackageId] = Iterable(templateId.packageId)
|
||||||
|
|
||||||
|
override def informeesOfNode: Set[Party] = signatories | actingParties
|
||||||
|
override def requiredAuthorizers: Set[Party] = actingParties
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Denotes a transaction node for an exercise.
|
/** Denotes a transaction node for an exercise.
|
||||||
@ -146,15 +155,14 @@ object Node {
|
|||||||
stakeholders: Set[Party],
|
stakeholders: Set[Party],
|
||||||
signatories: Set[Party],
|
signatories: Set[Party],
|
||||||
choiceObservers: Set[Party],
|
choiceObservers: Set[Party],
|
||||||
override val choiceAuthorizers: Option[Set[Party]],
|
choiceAuthorizers: Option[Set[Party]],
|
||||||
children: ImmArray[NodeId],
|
children: ImmArray[NodeId],
|
||||||
exerciseResult: Option[Value],
|
exerciseResult: Option[Value],
|
||||||
keyOpt: Option[GlobalKeyWithMaintainers],
|
keyOpt: Option[GlobalKeyWithMaintainers],
|
||||||
override val byKey: Boolean,
|
override val byKey: Boolean,
|
||||||
// For the sake of consistency between types with a version field, keep this field the last.
|
// For the sake of consistency between types with a version field, keep this field the last.
|
||||||
override val version: TransactionVersion,
|
override val version: TransactionVersion,
|
||||||
) extends Action
|
) extends Action {
|
||||||
with ActionNodeInfo.Exercise {
|
|
||||||
|
|
||||||
def qualifiedChoiceName = QualifiedChoiceName(interfaceId, choiceId)
|
def qualifiedChoiceName = QualifiedChoiceName(interfaceId, choiceId)
|
||||||
|
|
||||||
@ -184,6 +192,12 @@ object Node {
|
|||||||
|
|
||||||
def versionedKey: Option[Versioned[GlobalKeyWithMaintainers]] = keyOpt.map(versioned)
|
def versionedKey: Option[Versioned[GlobalKeyWithMaintainers]] = keyOpt.map(versioned)
|
||||||
|
|
||||||
|
override def informeesOfNode: Set[Party] =
|
||||||
|
if (consuming)
|
||||||
|
stakeholders | actingParties | choiceObservers | choiceAuthorizers.getOrElse(Set.empty)
|
||||||
|
else
|
||||||
|
signatories | actingParties | choiceObservers | choiceAuthorizers.getOrElse(Set.empty)
|
||||||
|
override def requiredAuthorizers: Set[Party] = actingParties
|
||||||
}
|
}
|
||||||
|
|
||||||
final case class LookupByKey(
|
final case class LookupByKey(
|
||||||
@ -192,8 +206,7 @@ object Node {
|
|||||||
result: Option[ContractId],
|
result: Option[ContractId],
|
||||||
// For the sake of consistency between types with a version field, keep this field the last.
|
// For the sake of consistency between types with a version field, keep this field the last.
|
||||||
override val version: TransactionVersion,
|
override val version: TransactionVersion,
|
||||||
) extends LeafOnlyAction
|
) extends LeafOnlyAction {
|
||||||
with ActionNodeInfo.LookupByKey {
|
|
||||||
|
|
||||||
override def keyOpt: Some[GlobalKeyWithMaintainers] = Some(key)
|
override def keyOpt: Some[GlobalKeyWithMaintainers] = Some(key)
|
||||||
|
|
||||||
@ -202,14 +215,22 @@ object Node {
|
|||||||
override def mapCid(f: ContractId => ContractId): Node.LookupByKey =
|
override def mapCid(f: ContractId => ContractId): Node.LookupByKey =
|
||||||
copy(result = result.map(f))
|
copy(result = result.map(f))
|
||||||
|
|
||||||
override def keyMaintainers: Set[Party] = key.maintainers
|
def keyMaintainers: Set[Party] = key.maintainers
|
||||||
override def hasResult: Boolean = result.isDefined
|
|
||||||
override def byKey: Boolean = true
|
override def byKey: Boolean = true
|
||||||
|
|
||||||
override private[lf] def updateVersion(version: TransactionVersion): Node.LookupByKey =
|
override private[lf] def updateVersion(version: TransactionVersion): Node.LookupByKey =
|
||||||
copy(version = version)
|
copy(version = version)
|
||||||
|
|
||||||
override def packageIds: Iterable[PackageId] = Iterable(templateId.packageId)
|
override def packageIds: Iterable[PackageId] = Iterable(templateId.packageId)
|
||||||
|
|
||||||
|
final def informeesOfNode: Set[Party] =
|
||||||
|
// TODO(JM): In the successful case the informees should be the
|
||||||
|
// signatories of the fetch contract. The signatories should be
|
||||||
|
// added to the LookupByKey node, or a successful lookup should
|
||||||
|
// become a Fetch.
|
||||||
|
keyMaintainers
|
||||||
|
def requiredAuthorizers: Set[Party] = keyMaintainers
|
||||||
}
|
}
|
||||||
|
|
||||||
@deprecated("use GlobalKey", since = "2.6.0")
|
@deprecated("use GlobalKey", since = "2.6.0")
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
package com.daml.lf.transaction
|
|
||||||
|
|
||||||
import com.daml.lf.data.Ref.Party
|
|
||||||
|
|
||||||
/** Trait for extracting information from an abstract action node.
|
|
||||||
* Used for sharing the implementation of common computations
|
|
||||||
* over nodes and transactions.
|
|
||||||
*
|
|
||||||
* External codebases use these utilities on transaction and
|
|
||||||
* node implementations that are not the one defined by [[ActionNode]]
|
|
||||||
* and hence the need for the indirection.
|
|
||||||
*/
|
|
||||||
trait ActionNodeInfo {
|
|
||||||
|
|
||||||
/** Compute the informees of a node based on the ledger model definition.
|
|
||||||
*
|
|
||||||
* Refer to https://docs.daml.com/concepts/ledger-model/ledger-privacy.html#projections
|
|
||||||
*/
|
|
||||||
def informeesOfNode: Set[Party]
|
|
||||||
|
|
||||||
/** Required authorizers (see ledger model); UNSAFE TO USE on fetch nodes of transaction with versions < 5
|
|
||||||
*
|
|
||||||
* The ledger model defines the fetch node actingParties as the nodes' required authorizers.
|
|
||||||
* However, the our transaction data structure did not include the actingParties in versions < 5.
|
|
||||||
* The usage of this method must thus be restricted to:
|
|
||||||
* 1. settings where no fetch nodes appear (for example, the `validate` method of DAMLe, which uses it on root
|
|
||||||
* nodes, which are guaranteed never to contain a fetch node)
|
|
||||||
* 2. Daml ledger implementations that do not store or process any transactions with version < 5
|
|
||||||
*/
|
|
||||||
def requiredAuthorizers: Set[Party]
|
|
||||||
}
|
|
||||||
|
|
||||||
object ActionNodeInfo {
|
|
||||||
|
|
||||||
trait Create extends ActionNodeInfo {
|
|
||||||
def signatories: Set[Party]
|
|
||||||
def stakeholders: Set[Party]
|
|
||||||
|
|
||||||
final def requiredAuthorizers: Set[Party] = signatories
|
|
||||||
final def informeesOfNode: Set[Party] = stakeholders
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Fetch extends ActionNodeInfo {
|
|
||||||
def signatories: Set[Party]
|
|
||||||
def stakeholders: Set[Party]
|
|
||||||
def actingParties: Set[Party]
|
|
||||||
|
|
||||||
final def requiredAuthorizers: Set[Party] =
|
|
||||||
actingParties
|
|
||||||
final def informeesOfNode: Set[Party] =
|
|
||||||
signatories | actingParties
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
trait Exercise extends ActionNodeInfo {
|
|
||||||
|
|
||||||
def consuming: Boolean
|
|
||||||
def signatories: Set[Party]
|
|
||||||
def stakeholders: Set[Party]
|
|
||||||
def actingParties: Set[Party]
|
|
||||||
def choiceObservers: Set[Party]
|
|
||||||
def choiceAuthorizers: Option[Set[Party]]
|
|
||||||
|
|
||||||
final def requiredAuthorizers: Set[Party] = actingParties
|
|
||||||
|
|
||||||
final def informeesOfNode: Set[Party] =
|
|
||||||
if (consuming)
|
|
||||||
stakeholders | actingParties | choiceObservers | choiceAuthorizers.getOrElse(Set.empty)
|
|
||||||
else
|
|
||||||
signatories | actingParties | choiceObservers | choiceAuthorizers.getOrElse(Set.empty)
|
|
||||||
}
|
|
||||||
|
|
||||||
trait LookupByKey extends ActionNodeInfo {
|
|
||||||
def keyMaintainers: Set[Party]
|
|
||||||
def hasResult: Boolean
|
|
||||||
|
|
||||||
final def requiredAuthorizers: Set[Party] = keyMaintainers
|
|
||||||
final def informeesOfNode: Set[Party] =
|
|
||||||
// TODO(JM): In the successful case the informees should be the
|
|
||||||
// signatories of the fetch contract. The signatories should be
|
|
||||||
// added to the LookupByKey node, or a successful lookup should
|
|
||||||
// become a Fetch.
|
|
||||||
keyMaintainers
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -255,13 +255,6 @@ sealed abstract class HasTxNodes {
|
|||||||
|
|
||||||
def roots: ImmArray[NodeId]
|
def roots: ImmArray[NodeId]
|
||||||
|
|
||||||
/** The union of the informees of all the action nodes. */
|
|
||||||
lazy val informees: Set[Ref.Party] =
|
|
||||||
nodes.values.foldLeft(Set.empty[Ref.Party]) {
|
|
||||||
case (acc, node: Node.Action) => acc | node.informeesOfNode
|
|
||||||
case (acc, _: Node.Rollback) => acc
|
|
||||||
}
|
|
||||||
|
|
||||||
// We assume that rollback node cannot be a root of a transaction.
|
// We assume that rollback node cannot be a root of a transaction.
|
||||||
// This is correct for an unprojected transaction. For a project transaction,
|
// This is correct for an unprojected transaction. For a project transaction,
|
||||||
// Canton handles rollback nodes itself so this is assumption still holds
|
// Canton handles rollback nodes itself so this is assumption still holds
|
||||||
|
@ -837,94 +837,6 @@ object TransactionCoder {
|
|||||||
else
|
else
|
||||||
decodeVersion(node.getVersion)
|
decodeVersion(node.getVersion)
|
||||||
|
|
||||||
/** Action node information for a serialized transaction node. Used to compute
|
|
||||||
* informees when deserialization is too costly.
|
|
||||||
* This method is not supported for transaction version <5 (as NodeInfo does not support it).
|
|
||||||
* We're not using e.g. "implicit class" in order to keep the decoding errors explicit.
|
|
||||||
* NOTE(JM): Currently used only externally, but kept here to keep in sync
|
|
||||||
* with the implementation.
|
|
||||||
* Note that this can only be applied to action nodes and will return Left on
|
|
||||||
* rollback nodes.
|
|
||||||
*/
|
|
||||||
def protoActionNodeInfo(
|
|
||||||
txVersion: TransactionVersion,
|
|
||||||
protoNode: TransactionOuterClass.Node,
|
|
||||||
): Either[DecodeError, ActionNodeInfo] =
|
|
||||||
protoNode.getNodeTypeCase match {
|
|
||||||
case NodeTypeCase.ROLLBACK =>
|
|
||||||
Left(
|
|
||||||
DecodeError(
|
|
||||||
"protoActionNodeInfo only supports action nodes but was applied to a rollback node"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
case NodeTypeCase.CREATE =>
|
|
||||||
val protoCreate = protoNode.getCreate
|
|
||||||
for {
|
|
||||||
signatories_ <- toPartySet(protoCreate.getSignatoriesList)
|
|
||||||
stakeholders_ <- toPartySet(protoCreate.getStakeholdersList)
|
|
||||||
} yield {
|
|
||||||
new ActionNodeInfo.Create {
|
|
||||||
def signatories = signatories_
|
|
||||||
def stakeholders = stakeholders_
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case NodeTypeCase.FETCH =>
|
|
||||||
val protoFetch = protoNode.getFetch
|
|
||||||
for {
|
|
||||||
actingParties_ <- toPartySet(protoFetch.getActorsList)
|
|
||||||
stakeholders_ <- toPartySet(protoFetch.getStakeholdersList)
|
|
||||||
signatories_ <- toPartySet(protoFetch.getSignatoriesList)
|
|
||||||
} yield {
|
|
||||||
new ActionNodeInfo.Fetch {
|
|
||||||
def signatories = signatories_
|
|
||||||
def stakeholders = stakeholders_
|
|
||||||
def actingParties = actingParties_
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case NodeTypeCase.EXERCISE =>
|
|
||||||
val protoExe = protoNode.getExercise
|
|
||||||
for {
|
|
||||||
actingParties_ <- toPartySet(protoExe.getActorsList)
|
|
||||||
signatories_ <- toPartySet(protoExe.getSignatoriesList)
|
|
||||||
stakeholders_ <- toPartySet(protoExe.getStakeholdersList)
|
|
||||||
choiceObservers_ <-
|
|
||||||
if (txVersion < TransactionVersion.minChoiceObservers)
|
|
||||||
Right(Set.empty[Party])
|
|
||||||
else
|
|
||||||
toPartySet(protoExe.getObserversList)
|
|
||||||
choiceAuthorizers_ <-
|
|
||||||
if (txVersion < TransactionVersion.minChoiceAuthorizers) {
|
|
||||||
Right(None)
|
|
||||||
} else
|
|
||||||
toPartySet(protoExe.getAuthorizersList).map(choiceAuthorizersList =>
|
|
||||||
Option.when(choiceAuthorizersList.nonEmpty)(choiceAuthorizersList)
|
|
||||||
)
|
|
||||||
} yield {
|
|
||||||
new ActionNodeInfo.Exercise {
|
|
||||||
def signatories = signatories_
|
|
||||||
def stakeholders = stakeholders_
|
|
||||||
def actingParties = actingParties_
|
|
||||||
def choiceObservers = choiceObservers_
|
|
||||||
def choiceAuthorizers = choiceAuthorizers_
|
|
||||||
def consuming = protoExe.getConsuming
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case NodeTypeCase.LOOKUP_BY_KEY =>
|
|
||||||
val protoLookupByKey = protoNode.getLookupByKey
|
|
||||||
for {
|
|
||||||
maintainers <- toPartySet(protoLookupByKey.getKeyWithMaintainers.getMaintainersList)
|
|
||||||
} yield {
|
|
||||||
new ActionNodeInfo.LookupByKey {
|
|
||||||
def hasResult = protoLookupByKey.hasContractIdStruct
|
|
||||||
def keyMaintainers = maintainers
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case NodeTypeCase.NODETYPE_NOT_SET => Left(DecodeError("Unset Node type"))
|
|
||||||
}
|
|
||||||
|
|
||||||
private[this] def keyHash(
|
private[this] def keyHash(
|
||||||
nodeVersion: TransactionVersion,
|
nodeVersion: TransactionVersion,
|
||||||
rawTmplId: ValueOuterClass.Identifier,
|
rawTmplId: ValueOuterClass.Identifier,
|
||||||
|
@ -71,11 +71,6 @@ class TransactionCoderSpec
|
|||||||
txVersion,
|
txVersion,
|
||||||
encodedNode,
|
encodedNode,
|
||||||
) shouldBe Right((NodeId(0), normalizeCreate(versionedNode)))
|
) shouldBe Right((NodeId(0), normalizeCreate(versionedNode)))
|
||||||
|
|
||||||
Right(createNode.informeesOfNode) shouldEqual
|
|
||||||
TransactionCoder
|
|
||||||
.protoActionNodeInfo(txVersion, encodedNode)
|
|
||||||
.map(_.informeesOfNode)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,10 +96,6 @@ class TransactionCoderSpec
|
|||||||
txVersion,
|
txVersion,
|
||||||
encodedNode,
|
encodedNode,
|
||||||
) shouldBe Right((NodeId(0), normalizeFetch(versionedNode)))
|
) shouldBe Right((NodeId(0), normalizeFetch(versionedNode)))
|
||||||
Right(fetchNode.informeesOfNode) shouldEqual
|
|
||||||
TransactionCoder
|
|
||||||
.protoActionNodeInfo(txVersion, encodedNode)
|
|
||||||
.map(_.informeesOfNode)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,11 +119,6 @@ class TransactionCoderSpec
|
|||||||
txVersion,
|
txVersion,
|
||||||
encodedNode,
|
encodedNode,
|
||||||
) shouldBe Right((NodeId(0), normalizedNode))
|
) shouldBe Right((NodeId(0), normalizedNode))
|
||||||
|
|
||||||
Right(normalizedNode.informeesOfNode) shouldEqual
|
|
||||||
TransactionCoder
|
|
||||||
.protoActionNodeInfo(txVersion, encodedNode)
|
|
||||||
.map(_.informeesOfNode)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,8 +29,6 @@
|
|||||||
type: jar-scala
|
type: jar-scala
|
||||||
- target: //daml-lf/interpreter:interpreter
|
- target: //daml-lf/interpreter:interpreter
|
||||||
type: jar-scala
|
type: jar-scala
|
||||||
- target: //daml-lf/kv-support:kv-support
|
|
||||||
type: jar-scala
|
|
||||||
- target: //daml-lf/language:language
|
- target: //daml-lf/language:language
|
||||||
type: jar-scala
|
type: jar-scala
|
||||||
- target: //daml-lf/parser:parser
|
- target: //daml-lf/parser:parser
|
||||||
|
Loading…
Reference in New Issue
Block a user