LF: Add version directly in GenNode (#8154)

CHANGELOG_BEGIN
CHANGELOG_END
This commit is contained in:
Remy 2020-12-08 16:12:12 +01:00 committed by GitHub
parent 38455e8ca9
commit 8f3c6a4494
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 645 additions and 567 deletions

View File

@ -9,11 +9,11 @@ abstract class LfVersions[V](versionsAscending: NonEmptyList[V])(protoValue: V =
protected val maxVersion: V = versionsAscending.last
val acceptedVersions: List[V] = versionsAscending.list.toList
private[lf] val acceptedVersions: List[V] = versionsAscending.list.toList
private val acceptedVersionsMap: Map[String, V] =
acceptedVersions.iterator.map(v => (protoValue(v), v)).toMap
def isAcceptedVersion(version: String): Option[V] = acceptedVersionsMap.get(version)
private[lf] def isAcceptedVersion(version: String): Option[V] = acceptedVersionsMap.get(version)
}

View File

@ -352,9 +352,7 @@ class Engine(val config: EngineConfig = EngineConfig.Stable) {
}
}
onLedger.ptx.finish(
compiledPackages.packageLanguageVersion,
) match {
onLedger.ptx.finish match {
case PartialTransaction.CompleteTransaction(tx) =>
val meta = Tx.Metadata(
submissionSeed = None,

View File

@ -143,7 +143,14 @@ private[engine] final class Preprocessor(compiledPackages: MutableCompiledPackag
private def getTemplateId(node: Node.GenNode.WithTxValue[NodeId, _]) =
node match {
case Node.NodeCreate(coid @ _, coinst, optLoc @ _, sigs @ _, stks @ _, key @ _) =>
case Node.NodeCreate(
coid @ _,
coinst,
optLoc @ _,
sigs @ _,
stks @ _,
key @ _,
version @ _) =>
coinst.template
case Node.NodeExercises(
coid @ _,
@ -160,11 +167,12 @@ private[engine] final class Preprocessor(compiledPackages: MutableCompiledPackag
exerciseResult @ _,
key @ _,
byKey @ _,
version @ _,
) =>
templateId
case Node.NodeFetch(coid @ _, templateId, _, _, _, _, _, _) =>
case Node.NodeFetch(coid @ _, templateId, _, _, _, _, _, _, _) =>
templateId
case Node.NodeLookupByKey(templateId, _, key @ _, _) =>
case Node.NodeLookupByKey(templateId, _, key @ _, _, _) =>
templateId
}

View File

@ -34,7 +34,14 @@ private[preprocessing] final class TransactionPreprocessor(
val (localCids, globalCids) = acc
node match {
case Node.NodeCreate(coid @ _, coinst, optLoc @ _, sigs @ _, stks @ _, key @ _) =>
case Node.NodeCreate(
coid @ _,
coinst,
optLoc @ _,
sigs @ _,
stks @ _,
key @ _,
version @ _) =>
val identifier = coinst.template
if (globalCids(coid))
fail("Conflicting discriminators between a global and local contract ID.")
@ -59,15 +66,17 @@ private[preprocessing] final class TransactionPreprocessor(
children @ _,
exerciseResult @ _,
key @ _,
byKey @ _) =>
byKey @ _,
version @ _,
) =>
val templateId = template
val (cmd, newCids) =
commandPreprocessor.unsafePreprocessExercise(templateId, coid, choice, chosenVal.value)
(cmd, (localCids | newCids.filterNot(globalCids), globalCids))
case Node.NodeFetch(coid, templateId, _, _, _, _, _, _) =>
case Node.NodeFetch(coid, templateId, _, _, _, _, _, _, _) =>
val cmd = commandPreprocessor.unsafePreprocessFetch(templateId, coid)
(cmd, acc)
case Node.NodeLookupByKey(templateId, _, key, _) =>
case Node.NodeLookupByKey(templateId, _, key, _, _) =>
val keyValue = unsafeAsValueWithNoContractIds(key.key.value)
val cmd = commandPreprocessor.unsafePreprocessLookupByKey(templateId, keyValue)
(cmd, acc)
@ -89,9 +98,9 @@ private[preprocessing] final class TransactionPreprocessor(
fail(s"invalid transaction, root refers to non-existing node $id")
case Some(node) =>
node match {
case Node.NodeFetch(_, _, _, _, _, _, _, _) =>
case _: Node.NodeFetch[_, _] =>
fail(s"Transaction contains a fetch root node $id")
case Node.NodeLookupByKey(_, _, _, _) =>
case _: Node.NodeLookupByKey[_, _] =>
fail(s"Transaction contains a lookup by key root node $id")
case _ =>
val (cmd, acc) = unsafeTranslateNode(cids, node)

View File

@ -29,7 +29,6 @@ import Value._
import com.daml.lf.speedy.{InitialSeeding, SValue, svalue}
import com.daml.lf.speedy.SValue._
import com.daml.lf.command._
import com.daml.lf.transaction.Node.VersionedNode
import com.daml.lf.transaction.TransactionVersions.UnversionedNode
import com.daml.lf.value.ValueVersions.assertAsVersionedValue
import org.scalactic.Equality
@ -1220,6 +1219,7 @@ class EngineTest
_,
_,
_,
_,
) =>
coid shouldBe originalCoid
consuming shouldBe true
@ -1230,7 +1230,7 @@ class EngineTest
}
findNodeByIdx(bobView.nodes, 1).getOrElse(fail("node not found")) match {
case Node.NodeCreate(_, coins, _, _, stakeholders, _) =>
case Node.NodeCreate(_, coins, _, _, stakeholders, _, _) =>
coins.template shouldBe templateId
stakeholders shouldBe Set(alice, clara)
case _ => fail("create event is expected")
@ -1242,7 +1242,7 @@ class EngineTest
claraView.nodes.size shouldBe 1
findNodeByIdx(claraView.nodes, 1).getOrElse(fail("node not found")) match {
case Node.NodeCreate(_, coins, _, _, stakeholders, _) =>
case Node.NodeCreate(_, coins, _, _, stakeholders, _, _) =>
coins.template shouldBe templateId
stakeholders shouldBe Set(alice, clara)
case _ => fail("create event is expected")
@ -1367,7 +1367,7 @@ class EngineTest
def actFetchActors[Nid, Cid, Val](n: Node.GenNode[Nid, Cid, Val]): Set[Party] = {
n match {
case Node.NodeFetch(_, _, _, actingParties, _, _, _, _) => actingParties
case Node.NodeFetch(_, _, _, actingParties, _, _, _, _, _) => actingParties
case _ => Set()
}
}
@ -1420,8 +1420,8 @@ class EngineTest
"be retained when reinterpreting single fetch nodes" in {
val Right((tx, txMeta)) = runExample(fetcher1Cid, clara)
val fetchNodes = tx.versionedNodes.iterator.collect {
case entry @ (_, VersionedNode(_, Node.NodeFetch(_, _, _, _, _, _, _, _))) => entry
val fetchNodes = tx.nodes.iterator.collect {
case entry @ (_, Node.NodeFetch(_, _, _, _, _, _, _, _, _)) => entry
}
fetchNodes.foreach {
@ -1429,7 +1429,7 @@ class EngineTest
val fetchTx = VersionedTransaction(n.version, Map(nid -> n), ImmArray(nid))
val Right((reinterpreted, _)) =
engine
.reinterpret(n.node.requiredAuthorizers, n.node, txMeta.nodeSeeds.toSeq.collectFirst {
.reinterpret(n.requiredAuthorizers, n, txMeta.nodeSeeds.toSeq.collectFirst {
case (`nid`, seed) => seed
}, txMeta.submissionTime, let)
.consume(lookupContract, lookupPackage, lookupKey)
@ -1481,6 +1481,7 @@ class EngineTest
stakeholders = Set.empty,
key = None,
byKey = false,
version = TxVersions.minVersion,
)
val let = Time.Timestamp.now()
@ -1531,7 +1532,7 @@ class EngineTest
tx: GenTx[Nid, Cid, Val],
): Option[(Nid, Node.NodeLookupByKey[Cid, Val])] =
tx.nodes.collectFirst {
case (nid, nl @ Node.NodeLookupByKey(_, _, _, _)) => nid -> nl
case (nid, nl @ Node.NodeLookupByKey(_, _, _, _, _)) => nid -> nl
}
val now = Time.Timestamp.now()
@ -1686,7 +1687,7 @@ class EngineTest
.consume(lookupContractMap.get, lookupPackage, lookupKey)
tx.transaction.nodes.values.headOption match {
case Some(Node.NodeFetch(_, _, _, _, _, _, key, _)) =>
case Some(Node.NodeFetch(_, _, _, _, _, _, key, _, _)) =>
key match {
// just test that the maintainers match here, getting the key out is a bit hairier
case Some(Node.KeyWithMaintainers(_, maintainers)) =>
@ -1811,7 +1812,7 @@ class EngineTest
"be partially reinterpretable" in {
val Right((tx, txMeta)) = run(3)
val ImmArray(_, exeNode1) = tx.transaction.roots
val Node.NodeExercises(_, _, _, _, _, _, _, _, _, _, children, _, _, _) =
val Node.NodeExercises(_, _, _, _, _, _, _, _, _, _, children, _, _, _, _) =
tx.transaction.nodes(exeNode1)
val nids = children.toSeq.take(2).toImmArray
@ -2062,11 +2063,13 @@ object EngineTest {
_,
_,
_,
_))) =>
_,
_,
))) =>
(contracts - targetCoid, keys)
case (
(contracts, keys),
(_, Node.NodeCreate(cid: ContractId, coinst, _, _, _, key))) =>
(_, Node.NodeCreate(cid: ContractId, coinst, _, _, _, key, _))) =>
(
contracts.updated(
cid,
@ -2106,7 +2109,6 @@ object EngineTest {
case (nodes, roots, dependsOnTime, nodeSeeds, _, _) =>
(
TxVersions.asVersionedTransaction(
engine.compiledPackages().packageLanguageVersion,
roots.toImmArray,
nodes,
),

View File

@ -95,7 +95,7 @@ class LargeTransactionTest extends AnyWordSpec with Matchers with BazelRunfiles
cmdReference = "create RangeOfInts",
seed = hash("testLargeTransactionOneContract:create", txSize))
val contractId = firstRootNode(createCmdTx) match {
case N.NodeCreate(coid, _, _, _, _, _) => coid
case N.NodeCreate(coid, _, _, _, _, _, _) => coid
case n @ _ => fail(s"Expected NodeCreate, but got: $n")
}
val exerciseCmd = toListContainerExerciseCmd(rangeOfIntsTemplateId, contractId)
@ -122,7 +122,7 @@ class LargeTransactionTest extends AnyWordSpec with Matchers with BazelRunfiles
cmdReference = "create RangeOfInts",
seed = hash("testLargeTransactionManySmallContracts:create", num))
val contractId = firstRootNode(createCmdTx) match {
case N.NodeCreate(coid, _, _, _, _, _) => coid
case N.NodeCreate(coid, _, _, _, _, _, _) => coid
case n @ _ => fail(s"Expected NodeCreate, but got: $n")
}
val exerciseCmd = toListOfIntContainers(rangeOfIntsTemplateId, contractId)
@ -149,7 +149,7 @@ class LargeTransactionTest extends AnyWordSpec with Matchers with BazelRunfiles
cmdReference = "create ListUtil",
seed = hash("testLargeChoiceArgument:create", size))
val contractId = firstRootNode(createCmdTx) match {
case N.NodeCreate(coid, _, _, _, _, _) => coid
case N.NodeCreate(coid, _, _, _, _, _, _) => coid
case n @ _ => fail(s"Expected NodeCreate, but got: $n")
}
val exerciseCmd = sizeExerciseCmd(listUtilTemplateId, contractId)(size)
@ -192,7 +192,7 @@ class LargeTransactionTest extends AnyWordSpec with Matchers with BazelRunfiles
}
newContracts.count {
case N.NodeCreate(_, _, _, _, _, _) => true
case N.NodeCreate(_, _, _, _, _, _, _) => true
case n @ _ => fail(s"Unexpected match: $n")
} shouldBe expectedNumberOfContracts
}
@ -317,7 +317,7 @@ class LargeTransactionTest extends AnyWordSpec with Matchers with BazelRunfiles
}
createNode match {
case N.NodeCreate(_, x: ContractInst[_], _, _, _, _) => x
case N.NodeCreate(_, x: ContractInst[_], _, _, _, _, _) => x
case n @ _ => fail(s"Unexpected match: $n")
}
}

View File

@ -448,7 +448,7 @@ object ScenarioLedger {
}
processNodes(mbNewCache2, idsToProcess)
case NodeFetch(referencedCoid, templateId @ _, optLoc @ _, _, _, _, _, _) =>
case NodeFetch(referencedCoid, templateId @ _, optLoc @ _, _, _, _, _, _, _) =>
val newCacheP =
newCache.updateLedgerNodeInfo(referencedCoid)(info =>
info.copy(referencedBy = info.referencedBy + eventId))

View File

@ -15,7 +15,7 @@ import com.daml.lf.data.Ref._
import com.daml.lf.data.Time
import com.daml.lf.scenario.ScenarioLedger.TransactionId
import com.daml.lf.scenario._
import com.daml.lf.transaction.{NodeId, Transaction => Tx}
import com.daml.lf.transaction.{NodeId, TransactionVersions, Transaction => Tx}
import com.daml.lf.speedy.SError._
import com.daml.lf.speedy.SValue._
import com.daml.lf.speedy.SBuiltin._
@ -41,6 +41,7 @@ private[lf] object Pretty {
def prettyError(err: SError): Doc = {
val ptx = PartialTransaction.initial(
(_ => TransactionVersions.minVersion),
submissionTime = Time.Timestamp.MinValue,
initialSeeds = InitialSeeding.NoSeed
)

View File

@ -1266,9 +1266,7 @@ private[lf] object SBuiltin {
throw SpeedyHungry(SResultScenarioInsertMustFail(committerOld, commitLocationOld))
case SBool(false) =>
ptxOld.finish(
machine.compiledPackages.packageLanguageVersion,
) match {
ptxOld.finish match {
case PartialTransaction.CompleteTransaction(tx) =>
// Transaction finished successfully. It might still
// fail when committed, so tell the scenario runner to
@ -1289,10 +1287,7 @@ private[lf] object SBuiltin {
args: util.ArrayList[SValue],
machine: Machine,
onLedger: OnLedger): Unit =
onLedger.ptx
.finish(
machine.compiledPackages.packageLanguageVersion,
) match {
onLedger.ptx.finish match {
case PartialTransaction.CompleteTransaction(tx) =>
throw SpeedyHungry(
SResultScenarioCommit(

View File

@ -15,6 +15,7 @@ import com.daml.lf.speedy.SError._
import com.daml.lf.speedy.SExpr._
import com.daml.lf.speedy.SResult._
import com.daml.lf.speedy.SValue._
import com.daml.lf.transaction.TransactionVersions
import com.daml.lf.value.{Value => V}
import org.slf4j.LoggerFactory
@ -629,7 +630,8 @@ private[lf] object Speedy {
onLedger.committers = Set.empty
onLedger.commitLocation = None
onLedger.ptx = PartialTransaction.initial(
submissionTime = onLedger.ptx.submissionTime,
onLedger.ptx.packageToTransactionVersion,
onLedger.ptx.submissionTime,
InitialSeeding.TransactionSeed(freshSeed),
)
}
@ -738,7 +740,9 @@ private[lf] object Speedy {
validating: Boolean = false,
checkAuthorization: CheckAuthorizationMode = CheckAuthorizationMode.On,
traceLog: TraceLog = RingBufferTraceLog(damlTraceLog, 100),
): Machine =
): Machine = {
val pkg2TxVersion =
compiledPackages.packageLanguageVersion.andThen(TransactionVersions.assignNodeVersion)
new Machine(
ctrl = expr,
returnValue = null,
@ -751,7 +755,7 @@ private[lf] object Speedy {
ledgerMode = OnLedger(
validating = validating,
checkAuthorization = checkAuthorization,
ptx = PartialTransaction.initial(submissionTime, initialSeeding),
ptx = PartialTransaction.initial(pkg2TxVersion, submissionTime, initialSeeding),
committers = committers,
commitLocation = None,
dependsOnTime = false,
@ -766,6 +770,7 @@ private[lf] object Speedy {
track = Instrumentation(),
profile = new Profile(),
)
}
@throws[PackageNotFound]
@throws[CompilationError]

View File

@ -6,16 +6,15 @@ package speedy
import com.daml.lf.data.Ref.{ChoiceName, Location, Party, TypeConName}
import com.daml.lf.data.{BackStack, ImmArray, Ref, Time}
import com.daml.lf.language.LanguageVersion
import com.daml.lf.ledger.Authorize
import com.daml.lf.ledger.FailedAuthorization
import com.daml.lf.transaction.{
GenTransaction,
GlobalKey,
Node,
NodeId,
SubmittedTransaction,
TransactionVersion,
TransactionVersions,
Transaction => Tx
}
@ -115,9 +114,11 @@ private[lf] object PartialTransaction {
)
def initial(
pkg2TxVersion: Ref.PackageId => TransactionVersion,
submissionTime: Time.Timestamp,
initialSeeds: InitialSeeding,
) = PartialTransaction(
pkg2TxVersion,
submissionTime = submissionTime,
nextNodeIdx = 0,
nodes = HashMap.empty,
@ -160,6 +161,7 @@ private[lf] object PartialTransaction {
* locally archived contract ids will succeed wrongly.
*/
private[lf] case class PartialTransaction(
packageToTransactionVersion: Ref.PackageId => TransactionVersion,
submissionTime: Time.Timestamp,
nextNodeIdx: Int,
nodes: HashMap[NodeId, PartialTransaction.Node],
@ -224,21 +226,14 @@ private[lf] case class PartialTransaction(
* - an error in case the transaction cannot be serialized using
* the `outputTransactionVersions`.
*/
def finish(
packageLanguageVersion: Ref.PackageId => LanguageVersion,
): PartialTransaction.Result =
if (context.exeContext.isEmpty && aborted.isEmpty)
def finish: PartialTransaction.Result =
if (context.exeContext.isEmpty && aborted.isEmpty) {
CompleteTransaction(
SubmittedTransaction(
TransactionVersions
.asVersionedTransaction(
packageLanguageVersion,
context.children.toImmArray,
nodes
)
TransactionVersions.asVersionedTransaction(context.children.toImmArray, nodes)
)
)
else
} else
IncompleteTransaction(this)
/** Extend the 'PartialTransaction' with a node for creating a
@ -270,6 +265,7 @@ private[lf] case class PartialTransaction(
signatories,
stakeholders,
key,
packageToTransactionVersion(coinst.template.packageId)
)
val nid = NodeId(nextNodeIdx)
val ptx = copy(
@ -312,7 +308,8 @@ private[lf] case class PartialTransaction(
signatories,
stakeholders,
key,
byKey
byKey,
packageToTransactionVersion(templateId.packageId),
)
mustBeActive(
coid,
@ -329,7 +326,13 @@ private[lf] case class PartialTransaction(
result: Option[Value.ContractId],
): PartialTransaction = {
val nid = NodeId(nextNodeIdx)
val node = Node.NodeLookupByKey(templateId, optLocation, key, result)
val node = Node.NodeLookupByKey(
templateId,
optLocation,
key,
result,
packageToTransactionVersion(templateId.packageId),
)
insertLeafNode(node)
.noteAuthFails(nid, CheckAuthorization.authorizeLookupByKey(node), auth)
}
@ -414,6 +417,7 @@ private[lf] case class PartialTransaction(
exerciseResult = Some(value),
key = ec.contractKey,
byKey = ec.byKey,
version = packageToTransactionVersion(ec.templateId.packageId),
)
val nodeId = ec.nodeId
val nodeSeed = ec.parent.nextChildrenSeed

View File

@ -6,14 +6,15 @@ package transaction
package test
import com.daml.lf.data.{BackStack, ImmArray, Ref}
import com.daml.lf.transaction.Node.{GenNode, VersionedNode}
import com.daml.lf.transaction.Node.GenNode
import com.daml.lf.transaction.{Transaction => Tx}
import com.daml.lf.value.Value.{ContractId, ContractInst}
import com.daml.lf.value.{ValueVersions, Value => LfValue}
import scala.collection.immutable.HashMap
final class TransactionBuilder(pkgTxVersion: Ref.PackageId => TransactionVersion) {
final class TransactionBuilder(pkgTxVersion: Ref.PackageId => TransactionVersion = _ =>
TransactionVersions.minVersion) {
import TransactionBuilder._
@ -44,7 +45,7 @@ final class TransactionBuilder(pkgTxVersion: Ref.PackageId => TransactionVersion
def add(node: Node, parentId: NodeId): NodeId = ids.synchronized {
lazy val nodeId = newNode(node) // lazy to avoid getting the next id if the method later throws
nodes(parentId) match {
case VersionedNode(_, _: TxExercise) =>
case _: TxExercise =>
children += parentId -> (children(parentId) :+ nodeId)
case _ =>
throw new IllegalArgumentException(
@ -57,9 +58,9 @@ final class TransactionBuilder(pkgTxVersion: Ref.PackageId => TransactionVersion
import VersionTimeline.Implicits._
val finalNodes = nodes.transform {
case (nid, VersionedNode(version, exe: TxExercise)) =>
VersionedNode[NodeId, ContractId](version, exe.copy(children = children(nid).toImmArray))
case (_, node @ VersionedNode(_, _: Node.LeafOnlyNode[_, _])) =>
case (nid, exe: TxExercise) =>
exe.copy(children = children(nid).toImmArray)
case (_, node: Node.LeafOnlyNode[ContractId, TxValue]) =>
node
}
val finalRoots = roots.toImmArray
@ -93,17 +94,100 @@ final class TransactionBuilder(pkgTxVersion: Ref.PackageId => TransactionVersion
value.Value.VersionedValue(pkgValVersion(templateId.packageId), _)
private[this] def versionN(node: Node): TxNode =
VersionedNode(
pkgTxVersion(node.templateId.packageId),
GenNode.map3(identity[NodeId], identity[ContractId], versionValue(node.templateId))(node)
GenNode.map3(identity[NodeId], identity[ContractId], versionValue(node.templateId))(node)
def create(
id: String,
template: String,
argument: Value,
signatories: Seq[String],
observers: Seq[String],
key: Option[String],
): Create = {
val templateId = Ref.Identifier.assertFromString(template)
Create(
coid = ContractId.assertFromString(id),
coinst = ContractInst(
template = templateId,
arg = argument,
agreementText = "",
),
optLocation = None,
signatories = signatories.map(Ref.Party.assertFromString).toSet,
stakeholders = signatories.union(observers).map(Ref.Party.assertFromString).toSet,
key = key.map(keyWithMaintainers(maintainers = signatories, _)),
version = pkgTxVersion(templateId.packageId),
)
}
def exercise(
contract: Create,
choice: String,
consuming: Boolean,
actingParties: Set[String],
argument: Value,
byKey: Boolean = true,
): Exercise =
Exercise(
choiceObservers = Set.empty, //FIXME #7709: take observers as argument (pref no default value)
targetCoid = contract.coid,
templateId = contract.coinst.template,
choiceId = Ref.ChoiceName.assertFromString(choice),
optLocation = None,
consuming = consuming,
actingParties = actingParties.map(Ref.Party.assertFromString),
chosenValue = argument,
stakeholders = contract.stakeholders,
signatories = contract.signatories,
children = ImmArray.empty,
exerciseResult = None,
key = contract.key,
byKey = byKey,
version = pkgTxVersion(contract.coinst.template.packageId),
)
def exerciseByKey(
contract: Create,
choice: String,
consuming: Boolean,
actingParties: Set[String],
argument: Value,
): Exercise =
exercise(contract, choice, consuming, actingParties, argument, byKey = true)
def fetch(contract: Create, byKey: Boolean = true): Fetch =
Fetch(
coid = contract.coid,
templateId = contract.coinst.template,
optLocation = None,
actingParties = contract.signatories.map(Ref.Party.assertFromString),
signatories = contract.signatories,
stakeholders = contract.stakeholders,
key = contract.key,
byKey = byKey,
version = pkgTxVersion(contract.coinst.template.packageId),
)
def fetchByKey(contract: Create): Fetch =
fetch(contract, byKey = true)
def lookupByKey(contract: Create, found: Boolean): LookupByKey =
LookupByKey(
templateId = contract.coinst.template,
optLocation = None,
key = contract.key.get,
result = if (found) Some(contract.coid) else None,
version = pkgTxVersion(contract.coinst.template.packageId),
)
}
object TransactionBuilder {
type Value = value.Value[ContractId]
type TxValue = value.Value.VersionedValue[ContractId]
type Node = Node.GenNode[NodeId, ContractId, Value]
type TxNode = VersionedNode[NodeId, ContractId]
type TxNode = Node.GenNode.WithTxValue[NodeId, ContractId]
type Create = Node.NodeCreate[ContractId, Value]
type Exercise = Node.NodeExercises[NodeId, ContractId, Value]
@ -157,84 +241,6 @@ object TransactionBuilder {
maintainers = maintainers.map(Ref.Party.assertFromString).toSet,
)
def create(
id: String,
template: String,
argument: Value,
signatories: Seq[String],
observers: Seq[String],
key: Option[String],
): Create =
Create(
coid = ContractId.assertFromString(id),
coinst = ContractInst(
template = Ref.Identifier.assertFromString(template),
arg = argument,
agreementText = "",
),
optLocation = None,
signatories = signatories.map(Ref.Party.assertFromString).toSet,
stakeholders = signatories.union(observers).map(Ref.Party.assertFromString).toSet,
key = key.map(keyWithMaintainers(maintainers = signatories, _)),
)
def exercise(
contract: Create,
choice: String,
consuming: Boolean,
actingParties: Set[String],
argument: Value,
byKey: Boolean = true,
): Exercise =
Exercise(
choiceObservers = Set.empty, //FIXME #7709: take observers as argument (pref no default value)
targetCoid = contract.coid,
templateId = contract.coinst.template,
choiceId = Ref.ChoiceName.assertFromString(choice),
optLocation = None,
consuming = consuming,
actingParties = actingParties.map(Ref.Party.assertFromString),
chosenValue = argument,
stakeholders = contract.stakeholders,
signatories = contract.signatories,
children = ImmArray.empty,
exerciseResult = None,
key = contract.key,
byKey = byKey
)
def exerciseByKey(
contract: Create,
choice: String,
consuming: Boolean,
actingParties: Set[String],
argument: Value,
): Exercise =
exercise(contract, choice, consuming, actingParties, argument, byKey = true)
def fetch(contract: Create, byKey: Boolean = true): Fetch =
Fetch(
coid = contract.coid,
templateId = contract.coinst.template,
optLocation = None,
actingParties = contract.signatories.map(Ref.Party.assertFromString),
signatories = contract.signatories,
stakeholders = contract.stakeholders,
key = contract.key,
byKey = byKey,
)
def fetchByKey(contract: Create): Fetch =
fetch(contract, byKey = true)
def lookupByKey(contract: Create, found: Boolean): LookupByKey =
LookupByKey(
templateId = contract.coinst.template,
optLocation = None,
key = contract.key.get,
result = if (found) Some(contract.coid) else None
)
def just(node: Node, nodes: Node*): Tx.Transaction = {
val builder = TransactionBuilder()
val _ = builder.add(node)

View File

@ -8,11 +8,11 @@ package test
import com.daml.lf.data.Ref._
import com.daml.lf.data._
import com.daml.lf.transaction.Node.{
GenNode,
KeyWithMaintainers,
NodeCreate,
NodeExercises,
NodeFetch,
VersionedNode
NodeFetch
}
import com.daml.lf.transaction.VersionTimeline.Implicits._
import com.daml.lf.transaction.{
@ -284,16 +284,18 @@ object ValueGenerators {
*/
val malformedCreateNodeGen: Gen[NodeCreate.WithTxValue[Value.ContractId]] = {
for {
version <- transactionVersionGen
coid <- coidGen
coinst <- contractInstanceGen
signatories <- genNonEmptyParties
stakeholders <- genNonEmptyParties
key <- Gen.option(keyWithMaintainersGen)
} yield NodeCreate(coid, coinst, None, signatories, stakeholders, key)
} yield NodeCreate(coid, coinst, None, signatories, stakeholders, key, version)
}
val fetchNodeGen: Gen[NodeFetch.WithTxValue[ContractId]] = {
for {
version <- transactionVersionGen
coid <- coidGen
templateId <- idGen
actingParties <- genNonEmptyParties
@ -301,13 +303,24 @@ object ValueGenerators {
stakeholders <- genNonEmptyParties
key <- Gen.option(keyWithMaintainersGen)
byKey <- Gen.oneOf(true, false)
} yield NodeFetch(coid, templateId, None, actingParties, signatories, stakeholders, key, byKey)
} yield
NodeFetch(
coid,
templateId,
None,
actingParties,
signatories,
stakeholders,
key,
byKey,
version)
}
/** Makes exercise nodes with some random child IDs. */
val danglingRefExerciseNodeGen
: Gen[NodeExercises[NodeId, Value.ContractId, Tx.Value[Value.ContractId]]] = {
for {
version <- transactionVersionGen
targetCoid <- coidGen
templateId <- idGen
choiceId <- nameGen
@ -340,7 +353,8 @@ object ValueGenerators {
children,
Some(exerciseResultValue),
Some(KeyWithMaintainers(key, maintainers)),
byKey = byKey,
byKey,
version,
)
}
@ -450,12 +464,12 @@ object ValueGenerators {
tx <- noDanglingRefGenTransaction
txVer <- transactionVersionGen
nodeVersionGen = transactionVersionGen.filter(v => !(txVer precedes v))
nodes <- tx.fold(Gen.const(HashMap.empty[NodeId, VersionedNode[NodeId, ContractId]])) {
nodes <- tx.fold(Gen.const(HashMap.empty[NodeId, GenNode.WithTxValue[NodeId, ContractId]])) {
case (acc, (nodeId, node)) =>
for {
hashMap <- acc
version <- nodeVersionGen
} yield hashMap.updated(nodeId, VersionedNode(version, node))
} yield hashMap.updated(nodeId, node.updateVersion(version))
}
} yield VersionedTransaction(txVer, nodes, tx.roots)

View File

@ -6,15 +6,7 @@ package transaction
import com.daml.lf.data.Ref._
import com.daml.lf.data.{ImmArray, ScalazEqual}
import com.daml.lf.value.Value.VersionedValue
import com.daml.lf.value.{
CidContainer,
CidContainer1,
CidContainer2,
CidContainer3,
CidMapper,
Value
}
import com.daml.lf.value._
import scalaz.Equal
import scalaz.std.option._
import scalaz.syntax.equal._
@ -35,6 +27,7 @@ object Node {
with CidContainer[GenNode[Nid, Cid, Val]] {
def templateId: TypeConName
def version: TransactionVersion
final override protected def self: this.type = this
@ -62,6 +55,8 @@ object Node {
def foreach3(fNid: Nid => Unit, fCid: Cid => Unit, fVal: Val => Unit) =
GenNode.foreach3(fNid, fCid, fVal)(this)
private[lf] def updateVersion(version: TransactionVersion): GenNode[Nid, Cid, Val]
}
object GenNode extends WithTxValue3[GenNode] with CidContainer3[GenNode] {
@ -78,6 +73,7 @@ object Node {
_,
_,
key,
_,
) =>
self copy (
coid = f2(coid),
@ -93,6 +89,7 @@ object Node {
_,
key,
_,
_,
) =>
self copy (
coid = f2(coid),
@ -113,6 +110,7 @@ object Node {
exerciseResult,
key,
_,
_,
) =>
self copy (
targetCoid = f2(targetCoid),
@ -126,6 +124,7 @@ object Node {
_,
key,
result,
_,
) =>
self copy (
key = KeyWithMaintainers.map1(f3)(key),
@ -145,6 +144,7 @@ object Node {
signatories @ _,
stakeholders @ _,
key,
_,
) =>
f2(coid)
Value.ContractInst.foreach1(f3)(coinst)
@ -158,6 +158,7 @@ object Node {
stakeholdersd @ _,
key,
_,
_,
) =>
f2(coid)
key.foreach(KeyWithMaintainers.foreach1(f3))
@ -176,6 +177,7 @@ object Node {
exerciseResult,
key,
_,
_,
) =>
f2(targetCoid)
f3(chosenValue)
@ -187,6 +189,7 @@ object Node {
optLocation @ _,
key,
result,
_,
) =>
KeyWithMaintainers.foreach1(f3)(key)
result.foreach(f2)
@ -206,11 +209,16 @@ object Node {
signatories: Set[Party],
stakeholders: Set[Party],
key: Option[KeyWithMaintainers[Val]],
// For the sake of consistency between types with a version field, keep this field the last.
override val version: TransactionVersion,
) extends LeafOnlyNode[Cid, Val]
with NodeInfo.Create {
override def templateId: TypeConName = coinst.template
override def byKey: Boolean = false
override private[lf] def updateVersion(version: TransactionVersion): NodeCreate[Cid, Val] =
copy(version = version)
}
object NodeCreate extends WithTxValue2[NodeCreate]
@ -224,9 +232,15 @@ object Node {
signatories: Set[Party],
stakeholders: Set[Party],
key: Option[KeyWithMaintainers[Val]],
override val byKey: Boolean // invariant (!byKey || exerciseResult.isDefined)
override val byKey: Boolean, // invariant (!byKey || exerciseResult.isDefined)
// For the sake of consistency between types with a version field, keep this field the last.
override val version: TransactionVersion,
) extends LeafOnlyNode[Cid, Val]
with NodeInfo.Fetch
with NodeInfo.Fetch {
override private[lf] def updateVersion(version: TransactionVersion): NodeFetch[Cid, Val] =
copy(version = version)
}
object NodeFetch extends WithTxValue2[NodeFetch]
@ -249,11 +263,18 @@ object Node {
children: ImmArray[Nid],
exerciseResult: Option[Val],
key: Option[KeyWithMaintainers[Val]],
override val byKey: Boolean // invariant (!byKey || exerciseResult.isDefined)
override val byKey: Boolean, // invariant (!byKey || exerciseResult.isDefined)
// For the sake of consistency between types with a version field, keep this field the last.
override val version: TransactionVersion,
) extends GenNode[Nid, Cid, Val]
with NodeInfo.Exercise {
@deprecated("use actingParties instead", since = "1.1.2")
private[daml] def controllers: actingParties.type = actingParties
override private[lf] def updateVersion(
version: TransactionVersion,
): NodeExercises[Nid, Cid, Val] =
copy(version = version)
}
object NodeExercises extends WithTxValue3[NodeExercises]
@ -263,12 +284,17 @@ object Node {
optLocation: Option[Location],
key: KeyWithMaintainers[Val],
result: Option[Cid],
// For the sake of consistency between types with a version field, keep this field the last.
override val version: TransactionVersion,
) extends LeafOnlyNode[Cid, Val]
with NodeInfo.LookupByKey {
override def keyMaintainers: Set[Party] = key.maintainers
override def hasResult: Boolean = result.isDefined
override def byKey: Boolean = true
override private[lf] def updateVersion(version: TransactionVersion): NodeLookupByKey[Cid, Val] =
copy(version = version)
}
object NodeLookupByKey extends WithTxValue2[NodeLookupByKey]
@ -311,10 +337,18 @@ object Node {
): Boolean =
ScalazEqual.match2[recorded.type, isReplayedBy.type, Boolean](fallback = false) {
case nc: NodeCreate[Cid, Val] => {
case NodeCreate(coid2, coinst2, optLocation2 @ _, signatories2, stakeholders2, key2) =>
case NodeCreate(
coid2,
coinst2,
optLocation2 @ _,
signatories2,
stakeholders2,
key2,
version2) =>
import nc._
// NOTE(JM): Do not compare location annotations as they may differ due to
// differing update expression constructed from the root node.
version == version2 &&
coid === coid2 && coinst === coinst2 &&
signatories == signatories2 && stakeholders == stakeholders2 && key === key2
case _ => false
@ -329,8 +363,10 @@ object Node {
stakeholders2,
key2,
_,
version2,
) =>
import nf._
version == version2 &&
coid === coid2 && templateId == templateId2 &&
actingParties.forall(_ => actingParties == actingParties2) &&
signatories == signatories2 && stakeholders == stakeholders2 &&
@ -352,8 +388,10 @@ object Node {
exerciseResult2,
key2,
_,
version2,
) =>
import ne._
version == version2 &&
targetCoid === targetCoid2 && templateId == templateId2 && choiceId == choiceId2 &&
consuming == consuming2 && actingParties == actingParties2 && chosenValue === chosenValue2 &&
stakeholders == stakeholders2 && signatories == signatories2 && choiceObservers == choiceObservers2 &&
@ -361,8 +399,9 @@ object Node {
key.fold(true)(_ => key === key2)
}
case nl: NodeLookupByKey[Cid, Val] => {
case NodeLookupByKey(templateId2, optLocation2 @ _, key2, result2) =>
case NodeLookupByKey(templateId2, optLocation2 @ _, key2, result2, version2) =>
import nl._
version == version2 &&
templateId == templateId2 &&
key === key2 && result === result2
}
@ -376,31 +415,6 @@ object Node {
type WithTxValue[+Nid, +Cid] = F[Nid, Cid, Transaction.Value[Cid]]
}
case class VersionedNode[+Nid, +Cid](
version: TransactionVersion,
node: Node.GenNode.WithTxValue[Nid, Cid],
) extends CidContainer[VersionedNode[Nid, Cid]] {
override protected def self: this.type = this
}
object VersionedNode extends CidContainer2[VersionedNode] {
override private[lf] def map2[A1, B1, A2, B2](
f1: A1 => A2,
f2: B1 => B2,
): VersionedNode[A1, B1] => VersionedNode[A2, B2] = {
case VersionedNode(version, node) =>
VersionedNode(version, GenNode.map3(f1, f2, VersionedValue.map1(f2))(node))
}
override private[lf] def foreach2[A, B](
f1: A => Unit,
f2: B => Unit,
): VersionedNode[A, B] => Unit = {
case VersionedNode(_, node) =>
GenNode.foreach3(f1, f2, VersionedValue.foreach1(f2))(node)
}
}
}
final case class NodeId(index: Int)

View File

@ -7,7 +7,7 @@ package transaction
import com.daml.lf.data.Ref._
import com.daml.lf.data._
import com.daml.lf.ledger.FailedAuthorization
import com.daml.lf.transaction.Node.VersionedNode
import com.daml.lf.transaction.Node.GenNode
import com.daml.lf.value.Value
import scalaz.Equal
@ -16,7 +16,7 @@ import scala.collection.immutable.HashMap
final case class VersionedTransaction[Nid, +Cid] private[lf] (
version: TransactionVersion,
versionedNodes: Map[Nid, Node.VersionedNode[Nid, Cid]],
nodes: Map[Nid, GenNode.WithTxValue[Nid, Cid]],
override val roots: ImmArray[Nid],
) extends HasTxNodes[Nid, Cid, Transaction.Value[Cid]]
with value.CidContainer[VersionedTransaction[Nid, Cid]]
@ -26,20 +26,14 @@ final case class VersionedTransaction[Nid, +Cid] private[lf] (
@deprecated("use resolveRelCid/ensureNoCid/ensureNoRelCid", since = "0.13.52")
def mapContractId[Cid2](f: Cid => Cid2): VersionedTransaction[Nid, Cid2] = {
val versionNode = Node.VersionedNode.map2(identity[Nid], f)
val versionNode = GenNode.map3(identity[Nid], f, Value.VersionedValue.map1(f))
VersionedTransaction(
version,
versionedNodes = versionedNodes.transform((_, node) => versionNode(node)),
nodes = nodes.transform((_, node) => versionNode(node)),
roots,
)
}
// O(1)
override def nodes: Map[Nid, Node.GenNode.WithTxValue[Nid, Cid]] =
// Here we use intentionally `mapValues` to get the time/memory complexity of the method constant.
// It is fine since the function `_.node` is very cheap.
versionedNodes.mapValues(_.node)
// O(1)
def transaction: GenTransaction[Nid, Cid, Transaction.Value[Cid]] =
GenTransaction(nodes, roots)
@ -53,7 +47,7 @@ object VersionedTransaction extends value.CidContainer2[VersionedTransaction] {
f2: B1 => B2,
): VersionedTransaction[A1, B1] => VersionedTransaction[A2, B2] = {
case VersionedTransaction(version, versionedNodes, roots) =>
val mapNode = Node.VersionedNode.map2(f1, f2)
val mapNode = GenNode.map3(f1, f2, Value.VersionedValue.map1(f2))
VersionedTransaction(
version,
versionedNodes.map {
@ -68,7 +62,7 @@ object VersionedTransaction extends value.CidContainer2[VersionedTransaction] {
f2: B => Unit,
): VersionedTransaction[A, B] => Unit = {
case VersionedTransaction(_, versionedNodes, _) =>
val foreachNode = Node.VersionedNode.foreach2(f1, f2)
val foreachNode = GenNode.foreach3(f1, f2, Value.VersionedValue.foreach1(f2))
versionedNodes.foreach {
case (nid, node) =>
f1(nid)
@ -331,7 +325,7 @@ sealed abstract class HasTxNodes[Nid, +Cid, +Val] {
final def localContracts[Cid2 >: Cid]: Map[Cid2, Nid] =
fold(Map.empty[Cid2, Nid]) {
case (acc, (nid, create @ Node.NodeCreate(_, _, _, _, _, _))) =>
case (acc, (nid, create @ Node.NodeCreate(_, _, _, _, _, _, _))) =>
acc.updated(create.coid, nid)
case (acc, _) => acc
}
@ -341,11 +335,11 @@ sealed abstract class HasTxNodes[Nid, +Cid, +Val] {
*/
final def inputContracts[Cid2 >: Cid]: Set[Cid2] =
fold(Set.empty[Cid2]) {
case (acc, (_, Node.NodeExercises(coid, _, _, _, _, _, _, _, _, _, _, _, _, _))) =>
case (acc, (_, Node.NodeExercises(coid, _, _, _, _, _, _, _, _, _, _, _, _, _, _))) =>
acc + coid
case (acc, (_, Node.NodeFetch(coid, _, _, _, _, _, _, _))) =>
case (acc, (_, Node.NodeFetch(coid, _, _, _, _, _, _, _, _))) =>
acc + coid
case (acc, (_, Node.NodeLookupByKey(_, _, _, Some(coid)))) =>
case (acc, (_, Node.NodeLookupByKey(_, _, _, Some(coid), _))) =>
acc + coid
case (acc, _) => acc
} -- localContracts.keySet
@ -478,15 +472,15 @@ object GenTransaction extends value.CidContainer3[GenTransaction] {
tx.fold(State(Set.empty, Set.empty)) {
case (state, (_, node)) =>
node match {
case Node.NodeCreate(_, c, _, _, _, Some(key)) =>
case Node.NodeCreate(_, c, _, _, _, Some(key), _) =>
state.created(globalKey(c.template, key.key.value))
case Node.NodeExercises(_, tmplId, _, _, true, _, _, _, _, _, _, _, Some(key), _) =>
case Node.NodeExercises(_, tmplId, _, _, true, _, _, _, _, _, _, _, Some(key), _, _) =>
state.consumed(globalKey(tmplId, key.key.value))
case Node.NodeExercises(_, tmplId, _, _, false, _, _, _, _, _, _, _, Some(key), _) =>
case Node.NodeExercises(_, tmplId, _, _, false, _, _, _, _, _, _, _, Some(key), _, _) =>
state.referenced(globalKey(tmplId, key.key.value))
case Node.NodeFetch(_, tmplId, _, _, _, _, Some(key), _) =>
case Node.NodeFetch(_, tmplId, _, _, _, _, Some(key), _, _) =>
state.referenced(globalKey(tmplId, key.key.value))
case Node.NodeLookupByKey(tmplId, _, key, Some(_)) =>
case Node.NodeLookupByKey(tmplId, _, key, Some(_), _) =>
state.referenced(globalKey(tmplId, key.key.value))
case _ =>
state
@ -598,123 +592,128 @@ object Transaction {
): Either[ReplayMismatch[Nid, Cid], Unit] =
(nids1, nids2) match {
case (nid1 #:: rest1, nid2 #:: rest2) =>
val VersionedNode(version1, node1) = recorded.versionedNodes(nid1)
val VersionedNode(version2, node2) = replayed.versionedNodes(nid2)
if (version1 != version2)
Left(ReplayNodeMismatch(recorded, nid1, replayed, nid2))
else
(node1, node2) match {
case (
Node.NodeCreate(
coid1,
coinst1,
optLocation1 @ _,
signatories1,
stakeholders1,
key1
),
Node.NodeCreate(
coid2,
coinst2,
optLocation2 @ _,
signatories2,
stakeholders2,
key2
))
if coid1 === coid2 &&
coinst1 === coinst2 &&
signatories1 == signatories2 &&
stakeholders1 == stakeholders2 &&
key1 === key2 =>
loop(rest1, rest2, stack)
case (
Node.NodeFetch(
coid1,
templateId1,
optLocation1 @ _,
actingParties1,
signatories1,
stakeholders1,
key1,
byKey1 @ _,
),
Node.NodeFetch(
coid2,
templateId2,
optLocation2 @ _,
actingParties2,
signatories2,
stakeholders2,
key2,
byKey2 @ _,
))
if coid1 === coid2 &&
templateId1 == templateId2 &&
(actingParties1.isEmpty || actingParties1 == actingParties2) &&
signatories1 == signatories2 &&
stakeholders1 == stakeholders2 &&
(key1.isEmpty || key1 === key2) =>
loop(rest1, rest2, stack)
case (
exe1 @ Node.NodeExercises(
targetCoid1,
templateId1,
choiceId1,
optLocation1 @ _,
consuming1,
actingParties1,
chosenValue1,
stakeholders1,
signatories1,
choiceObservers1,
children1 @ _,
exerciseResult1 @ _,
key1,
byKey1 @ _,
),
exe2 @ Node.NodeExercises(
targetCoid2,
templateId2,
choiceId2,
optLocation2 @ _,
consuming2,
actingParties2,
chosenValue2,
stakeholders2,
signatories2,
choiceObservers2,
children2 @ _,
exerciseResult2 @ _,
key2,
byKey2 @ _,
))
// results are checked after the children
if targetCoid1 === targetCoid2 &&
templateId1 == templateId2 &&
choiceId1 == choiceId2 &&
consuming1 == consuming2 &&
actingParties1 == actingParties2 &&
chosenValue1 === chosenValue2 &&
stakeholders1 == stakeholders2 &&
signatories1 == signatories2 &&
choiceObservers1 == choiceObservers2 &&
(key1.isEmpty || key1 === key2) =>
loop(
children1.iterator.toStream,
children2.iterator.toStream,
(nid1, exe1, rest1, nid2, exe2, rest2) :: stack
(recorded.nodes(nid1), replayed.nodes(nid2)) match {
case (
Node.NodeCreate(
coid1,
coinst1,
optLocation1 @ _,
signatories1,
stakeholders1,
key1,
version1,
),
Node.NodeCreate(
coid2,
coinst2,
optLocation2 @ _,
signatories2,
stakeholders2,
key2,
version2,
))
if version1 == version2 &&
coid1 === coid2 &&
coinst1 === coinst2 &&
signatories1 == signatories2 &&
stakeholders1 == stakeholders2 &&
key1 === key2 =>
loop(rest1, rest2, stack)
case (
Node.NodeFetch(
coid1,
templateId1,
optLocation1 @ _,
actingParties1,
signatories1,
stakeholders1,
key1,
byKey1 @ _,
version1,
),
Node.NodeFetch(
coid2,
templateId2,
optLocation2 @ _,
actingParties2,
signatories2,
stakeholders2,
key2,
byKey2 @ _,
version2,
))
if version1 == version2 &&
coid1 === coid2 &&
templateId1 == templateId2 &&
(actingParties1.isEmpty || actingParties1 == actingParties2) &&
signatories1 == signatories2 &&
stakeholders1 == stakeholders2 &&
(key1.isEmpty || key1 === key2) =>
loop(rest1, rest2, stack)
case (
exe1 @ Node.NodeExercises(
targetCoid1,
templateId1,
choiceId1,
optLocation1 @ _,
consuming1,
actingParties1,
chosenValue1,
stakeholders1,
signatories1,
choiceObservers1,
children1 @ _,
exerciseResult1 @ _,
key1,
byKey1 @ _,
version1,
),
exe2 @ Node.NodeExercises(
targetCoid2,
templateId2,
choiceId2,
optLocation2 @ _,
consuming2,
actingParties2,
chosenValue2,
stakeholders2,
signatories2,
choiceObservers2,
children2 @ _,
exerciseResult2 @ _,
key2,
byKey2 @ _,
version2,
))
// results are checked after the children
if version1 == version2 &&
targetCoid1 === targetCoid2 &&
templateId1 == templateId2 &&
choiceId1 == choiceId2 &&
consuming1 == consuming2 &&
actingParties1 == actingParties2 &&
chosenValue1 === chosenValue2 &&
stakeholders1 == stakeholders2 &&
signatories1 == signatories2 &&
choiceObservers1 == choiceObservers2 &&
(key1.isEmpty || key1 === key2) =>
loop(
children1.iterator.toStream,
children2.iterator.toStream,
(nid1, exe1, rest1, nid2, exe2, rest2) :: stack
)
case (
Node.NodeLookupByKey(templateId1, optLocation1 @ _, key1, result1, version1),
Node.NodeLookupByKey(templateId2, optLocation2 @ _, key2, result2, version2)
)
case (
Node.NodeLookupByKey(templateId1, optLocation1 @ _, key1, result1),
Node.NodeLookupByKey(templateId2, optLocation2 @ _, key2, result2)
)
if templateId1 == templateId2 &&
key1 === key2 &&
result1 === result2 =>
loop(rest1, rest2, stack)
case _ =>
Left(ReplayNodeMismatch(recorded, nid1, replayed, nid2))
}
if version1 == version2 &&
templateId1 == templateId2 &&
key1 === key2 &&
result1 === result2 =>
loop(rest1, rest2, stack)
case _ =>
Left(ReplayNodeMismatch(recorded, nid1, replayed, nid2))
}
case (Stream.Empty, Stream.Empty) =>
stack match {

View File

@ -118,19 +118,6 @@ object TransactionCoder {
.build()
)
private[lf] def encodeVersionedNode[Nid, Cid](
encodeNid: EncodeNid[Nid],
encodeCid: ValueCoder.EncodeCid[Cid],
enclosingVersion: TransactionVersion,
nodeId: Nid,
versionedNode: VersionedNode[Nid, Cid],
): Either[EncodeError, TransactionOuterClass.Node] =
if (enclosingVersion precedes versionedNode.version)
Left(EncodeError(
s"A transaction of version $enclosingVersion cannot contain nodes of newer version (${versionedNode.version}"))
else
encodeNode(encodeNid, encodeCid, versionedNode.version, nodeId, versionedNode.node)
/**
* encodes a [[GenNode[Nid, Cid]] to protocol buffer
* @param nodeId node id of the node to be encoded
@ -144,109 +131,116 @@ object TransactionCoder {
private[lf] def encodeNode[Nid, Cid](
encodeNid: EncodeNid[Nid],
encodeCid: ValueCoder.EncodeCid[Cid],
version: TransactionVersion,
enclosingVersion: TransactionVersion,
nodeId: Nid,
node: GenNode[Nid, Cid, Value.VersionedValue[Cid]],
): Either[EncodeError, TransactionOuterClass.Node] = {
val nodeBuilder = TransactionOuterClass.Node.newBuilder().setNodeId(encodeNid.asString(nodeId))
nodeBuilder.setVersion(version.protoValue)
): Either[EncodeError, TransactionOuterClass.Node] =
if (enclosingVersion precedes node.version)
Left(EncodeError(
s"A transaction of version $enclosingVersion cannot contain nodes of newer version (${node.version}"))
else {
node match {
case nc @ NodeCreate(_, _, _, _, _, _) =>
val createBuilder =
TransactionOuterClass.NodeCreate
val nodeBuilder =
TransactionOuterClass.Node.newBuilder().setNodeId(encodeNid.asString(nodeId))
nodeBuilder.setVersion(node.version.protoValue)
node match {
case nc @ NodeCreate(_, _, _, _, _, _, _) =>
val createBuilder =
TransactionOuterClass.NodeCreate
.newBuilder()
.addAllStakeholders(nc.stakeholders.toSet[String].asJava)
.addAllSignatories(nc.signatories.toSet[String].asJava)
for {
inst <- encodeContractInstance(encodeCid, nc.coinst)
optKey <- nc.key match {
case None => Right(None)
case Some(key) => encodeKeyWithMaintainers(encodeCid, key).map(Some(_))
}
encodedCid = encodeCid.encode(nc.coid)
} yield {
createBuilder.setContractIdStruct(encodedCid)
createBuilder.setContractInstance(inst)
optKey.foreach(createBuilder.setKeyWithMaintainers)
nodeBuilder.setCreate(createBuilder).build()
}
case nf @ NodeFetch(_, _, _, _, _, _, _, _, _) =>
val fetchBuilder = TransactionOuterClass.NodeFetch
.newBuilder()
.addAllStakeholders(nc.stakeholders.toSet[String].asJava)
.addAllSignatories(nc.signatories.toSet[String].asJava)
for {
inst <- encodeContractInstance(encodeCid, nc.coinst)
optKey <- nc.key match {
case None => Right(None)
case Some(key) => encodeKeyWithMaintainers(encodeCid, key).map(Some(_))
}
encodedCid = encodeCid.encode(nc.coid)
} yield {
createBuilder.setContractIdStruct(encodedCid)
createBuilder.setContractInstance(inst)
optKey.foreach(createBuilder.setKeyWithMaintainers)
nodeBuilder.setCreate(createBuilder).build()
}
.setTemplateId(ValueCoder.encodeIdentifier(nf.templateId))
.addAllStakeholders(nf.stakeholders.toSet[String].asJava)
.addAllSignatories(nf.signatories.toSet[String].asJava)
case nf @ NodeFetch(_, _, _, _, _, _, _, _) =>
val fetchBuilder = TransactionOuterClass.NodeFetch
.newBuilder()
.setTemplateId(ValueCoder.encodeIdentifier(nf.templateId))
.addAllStakeholders(nf.stakeholders.toSet[String].asJava)
.addAllSignatories(nf.signatories.toSet[String].asJava)
for {
optKey <- nf.key match {
case None => Right(None)
case Some(key) => encodeKeyWithMaintainers(encodeCid, key).map(Some(_))
for {
optKey <- nf.key match {
case None => Right(None)
case Some(key) => encodeKeyWithMaintainers(encodeCid, key).map(Some(_))
}
encodedCid = encodeCid.encode(nf.coid)
} yield {
fetchBuilder.setContractIdStruct(encodedCid)
nf.actingParties.foreach(fetchBuilder.addActors)
optKey.foreach(fetchBuilder.setKeyWithMaintainers)
nodeBuilder.setFetch(fetchBuilder).build()
}
encodedCid = encodeCid.encode(nf.coid)
} yield {
fetchBuilder.setContractIdStruct(encodedCid)
nf.actingParties.foreach(fetchBuilder.addActors)
optKey.foreach(fetchBuilder.setKeyWithMaintainers)
nodeBuilder.setFetch(fetchBuilder).build()
}
case ne @ NodeExercises(_, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
for {
_ <- Either.cond(
test =
!((version precedes TransactionVersions.minChoiceObservers) && ne.choiceObservers.nonEmpty),
right = (),
left = EncodeError(version, isTooOldFor = "non-empty choice-observers")
)
argValue <- encodeValue(encodeCid, ne.chosenValue)
retValue <- ne.exerciseResult match {
case Some(value) => encodeValue(encodeCid, value)
case None => Left(EncodeError("NodeExercises without result"))
case ne @ NodeExercises(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _) =>
for {
_ <- Either.cond(
test =
!((node.version precedes TransactionVersions.minChoiceObservers) && ne.choiceObservers.nonEmpty),
right = (),
left = EncodeError(node.version, isTooOldFor = "non-empty choice-observers")
)
argValue <- encodeValue(encodeCid, ne.chosenValue)
retValue <- ne.exerciseResult match {
case Some(value) => encodeValue(encodeCid, value)
case None => Left(EncodeError("NodeExercises without result"))
}
exBuilder = TransactionOuterClass.NodeExercise
.newBuilder()
.setChoice(ne.choiceId)
.setTemplateId(ValueCoder.encodeIdentifier(ne.templateId))
.setChosenValue(argValue)
.setConsuming(ne.consuming)
.addAllActors(ne.actingParties.toSet[String].asJava)
.addAllChildren(ne.children.map(encodeNid.asString).toList.asJava)
.addAllSignatories(ne.signatories.toSet[String].asJava)
.addAllStakeholders(ne.stakeholders.toSet[String].asJava)
.addAllObservers(ne.choiceObservers.toSet[String].asJava)
encodedCid = encodeCid.encode(ne.targetCoid)
_ <- Right(
ne.key
.map { kWithM =>
encodeKeyWithMaintainers(encodeCid, kWithM).foreach(
exBuilder.setKeyWithMaintainers)
}
.getOrElse(()),
)
} yield {
exBuilder.setContractIdStruct(encodedCid)
exBuilder.setReturnValue(retValue)
nodeBuilder.setExercise(exBuilder).build()
}
exBuilder = TransactionOuterClass.NodeExercise
.newBuilder()
.setChoice(ne.choiceId)
.setTemplateId(ValueCoder.encodeIdentifier(ne.templateId))
.setChosenValue(argValue)
.setConsuming(ne.consuming)
.addAllActors(ne.actingParties.toSet[String].asJava)
.addAllChildren(ne.children.map(encodeNid.asString).toList.asJava)
.addAllSignatories(ne.signatories.toSet[String].asJava)
.addAllStakeholders(ne.stakeholders.toSet[String].asJava)
.addAllObservers(ne.choiceObservers.toSet[String].asJava)
encodedCid = encodeCid.encode(ne.targetCoid)
_ <- Right(
ne.key
.map { kWithM =>
encodeKeyWithMaintainers(encodeCid, kWithM).foreach(exBuilder.setKeyWithMaintainers)
}
.getOrElse(()),
)
} yield {
exBuilder.setContractIdStruct(encodedCid)
exBuilder.setReturnValue(retValue)
nodeBuilder.setExercise(exBuilder).build()
}
case nlbk @ NodeLookupByKey(_, _, _, _) =>
val nlbkBuilder = TransactionOuterClass.NodeLookupByKey.newBuilder()
for {
encodedKey <- encodeKeyWithMaintainers(encodeCid, nlbk.key)
encodedCid = nlbk.result match {
case Some(cid) => Some(encodeCid.encode(cid))
case None => None
case nlbk @ NodeLookupByKey(_, _, _, _, _) =>
val nlbkBuilder = TransactionOuterClass.NodeLookupByKey.newBuilder()
for {
encodedKey <- encodeKeyWithMaintainers(encodeCid, nlbk.key)
encodedCid = nlbk.result match {
case Some(cid) => Some(encodeCid.encode(cid))
case None => None
}
} yield {
nlbkBuilder
.setTemplateId(ValueCoder.encodeIdentifier(nlbk.templateId))
.setKeyWithMaintainers(encodedKey)
encodedCid.foreach(nlbkBuilder.setContractIdStruct)
nodeBuilder.setLookupByKey(nlbkBuilder).build()
}
} yield {
nlbkBuilder
.setTemplateId(ValueCoder.encodeIdentifier(nlbk.templateId))
.setKeyWithMaintainers(encodedKey)
encodedCid.foreach(nlbkBuilder.setContractIdStruct)
nodeBuilder.setLookupByKey(nlbkBuilder).build()
}
}
}
}
private def decodeKeyWithMaintainers[Cid](
decodeCid: ValueCoder.DecodeCid[Cid],
keyWithMaintainers: TransactionOuterClass.KeyWithMaintainers,
@ -270,7 +264,7 @@ object TransactionCoder {
decodeCid: ValueCoder.DecodeCid[Cid],
enclosingVersion: TransactionVersion,
protoNode: TransactionOuterClass.Node,
): Either[DecodeError, (Nid, VersionedNode[Nid, Cid])] =
): Either[DecodeError, (Nid, GenNode.WithTxValue[Nid, Cid])] =
for {
version <- if (enclosingVersion precedes TransactionVersions.minNodeVersion) {
Right(enclosingVersion)
@ -293,7 +287,7 @@ object TransactionCoder {
decodeCid: ValueCoder.DecodeCid[Cid],
version: TransactionVersion,
protoNode: TransactionOuterClass.Node,
): Either[DecodeError, (Nid, VersionedNode[Nid, Cid])] = {
): Either[DecodeError, (Nid, GenNode.WithTxValue[Nid, Cid])] = {
val nodeId = decodeNid.fromString(protoNode.getNodeId)
protoNode.getNodeTypeCase match {
@ -308,8 +302,7 @@ object TransactionCoder {
key <- if (protoCreate.getKeyWithMaintainers == TransactionOuterClass.KeyWithMaintainers.getDefaultInstance)
Right(None)
else decodeKeyWithMaintainers(decodeCid, protoCreate.getKeyWithMaintainers).map(Some(_))
} yield
(ni, VersionedNode(version, NodeCreate(c, ci, None, signatories, stakeholders, key)))
} yield ni -> NodeCreate(c, ci, None, signatories, stakeholders, key, version)
case NodeTypeCase.FETCH =>
val protoFetch = protoNode.getFetch
for {
@ -324,12 +317,16 @@ object TransactionCoder {
else
decodeKeyWithMaintainers(decodeCid, protoFetch.getKeyWithMaintainers).map(Some(_))
} yield
(
ni,
VersionedNode(
version,
NodeFetch(c, templateId, None, actingParties, signatories, stakeholders, key, false),
)
ni -> NodeFetch(
c,
templateId,
None,
actingParties,
signatories,
stakeholders,
key,
false,
version,
)
case NodeTypeCase.EXERCISE =>
@ -364,27 +361,22 @@ object TransactionCoder {
}
choiceName <- toIdentifier(protoExe.getChoice)
} yield
(
ni,
VersionedNode(
version,
NodeExercises(
targetCoid = targetCoid,
templateId = templateId,
choiceId = choiceName,
optLocation = None,
consuming = protoExe.getConsuming,
actingParties = actingParties,
chosenValue = cv,
stakeholders = stakeholders,
signatories = signatories,
choiceObservers = choiceObservers,
children = children,
exerciseResult = Some(rv),
key = keyWithMaintainers,
byKey = false,
),
)
ni -> NodeExercises(
targetCoid = targetCoid,
templateId = templateId,
choiceId = choiceName,
optLocation = None,
consuming = protoExe.getConsuming,
actingParties = actingParties,
chosenValue = cv,
stakeholders = stakeholders,
signatories = signatories,
choiceObservers = choiceObservers,
children = children,
exerciseResult = Some(rv),
key = keyWithMaintainers,
byKey = false,
version = version,
)
case NodeTypeCase.LOOKUP_BY_KEY =>
val protoLookupByKey = protoNode.getLookupByKey
@ -394,13 +386,7 @@ object TransactionCoder {
key <- decodeKeyWithMaintainers(decodeCid, protoLookupByKey.getKeyWithMaintainers)
cid <- decodeCid.decodeOptional(protoLookupByKey.getContractIdStruct)
} yield
(
ni,
VersionedNode(
version,
NodeLookupByKey[Cid, Value.VersionedValue[Cid]](templateId, None, key, cid)
)
)
ni -> NodeLookupByKey[Cid, Value.VersionedValue[Cid]](templateId, None, key, cid, version)
case NodeTypeCase.NODETYPE_NOT_SET => Left(DecodeError("Unset Node type"))
}
}
@ -456,12 +442,12 @@ object TransactionCoder {
case (builderOrError, (nid, _)) =>
for {
builder <- builderOrError
encodedNode <- encodeVersionedNode(
encodedNode <- encodeNode(
encodeNid,
encodeCid,
transaction.version,
nid,
transaction.versionedNodes(nid),
transaction.nodes(nid),
)
} yield builder.addNodes(encodedNode)
}
@ -529,7 +515,8 @@ object TransactionCoder {
.map(_.toImmArray)
val nodes = protoTx.getNodesList.asScala
.foldLeft[Either[DecodeError, HashMap[Nid, VersionedNode[Nid, Cid]]]](Right(HashMap.empty)) {
.foldLeft[Either[DecodeError, HashMap[Nid, GenNode.WithTxValue[Nid, Cid]]]](
Right(HashMap.empty)) {
case (Left(e), _) => Left(e)
case (Right(acc), s) =>
decodeVersionedNode(decodeNid, decodeCid, txVersion, s).map(acc + _)

View File

@ -4,7 +4,7 @@
package com.daml.lf
package transaction
import com.daml.lf.data.{ImmArray, Ref}
import com.daml.lf.data.ImmArray
import com.daml.lf.language.LanguageVersion
import com.daml.lf.value.Value.{ContractId, VersionedValue}
import com.daml.lf.value.{Value, ValueVersion, ValueVersions}
@ -16,7 +16,7 @@ final case class TransactionVersion(protoValue: String)
/**
* Currently supported versions of the DAML-LF transaction specification.
*/
private[lf] object TransactionVersions
object TransactionVersions
extends LfVersions(versionsAscending = VersionTimeline.ascendingVersions[TransactionVersion])(
_.protoValue,
) {
@ -24,18 +24,18 @@ private[lf] object TransactionVersions
import VersionTimeline._
import VersionTimeline.Implicits._
private[transaction] val minVersion = TransactionVersion("10")
val minVersion = TransactionVersion("10")
private[transaction] val minChoiceObservers = TransactionVersion("dev")
private[transaction] val minNodeVersion = TransactionVersion("dev")
// Older versions are deprecated https://github.com/digital-asset/daml/issues/5220
val StableOutputVersions: VersionRange[TransactionVersion] =
private[lf] val StableOutputVersions: VersionRange[TransactionVersion] =
VersionRange(TransactionVersion("10"), TransactionVersion("10"))
val DevOutputVersions: VersionRange[TransactionVersion] =
private[lf] val DevOutputVersions: VersionRange[TransactionVersion] =
StableOutputVersions.copy(max = acceptedVersions.last)
val Empty: VersionRange[TransactionVersion] =
private[lf] val Empty: VersionRange[TransactionVersion] =
VersionRange(acceptedVersions.last, acceptedVersions.head)
private[lf] def assignValueVersion(nodeVersion: TransactionVersion): ValueVersion =
@ -44,25 +44,21 @@ private[lf] object TransactionVersions
private[lf] def assignNodeVersion(langVersion: LanguageVersion): TransactionVersion =
VersionTimeline.latestWhenAllPresent(TransactionVersions.minVersion, langVersion)
type UnversionedNode = Node.GenNode[NodeId, Value.ContractId, Value[Value.ContractId]]
type VersionedNode = Node.GenNode[NodeId, Value.ContractId, VersionedValue[Value.ContractId]]
private[lf] type UnversionedNode = Node.GenNode[NodeId, Value.ContractId, Value[Value.ContractId]]
private[lf] type VersionedNode =
Node.GenNode[NodeId, Value.ContractId, VersionedValue[Value.ContractId]]
def asVersionedTransaction(
pkgLangVersions: Ref.PackageId => LanguageVersion,
private[lf] def asVersionedTransaction(
roots: ImmArray[NodeId],
nodes: HashMap[NodeId, UnversionedNode],
): VersionedTransaction[NodeId, Value.ContractId] = {
val versionedNodes = nodes.transform { (_, node) =>
val nodeVersion = assignNodeVersion(pkgLangVersions(node.templateId.packageId))
val valueVersion = assignValueVersion(nodeVersion)
Node.VersionedNode(
nodeVersion,
Node.GenNode.map3(
identity[NodeId],
identity[ContractId],
VersionedValue[ContractId](valueVersion, _))(node),
)
val valueVersion = assignValueVersion(node.version)
Node.GenNode.map3(
identity[NodeId],
identity[ContractId],
VersionedValue[ContractId](valueVersion, _))(node)
}
val txVersion = roots.iterator.foldLeft(TransactionVersions.minVersion)((acc, nodeId) =>

View File

@ -57,9 +57,9 @@ class TransactionCoderSpec
forAll(malformedCreateNodeGen, transactionVersionGen, transactionVersionGen) {
(createNode, version1, version2) =>
val (nodeVersion, txVersion) = inIncreasingOrder(version1, version2)
val versionedNode = VersionedNode(nodeVersion, createNode)
val versionedNode = createNode.updateVersion(nodeVersion)
val Right(encodedNode) = TransactionCoder
.encodeVersionedNode(
.encodeNode(
TransactionCoder.NidEncoder,
ValueCoder.CidEncoder,
txVersion,
@ -86,10 +86,10 @@ class TransactionCoderSpec
(fetchNode, version1, version2) =>
val (nodeVersion, txVersion) = inIncreasingOrder(version1, version2)
val versionedNode = VersionedNode(nodeVersion, withoutByKeyFlag(fetchNode))
val versionedNode = withoutByKeyFlag(fetchNode).updateVersion(nodeVersion)
val encodedNode =
TransactionCoder
.encodeVersionedNode(
.encodeNode(
TransactionCoder.NidEncoder,
ValueCoder.CidEncoder,
txVersion,
@ -118,10 +118,10 @@ class TransactionCoderSpec
val (nodeVersion, txVersion) = inIncreasingOrder(version1, version2)
val normalizedNode = minimalistNode(nodeVersion)(exerciseNode)
val versionedNode = VersionedNode(nodeVersion, normalizedNode)
val versionedNode = normalizedNode.updateVersion(nodeVersion)
val Right(encodedNode) =
TransactionCoder
.encodeVersionedNode(
.encodeNode(
TransactionCoder.NidEncoder,
ValueCoder.CidEncoder,
txVersion,
@ -147,8 +147,8 @@ class TransactionCoderSpec
forAll(noDanglingRefGenVersionedTransaction, minSuccessful(50)) { tx =>
val tx2 = VersionedTransaction(
tx.version,
tx.versionedNodes.transform((_, node) =>
VersionedNode(node.version, minimalistNode(node.version)(node.node))),
tx.nodes.transform((_, node) =>
minimalistNode(node.version)(node.updateVersion(node.version))),
tx.roots,
)
inside(
@ -175,7 +175,12 @@ class TransactionCoderSpec
}
}
"succeed with encoding under later version if succeeded under earlier version" in
"succeed with encoding under later version if succeeded under earlier version" in {
def overrideNodeVersions[Nid, Cid](tx: GenTransaction.WithTxValue[Nid, Cid]) = {
tx.copy(nodes = tx.nodes.transform((_, node) =>
node.updateVersion(TransactionVersions.minVersion)))
}
forAll(noDanglingRefGenTransaction, minSuccessful(50)) { tx =>
forAll(transactionVersionGen, transactionVersionGen, minSuccessful(20)) {
(txVer1, txVer2) =>
@ -210,14 +215,16 @@ class TransactionCoderSpec
)),
) {
case (Right(decWithMin), Right(decWithMax)) =>
decWithMin.transaction shouldBe minimalistTx(txvMin, tx)
decWithMin.transaction shouldBe
minimalistTx(txvMin, decWithMax.transaction)
overrideNodeVersions(decWithMin.transaction) shouldBe
overrideNodeVersions(minimalistTx(txvMin, tx))
overrideNodeVersions(decWithMin.transaction) shouldBe
overrideNodeVersions(minimalistTx(txvMin, decWithMax.transaction))
}
}
}
}
}
}
"transactions decoding should fail when unsupported value version received" in
forAll(noDanglingRefGenTransaction, minSuccessful(50)) { tx =>
@ -260,7 +267,7 @@ class TransactionCoderSpec
ValueCoder.CidEncoder,
VersionedTransaction(
badTxVer,
tx.nodes.mapValues(VersionedNode(badTxVer, _)),
tx.nodes.mapValues(_.updateVersion(badTxVer)),
tx.roots),
),
)
@ -296,15 +303,16 @@ class TransactionCoderSpec
signatories = Set(Party.assertFromString("alice")),
stakeholders = Set(Party.assertFromString("alice"), Party.assertFromString("bob")),
key = None,
version = TransactionVersions.minVersion,
)
forEvery(transactionVersions) { version =>
val versionedNode = VersionedNode(version, node)
val versionedNode = node.updateVersion(version)
val roots = ImmArray.ImmArraySeq.range(0, 10000).map(NodeId(_)).toImmArray
val nodes = roots.iterator.map(nid => nid -> versionedNode).toMap
val tx = VersionedTransaction(
version,
versionedNodes = nodes,
nodes = nodes.mapValues(_.copy(version = version)),
roots = roots
)
@ -346,12 +354,12 @@ class TransactionCoderSpec
forEvery(transactionVersions) { txVersion =>
val result = TransactionCoder
.encodeVersionedNode(
.encodeNode(
TransactionCoder.NidEncoder,
ValueCoder.CidEncoder,
txVersion,
NodeId(0),
VersionedNode(v10, normalized)
normalized.updateVersion(v10),
)
result.isLeft shouldBe shouldFail
@ -369,12 +377,12 @@ class TransactionCoderSpec
val normalizedNode = minimalistNode(nodeVersion)(node)
TransactionCoder
.encodeVersionedNode(
.encodeNode(
TransactionCoder.NidEncoder,
ValueCoder.CidEncoder,
txVersion,
nodeId,
VersionedNode(nodeVersion, normalizedNode)
normalizedNode.updateVersion(nodeVersion),
) shouldBe 'left
}
}
@ -389,11 +397,11 @@ class TransactionCoderSpec
forAll(danglingRefGenNode, Gen.asciiStr, minSuccessful(10)) {
case ((nodeId, node), str) =>
val normalizedNode = VersionedNode(v10, normalize(node))
val normalizedNode = normalize(node.updateVersion(v10))
val Right(encoded) = for {
encoded <- TransactionCoder
.encodeVersionedNode(
.encodeNode(
TransactionCoder.NidEncoder,
ValueCoder.CidEncoder,
v10,
@ -424,10 +432,10 @@ class TransactionCoderSpec
whenever(version1 != version2) {
val (txVersion, nodeVersion) = inIncreasingOrder(version1, version2)
val normalizedNode = VersionedNode(nodeVersion, minimalistNode(nodeVersion)(node))
val normalizedNode = minimalistNode(nodeVersion)(node).updateVersion(nodeVersion)
val Right(encoded) = TransactionCoder
.encodeVersionedNode(
.encodeNode(
TransactionCoder.NidEncoder,
ValueCoder.CidEncoder,
nodeVersion,
@ -457,12 +465,12 @@ class TransactionCoderSpec
minSuccessful(50),
) { (nodeIdx, node, strings) =>
val nodeId = NodeId(nodeIdx)
val normalizedNode = VersionedNode(v10, normalize(node))
val normalizedNode = normalize(node).updateVersion(v10)
val Right(encoded) =
for {
encoded <- TransactionCoder
.encodeVersionedNode(
.encodeNode(
TransactionCoder.NidEncoder,
ValueCoder.CidEncoder,
v10,
@ -607,8 +615,8 @@ class TransactionCoderSpec
private def versionNodes[Nid, Cid](
version: TransactionVersion,
nodes: Map[Nid, GenNode[Nid, Cid, Tx.Value[Cid]]],
): Map[Nid, VersionedNode[Nid, Cid]] =
nodes.mapValues(VersionedNode(version, _))
): Map[Nid, GenNode.WithTxValue[Nid, Cid]] =
nodes.mapValues(_.updateVersion(version))
private[this] def inIncreasingOrder(version1: TransactionVersion, version2: TransactionVersion) =
if (version1 precedes version2)

View File

@ -12,10 +12,10 @@ import com.daml.lf.transaction.GenTransaction.{
NotWellFormedError,
OrphanedNode
}
import com.daml.lf.transaction.Node.{GenNode, NodeCreate, NodeExercises, VersionedNode}
import com.daml.lf.transaction.Node.{GenNode, NodeCreate, NodeExercises}
import com.daml.lf.value.Value.ContractId
import com.daml.lf.value.{Value => V}
import com.daml.lf.value.test.ValueGenerators.{danglingRefGenNode, transactionVersionGen}
import com.daml.lf.value.test.ValueGenerators.danglingRefGenNode
import org.scalacheck.Gen
import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks
import org.scalatest.matchers.should.Matchers
@ -133,19 +133,19 @@ class TransactionSpec extends AnyFreeSpec with Matchers with ScalaCheckDrivenPro
*/
"isReplayedBy" - {
def genTrans(node: VersionedNode[NodeId, ContractId]) = {
def genTrans(node: GenNode.WithTxValue[NodeId, ContractId]) = {
val nid = NodeId(1)
VersionedTransaction(node.version, HashMap(nid -> node), ImmArray(nid))
}
def isReplayedBy(
n1: VersionedNode[NodeId, ContractId],
n2: VersionedNode[NodeId, ContractId],
n1: GenNode.WithTxValue[NodeId, ContractId],
n2: GenNode.WithTxValue[NodeId, ContractId],
) = Transaction.isReplayedBy(genTrans(n1), genTrans(n2))
// the whole-transaction-relevant parts are handled by equalForest testing
type CidVal[F[_, _]] = F[V.ContractId, V.VersionedValue[V.ContractId]]
val genEmptyNode: Gen[VersionedNode[Nothing, V.ContractId]] =
val genEmptyNode: Gen[GenNode.WithTxValue[Nothing, V.ContractId]] =
for {
entry <- danglingRefGenNode
node = entry match {
@ -153,8 +153,7 @@ class TransactionSpec extends AnyFreeSpec with Matchers with ScalaCheckDrivenPro
case (_, ne: Node.NodeExercises.WithTxValue[_, V.ContractId]) =>
ne.copy(children = ImmArray.empty)
}
version <- transactionVersionGen
} yield VersionedNode(version, node)
} yield node
"is reflexive" in forAll(genEmptyNode) { n =>
isReplayedBy(n, n) shouldBe Right(())
@ -167,7 +166,7 @@ class TransactionSpec extends AnyFreeSpec with Matchers with ScalaCheckDrivenPro
if (randomVersion != v) randomVersion else versions.last
}
forAll(genEmptyNode, minSuccessful(10)) { n =>
val m = n.copy(version = diffVersion(n.version))
val m = n.updateVersion(diffVersion(n.version))
isReplayedBy(n, m) shouldBe 'left
}
}
@ -180,15 +179,14 @@ class TransactionSpec extends AnyFreeSpec with Matchers with ScalaCheckDrivenPro
"ignores location" in forAll(genEmptyNode) { n =>
val withoutLocation = {
val VersionedNode(version, node) = n
val nodeWithoutLocation = node match {
val nodeWithoutLocation = n match {
case nc: CidVal[Node.NodeCreate] => nc copy (optLocation = None)
case nf: Node.NodeFetch.WithTxValue[V.ContractId] => nf copy (optLocation = None)
case ne: Node.NodeExercises.WithTxValue[Nothing, V.ContractId] =>
ne copy (optLocation = None)
case nl: CidVal[Node.NodeLookupByKey] => nl copy (optLocation = None)
}
VersionedNode(version, nodeWithoutLocation)
nodeWithoutLocation
}
isReplayedBy(withoutLocation, n) shouldBe Right(())
isReplayedBy(n, withoutLocation) shouldBe Right(())
@ -266,7 +264,8 @@ object TransactionSpec {
children = children,
exerciseResult = if (hasExerciseResult) Some(V.ValueUnit) else None,
key = None,
byKey = false
byKey = false,
version = TransactionVersions.minVersion,
)
val dummyCid = V.ContractId.V1.assertBuild(
@ -289,6 +288,7 @@ object TransactionSpec {
signatories = Set.empty,
stakeholders = Set.empty,
key = None,
version = TransactionVersions.minVersion,
)
implicit def toChoiceName(s: String): Ref.Name = Ref.Name.assertFromString(s)

View File

@ -488,7 +488,7 @@ class IdeClient(val compiledPackages: CompiledPackages) extends ScriptLedgerClie
case SResultNeedKey(keyWithMaintainers, committers, cb) =>
scenarioRunner.lookupKey(keyWithMaintainers.globalKey, committers, cb).toTry.get
case SResultFinalValue(SUnit) =>
onLedger.ptx.finish(machine.compiledPackages.packageLanguageVersion) match {
onLedger.ptx.finish match {
case PartialTransaction.CompleteTransaction(tx) =>
val results: ImmArray[ScriptLedgerClient.CommandResult] = tx.roots.map { n =>
tx.nodes(n) match {

View File

@ -53,7 +53,7 @@ private[migration] class V5_1__Populate_Event_Data extends BaseJavaMigration {
val data = txs.flatMap {
case (txId, tx) =>
tx.nodes.collect {
case (nodeId, NodeCreate(cid, _, _, signatories, stakeholders, _)) =>
case (nodeId, NodeCreate(cid, _, _, signatories, stakeholders, _, _)) =>
(cid, EventId(txId, nodeId), signatories, stakeholders -- signatories)
}
}

View File

@ -53,7 +53,7 @@ private[migration] class V10_1__Populate_Event_Data extends BaseJavaMigration {
val data = txs.flatMap {
case (txId, tx) =>
tx.nodes.collect {
case (nodeId, NodeCreate(cid, _, _, signatories, stakeholders, _)) =>
case (nodeId, NodeCreate(cid, _, _, signatories, stakeholders, _, _)) =>
(cid, EventId(txId, nodeId), signatories, stakeholders -- signatories)
}
}

View File

@ -8,6 +8,7 @@ import java.util.UUID
import com.daml.lf.data.{ImmArray, Ref}
import com.daml.lf.transaction.Node.{KeyWithMaintainers, NodeCreate, NodeExercises, NodeFetch}
import com.daml.lf.transaction.TransactionVersions
import com.daml.lf.transaction.test.TransactionBuilder
import com.daml.lf.value.Value.{ContractInst, ValueParty, VersionedValue}
import com.daml.lf.value.ValueVersion
@ -32,7 +33,8 @@ private[dao] trait JdbcLedgerDaoDivulgenceSpec extends LoneElement with Inside {
optLocation = None,
signatories = Set(alice),
stakeholders = Set(alice),
key = None
key = None,
version = TransactionVersions.minVersion,
)
)
contractId -> builder.buildCommitted()
@ -49,7 +51,8 @@ private[dao] trait JdbcLedgerDaoDivulgenceSpec extends LoneElement with Inside {
stakeholders = Set(bob),
key = Some(
KeyWithMaintainers(ValueParty(bob), Set(bob))
)
),
version = TransactionVersions.minVersion,
)
)
contractId -> builder.buildCommitted()
@ -72,6 +75,7 @@ private[dao] trait JdbcLedgerDaoDivulgenceSpec extends LoneElement with Inside {
exerciseResult = None,
key = None,
byKey = false,
version = TransactionVersions.minVersion,
)
)
builder.add(
@ -85,7 +89,8 @@ private[dao] trait JdbcLedgerDaoDivulgenceSpec extends LoneElement with Inside {
key = Some(
KeyWithMaintainers(ValueParty(bob), Set(bob))
),
byKey = false
byKey = false,
version = TransactionVersions.minVersion,
),
parentId = rootExercise,
)
@ -107,6 +112,7 @@ private[dao] trait JdbcLedgerDaoDivulgenceSpec extends LoneElement with Inside {
KeyWithMaintainers(ValueParty(bob), Set(bob))
),
byKey = false,
version = TransactionVersions.minVersion,
),
parentId = rootExercise,
)
@ -119,7 +125,8 @@ private[dao] trait JdbcLedgerDaoDivulgenceSpec extends LoneElement with Inside {
stakeholders = Set(alice, bob),
key = Some(
KeyWithMaintainers(ValueParty(bob), Set(bob))
)
),
version = TransactionVersions.minVersion,
),
parentId = nestedExercise,
)

View File

@ -19,7 +19,13 @@ import com.daml.lf.data.Ref.{Identifier, Party}
import com.daml.lf.data.{ImmArray, Ref}
import com.daml.lf.transaction.Node._
import com.daml.lf.transaction.test.TransactionBuilder
import com.daml.lf.transaction.{BlindingInfo, CommittedTransaction, Node, NodeId}
import com.daml.lf.transaction.{
BlindingInfo,
CommittedTransaction,
Node,
NodeId,
TransactionVersions
}
import com.daml.lf.value.Value
import com.daml.lf.value.Value.{ContractId, ContractInst, ValueRecord, ValueText, ValueUnit}
import com.daml.logging.LoggingContext
@ -110,7 +116,8 @@ private[dao] trait JdbcLedgerDaoSuite extends JdbcLedgerDaoBackend {
optLocation = None,
signatories = signatories,
stakeholders = signatories,
key = None
key = None,
version = TransactionVersions.minVersion,
)
protected final def createWithStakeholders(
@ -124,7 +131,8 @@ private[dao] trait JdbcLedgerDaoSuite extends JdbcLedgerDaoBackend {
optLocation = None,
signatories = signatories,
stakeholders = stakeholders,
key = None
key = None,
version = TransactionVersions.minVersion,
)
private def exercise(
@ -145,6 +153,7 @@ private[dao] trait JdbcLedgerDaoSuite extends JdbcLedgerDaoBackend {
exerciseResult = Some(ValueText("some exercise result")),
key = None,
byKey = false,
version = TransactionVersions.minVersion,
)
// All non-transient contracts created in a transaction
@ -232,6 +241,7 @@ private[dao] trait JdbcLedgerDaoSuite extends JdbcLedgerDaoBackend {
exerciseResult = Some(ValueUnit),
key = None,
byKey = false,
version = TransactionVersions.minVersion,
)
)
txBuilder.add(
@ -244,6 +254,7 @@ private[dao] trait JdbcLedgerDaoSuite extends JdbcLedgerDaoBackend {
stakeholders = Set(alice),
None,
byKey = false,
version = TransactionVersions.minVersion,
),
exerciseId,
)
@ -561,7 +572,8 @@ private[dao] trait JdbcLedgerDaoSuite extends JdbcLedgerDaoBackend {
optLocation = None,
signatories = Set(party),
stakeholders = Set(party),
key = Some(KeyWithMaintainers(ValueText(key), Set(party)))
key = Some(KeyWithMaintainers(ValueText(key), Set(party))),
version = TransactionVersions.minVersion,
))
nextOffset() ->
LedgerEntry.Transaction(
@ -600,6 +612,7 @@ private[dao] trait JdbcLedgerDaoSuite extends JdbcLedgerDaoBackend {
exerciseResult = Some(ValueUnit),
key = maybeKey.map(k => KeyWithMaintainers(ValueText(k), Set(party))),
byKey = false,
version = TransactionVersions.minVersion,
))
nextOffset() -> LedgerEntry.Transaction(
commandId = Some(UUID.randomUUID().toString),
@ -627,6 +640,7 @@ private[dao] trait JdbcLedgerDaoSuite extends JdbcLedgerDaoBackend {
None,
KeyWithMaintainers(ValueText(key), Set(party)),
result,
version = TransactionVersions.minVersion,
))
nextOffset() -> LedgerEntry.Transaction(
commandId = Some(UUID.randomUUID().toString),
@ -656,6 +670,7 @@ private[dao] trait JdbcLedgerDaoSuite extends JdbcLedgerDaoBackend {
stakeholders = Set(party),
None,
byKey = false,
version = TransactionVersions.minVersion,
))
nextOffset() -> LedgerEntry.Transaction(
commandId = Some(UUID.randomUUID().toString),

View File

@ -115,7 +115,7 @@ final class PostCommitValidationSpec extends AnyWordSpec with Matchers {
val error =
store.validate(
transaction = TxBuilder.justCommitted(createContract, TxBuilder.fetch(createContract)),
transaction = TxBuilder.justCommitted(createContract, txBuilder.fetch(createContract)),
transactionLedgerEffectiveTime = Instant.now(),
divulged = Set.empty,
)
@ -130,7 +130,7 @@ final class PostCommitValidationSpec extends AnyWordSpec with Matchers {
val error =
store.validate(
transaction = TxBuilder.justCommitted(TxBuilder.fetch(divulgedContract)),
transaction = TxBuilder.justCommitted(txBuilder.fetch(divulgedContract)),
transactionLedgerEffectiveTime = Instant.now(),
divulged = Set(divulgedContract.coid),
)
@ -145,7 +145,7 @@ final class PostCommitValidationSpec extends AnyWordSpec with Matchers {
val error =
store.validate(
transaction = TxBuilder.justCommitted(TxBuilder.fetch(missingCreate)),
transaction = TxBuilder.justCommitted(txBuilder.fetch(missingCreate)),
transactionLedgerEffectiveTime = Instant.now(),
divulged = Set.empty,
)
@ -161,7 +161,7 @@ final class PostCommitValidationSpec extends AnyWordSpec with Matchers {
val error =
store.validate(
transaction = TxBuilder
.justCommitted(createContract, TxBuilder.lookupByKey(createContract, found = true)),
.justCommitted(createContract, txBuilder.lookupByKey(createContract, found = true)),
transactionLedgerEffectiveTime = Instant.now(),
divulged = Set.empty,
)
@ -177,7 +177,7 @@ final class PostCommitValidationSpec extends AnyWordSpec with Matchers {
val error =
store.validate(
transaction =
TxBuilder.justCommitted(TxBuilder.lookupByKey(missingCreate, found = true)),
TxBuilder.justCommitted(txBuilder.lookupByKey(missingCreate, found = true)),
transactionLedgerEffectiveTime = Instant.now(),
divulged = Set.empty,
)
@ -198,7 +198,7 @@ final class PostCommitValidationSpec extends AnyWordSpec with Matchers {
val error =
store.validate(
transaction =
TxBuilder.justCommitted(TxBuilder.lookupByKey(missingContract, found = false)),
TxBuilder.justCommitted(txBuilder.lookupByKey(missingContract, found = false)),
transactionLedgerEffectiveTime = Instant.now(),
divulged = Set.empty,
)
@ -276,7 +276,7 @@ final class PostCommitValidationSpec extends AnyWordSpec with Matchers {
val error =
store.validate(
transaction = TxBuilder.justCommitted(TxBuilder.fetch(committedContract)),
transaction = TxBuilder.justCommitted(txBuilder.fetch(committedContract)),
transactionLedgerEffectiveTime = committedContractLedgerEffectiveTime,
divulged = Set.empty,
)
@ -289,7 +289,7 @@ final class PostCommitValidationSpec extends AnyWordSpec with Matchers {
val error =
store.validate(
transaction = TxBuilder.justCommitted(TxBuilder.fetch(committedContract)),
transaction = TxBuilder.justCommitted(txBuilder.fetch(committedContract)),
transactionLedgerEffectiveTime = committedContractLedgerEffectiveTime.minusNanos(1),
divulged = Set.empty,
)
@ -308,7 +308,7 @@ final class PostCommitValidationSpec extends AnyWordSpec with Matchers {
val error =
store.validate(
transaction =
TxBuilder.justCommitted(TxBuilder.lookupByKey(committedContract, found = true)),
TxBuilder.justCommitted(txBuilder.lookupByKey(committedContract, found = true)),
transactionLedgerEffectiveTime = committedContractLedgerEffectiveTime,
divulged = Set.empty,
)
@ -322,7 +322,7 @@ final class PostCommitValidationSpec extends AnyWordSpec with Matchers {
val error =
store.validate(
transaction =
TxBuilder.justCommitted(TxBuilder.lookupByKey(committedContract, found = false)),
TxBuilder.justCommitted(txBuilder.lookupByKey(committedContract, found = false)),
transactionLedgerEffectiveTime = committedContractLedgerEffectiveTime,
divulged = Set.empty,
)
@ -368,7 +368,7 @@ final class PostCommitValidationSpec extends AnyWordSpec with Matchers {
val error =
store.validate(
transaction = TxBuilder.justCommitted(TxBuilder.fetch(divulgedContract)),
transaction = TxBuilder.justCommitted(txBuilder.fetch(divulgedContract)),
transactionLedgerEffectiveTime = Instant.now(),
divulged = Set.empty,
)
@ -402,8 +402,10 @@ final class PostCommitValidationSpec extends AnyWordSpec with Matchers {
object PostCommitValidationSpec {
val txBuilder = new TxBuilder()
private def genTestCreate(): TxBuilder.Create =
TxBuilder.create(
txBuilder.create(
id = s"#${UUID.randomUUID}",
template = "foo:bar:baz",
argument = TxBuilder.record("field" -> "value"),
@ -413,7 +415,7 @@ object PostCommitValidationSpec {
)
private def genTestExercise(create: TxBuilder.Create): TxBuilder.Exercise =
TxBuilder.exercise(
txBuilder.exercise(
contract = create,
choice = "SomeChoice",
consuming = true,

View File

@ -272,13 +272,18 @@ private[kvutils] class TransactionCommitter(
recordedTemplateId,
recordedOptLocation @ _,
recordedKey,
recordedResult),
recordedResult,
recordedVersion,
),
Node.NodeLookupByKey(
replayedTemplateId,
replayedOptLocation @ _,
replayedKey,
replayedResult))
if recordedTemplateId == replayedTemplateId && recordedKey == replayedKey
replayedResult,
replayedVersion,
))
if recordedVersion == replayedVersion &&
recordedTemplateId == replayedTemplateId && recordedKey == replayedKey
&& !resultIsCreatedInTx(recordedTx, recordedResult)
&& !resultIsCreatedInTx(replayedTx, replayedResult) =>
RejectionReason.Inconsistent(validationError.msg)

View File

@ -8,7 +8,7 @@ import com.daml.lf.data.{BackStack, ImmArray}
import com.daml.lf.engine.Blinding
import com.daml.lf.transaction.Transaction.Transaction
import com.daml.lf.transaction.test.TransactionBuilder
import com.daml.lf.transaction.Node
import com.daml.lf.transaction.{Node, TransactionVersions}
import com.daml.lf.value.Value.{ContractId, ContractInst, ValueText}
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
@ -28,7 +28,8 @@ class ProjectionsSpec extends AnyWordSpec with Matchers {
optLocation = None,
signatories = signatories,
stakeholders = stakeholders,
key = None
key = None,
version = TransactionVersions.minVersion,
)
def makeExeNode(
@ -54,6 +55,7 @@ class ProjectionsSpec extends AnyWordSpec with Matchers {
exerciseResult = None,
key = None,
byKey = false,
version = TransactionVersions.minVersion,
)
def project(tx: Transaction) = {

View File

@ -36,6 +36,7 @@ import org.scalatest.Inspectors.forEvery
import scala.collection.JavaConverters._
class TransactionCommitterSpec extends AnyWordSpec with Matchers with MockitoSugar {
private[this] val txBuilder = TransactionBuilder()
private val metrics = new Metrics(new MetricRegistry)
private val aDamlTransactionEntry = DamlTransactionEntry.newBuilder
.setTransaction(Conversions.encodeTransaction(TransactionBuilder.Empty))
@ -352,7 +353,7 @@ class TransactionCommitterSpec extends AnyWordSpec with Matchers with MockitoSug
val dummyValue = TransactionBuilder.record("field" -> "value")
def create(contractId: String, key: String = "key"): TransactionBuilder.Create =
TransactionBuilder.create(
txBuilder.create(
id = contractId,
template = "dummyPackage:DummyModule:DummyTemplate",
argument = dummyValue,
@ -386,7 +387,7 @@ class TransactionCommitterSpec extends AnyWordSpec with Matchers with MockitoSug
val create1 = create("#someContractId")
val create2 = create("#otherContractId")
val exercise = TransactionBuilder.exercise(
val exercise = txBuilder.exercise(
contract = createInput,
choice = "DummyChoice",
consuming = false,
@ -398,7 +399,7 @@ class TransactionCommitterSpec extends AnyWordSpec with Matchers with MockitoSug
val lookupNodes @ Seq(lookup1, lookup2, lookupNone, lookupOther @ _) =
Seq(create1 -> true, create2 -> true, create1 -> false, otherKeyCreate -> true) map {
case (create, found) => TransactionBuilder.lookupByKey(create, found)
case (create, found) => txBuilder.lookupByKey(create, found)
}
val Seq(tx1, tx2, txNone, txOther) = lookupNodes map { node =>
val builder = TransactionBuilder()