DAML-LF: remove seed from node, move it to transaction meta data. (#5570)

* DAML-LF: remove seed from node, move it to transaction meta data.

+ redesign seeding to validate partial transaction.

CHANGELOG_BEGIN
CHANGELOG_END
This commit is contained in:
Remy 2020-04-17 11:02:11 +02:00 committed by GitHub
parent 7067fa432e
commit a178e3e3e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 379 additions and 301 deletions

View File

@ -8,14 +8,13 @@ import com.daml.lf.command._
import com.daml.lf.data._ import com.daml.lf.data._
import com.daml.lf.data.Ref.{PackageId, ParticipantId, Party} import com.daml.lf.data.Ref.{PackageId, ParticipantId, Party}
import com.daml.lf.language.Ast._ import com.daml.lf.language.Ast._
import com.daml.lf.speedy.Compiler import com.daml.lf.speedy.{Compiler, InitialSeeding, Pretty, Command => SpeedyCommand}
import com.daml.lf.speedy.Pretty
import com.daml.lf.speedy.Speedy.Machine import com.daml.lf.speedy.Speedy.Machine
import com.daml.lf.speedy.SResult._ import com.daml.lf.speedy.SResult._
import com.daml.lf.transaction.Transaction import com.daml.lf.transaction.{Transaction => Tx}
import com.daml.lf.transaction.Node._ import com.daml.lf.transaction.Node._
import com.daml.lf.transaction.Transaction.{NodeId, Transaction}
import com.daml.lf.value.Value import com.daml.lf.value.Value
import com.daml.lf.speedy.{Command => SpeedyCommand}
/** /**
* Allows for evaluating [[Commands]] and validating [[Transaction]]s. * Allows for evaluating [[Commands]] and validating [[Transaction]]s.
@ -48,8 +47,8 @@ import com.daml.lf.speedy.{Command => SpeedyCommand}
* This class is thread safe as long `nextRandomInt` is. * This class is thread safe as long `nextRandomInt` is.
*/ */
final class Engine { final class Engine {
private[this] val _compiledPackages = ConcurrentCompiledPackages() private[this] val compiledPackages = ConcurrentCompiledPackages()
private[this] val _preprocessor = new preprocessing.Preprocessor(_compiledPackages) private[this] val preprocessor = new preprocessing.Preprocessor(compiledPackages)
/** /**
* Executes commands `cmds` under the authority of `cmds.submitter` and returns one of the following: * Executes commands `cmds` under the authority of `cmds.submitter` and returns one of the following:
@ -82,12 +81,12 @@ final class Engine {
cmds: Commands, cmds: Commands,
participantId: ParticipantId, participantId: ParticipantId,
submissionSeed: Option[crypto.Hash], submissionSeed: Option[crypto.Hash],
): Result[(Transaction.Transaction, Transaction.Metadata)] = { ): Result[(Tx.Transaction, Tx.Metadata)] = {
val submissionTime = cmds.ledgerEffectiveTime val submissionTime = cmds.ledgerEffectiveTime
_preprocessor preprocessor
.preprocessCommands(cmds.commands) .preprocessCommands(cmds.commands)
.flatMap { processedCmds => .flatMap { processedCmds =>
ShouldCheckSubmitterInMaintainers(_compiledPackages, cmds).flatMap { ShouldCheckSubmitterInMaintainers(compiledPackages, cmds).flatMap {
checkSubmitterInMaintainers => checkSubmitterInMaintainers =>
interpretCommands( interpretCommands(
validating = false, validating = false,
@ -96,10 +95,9 @@ final class Engine {
commands = processedCmds, commands = processedCmds,
ledgerTime = cmds.ledgerEffectiveTime, ledgerTime = cmds.ledgerEffectiveTime,
submissionTime = submissionTime, submissionTime = submissionTime,
transactionSeed = submissionSeed.map( seeding = Engine.initialSeeding(submissionSeed, participantId, submissionTime),
crypto.Hash.deriveTransactionSeed(_, participantId, submissionTime)),
) map { ) map {
case (tx, dependsOnTime) => case (tx, dependsOnTime, nodeSeeds) =>
// Annotate the transaction with the package dependencies. Since // Annotate the transaction with the package dependencies. Since
// all commands are actions on a contract template, with a fully typed // all commands are actions on a contract template, with a fully typed
// argument, we only need to consider the templates mentioned in the command // argument, we only need to consider the templates mentioned in the command
@ -107,16 +105,18 @@ final class Engine {
val deps = processedCmds.foldLeft(Set.empty[PackageId]) { (pkgIds, cmd) => val deps = processedCmds.foldLeft(Set.empty[PackageId]) { (pkgIds, cmd) =>
val pkgId = cmd.templateId.packageId val pkgId = cmd.templateId.packageId
val transitiveDeps = val transitiveDeps =
_compiledPackages compiledPackages
.getPackageDependencies(pkgId) .getPackageDependencies(pkgId)
.getOrElse( .getOrElse(
sys.error(s"INTERNAL ERROR: Missing dependencies of package $pkgId")) sys.error(s"INTERNAL ERROR: Missing dependencies of package $pkgId"))
(pkgIds + pkgId) union transitiveDeps (pkgIds + pkgId) union transitiveDeps
} }
tx -> Transaction.Metadata( tx -> Tx.Metadata(
submissionTime = submissionTime, submissionSeed,
usedPackages = deps, submissionTime,
dependsOnTime = dependsOnTime, deps,
dependsOnTime,
nodeSeeds,
) )
} }
} }
@ -124,47 +124,35 @@ final class Engine {
} }
/** /**
* Behaves like `submit`, but it takes GenNode arguments instead of a Commands argument. * Behaves like `submit`, but it takes a GenNode argument instead of a Commands argument.
* That is, it can be used to reinterpret an already interpreted transaction (since it consists of GenNodes). * That is, it can be used to reinterpret partially an already interpreted transaction (since it consists of GenNodes).
* Formally, the following is guaranteed to hold for all pcs, pkgs, and keys, when evaluated on the same Engine:
* evaluate(submit(cmds)) = ResultDone(tx) ==> evaluate(reinterpret(cmds.submitters, txRoots, cmds.ledgerEffectiveTime)) === ResultDone(tx)
* where:
* evaluate(result) = result.consume(pcs, pkgs, keys)
* txRoots = tx.roots.map(id => tx.nodes.get(id).get).toSeq
* tx === tx' if tx and tx' are equivalent modulo a renaming of node and relative contract IDs
* *
* Moreover, if the transaction tx is valid at time leTime, n belongs to tx.nodes, and subtx is the subtransaction of
* tx rooted at n, the following holds:
* evaluate(reinterpret(n.requiredAuthorizers, Seq(n), leTime) === subtx
* *
* In addition to the errors returned by `submit`, reinterpretation fails with a `ValidationError` whenever `nodes` * [[nodeSeed]] is the seed of the Create and Exercise node as generated during submission.
* contain a relative contract ID, either as the target contract of a fetch, or as an argument to a * If undefined the contract IDs are derive using V0 scheme.
* create or an exercise choice. * The value of [[nodeSeed]] does not matter for other kind of nodes.
*
* [[transactionSeed]] is the master hash te be used to derive node and contractId discriminator.
* If let undefined, no discriminator will be generated.
*/ */
def reinterpret( def reinterpret(
submissionTime: Time.Timestamp,
transactionSeed: Option[crypto.Hash],
submitters: Set[Party], submitters: Set[Party],
nodes: Seq[GenNode.WithTxValue[Value.NodeId, Value.ContractId]], node: GenNode.WithTxValue[Value.NodeId, Value.ContractId],
nodeSeed: Option[crypto.Hash],
submissionTime: Time.Timestamp,
ledgerEffectiveTime: Time.Timestamp, ledgerEffectiveTime: Time.Timestamp,
): Result[(Transaction.Transaction, Boolean)] = ): Result[(Tx.Transaction, Boolean, ImmArray[(NodeId, crypto.Hash)])] =
for { for {
commands <- Result.sequence(ImmArray(nodes).map(_preprocessor.translateNode)) command <- preprocessor.translateNode(node)
checkSubmitterInMaintainers <- ShouldCheckSubmitterInMaintainers( checkSubmitterInMaintainers <- ShouldCheckSubmitterInMaintainers(
_compiledPackages, compiledPackages,
commands.map(_.templateId)) ImmArray(command.templateId))
// reinterpret is never used for submission, only for validation. // reinterpret is never used for submission, only for validation.
result <- interpretCommands( result <- interpretCommands(
validating = true, validating = true,
checkSubmitterInMaintainers = checkSubmitterInMaintainers, checkSubmitterInMaintainers = checkSubmitterInMaintainers,
submitters = submitters, submitters = submitters,
commands = commands, commands = ImmArray(command),
ledgerTime = ledgerEffectiveTime, ledgerTime = ledgerEffectiveTime,
submissionTime, submissionTime = submissionTime,
transactionSeed, seeding = InitialSeeding.RootNodeSeeds(ImmArray(nodeSeed)),
) )
} yield result } yield result
@ -184,7 +172,7 @@ final class Engine {
* @param ledgerEffectiveTime time when the transaction is claimed to be submitted * @param ledgerEffectiveTime time when the transaction is claimed to be submitted
*/ */
def validate( def validate(
tx: Transaction.Transaction, tx: Tx.Transaction,
ledgerEffectiveTime: Time.Timestamp, ledgerEffectiveTime: Time.Timestamp,
participantId: Ref.ParticipantId, participantId: Ref.ParticipantId,
submissionTime: Time.Timestamp, submissionTime: Time.Timestamp,
@ -217,9 +205,9 @@ final class Engine {
// For empty transactions, use an empty set of submitters // For empty transactions, use an empty set of submitters
submitters = submittersOpt.getOrElse(Set.empty) submitters = submittersOpt.getOrElse(Set.empty)
commands <- _preprocessor.translateTransactionRoots(tx) commands <- preprocessor.translateTransactionRoots(tx)
checkSubmitterInMaintainers <- ShouldCheckSubmitterInMaintainers( checkSubmitterInMaintainers <- ShouldCheckSubmitterInMaintainers(
_compiledPackages, compiledPackages,
commands.map(_._2.templateId)) commands.map(_._2.templateId))
result <- interpretCommands( result <- interpretCommands(
validating = true, validating = true,
@ -227,10 +215,10 @@ final class Engine {
submitters = submitters, submitters = submitters,
commands = commands.map(_._2), commands = commands.map(_._2),
ledgerTime = ledgerEffectiveTime, ledgerTime = ledgerEffectiveTime,
submissionTime, submissionTime = submissionTime,
submissionSeed.map(crypto.Hash.deriveTransactionSeed(_, participantId, submissionTime)) seeding = Engine.initialSeeding(submissionSeed, participantId, submissionTime),
) )
(rtx, _) = result (rtx, _, _) = result
validationResult <- if (tx isReplayedBy rtx) { validationResult <- if (tx isReplayedBy rtx) {
ResultDone(()) ResultDone(())
} else { } else {
@ -246,8 +234,7 @@ final class Engine {
* Submitters are a set, in order to support interpreting subtransactions * Submitters are a set, in order to support interpreting subtransactions
* (a subtransaction can be authorized by multiple parties). * (a subtransaction can be authorized by multiple parties).
* *
* [[transactionSeed]] is the master hash used to derive node and contractId discriminator. * [[seeding]] is seeding used to derive node seed and contractId discriminator.
* If let undefined, no discriminator will be generated.
*/ */
private[engine] def interpretCommands( private[engine] def interpretCommands(
validating: Boolean, validating: Boolean,
@ -257,15 +244,15 @@ final class Engine {
commands: ImmArray[SpeedyCommand], commands: ImmArray[SpeedyCommand],
ledgerTime: Time.Timestamp, ledgerTime: Time.Timestamp,
submissionTime: Time.Timestamp, submissionTime: Time.Timestamp,
transactionSeed: Option[crypto.Hash], seeding: speedy.InitialSeeding,
): Result[(Transaction.Transaction, Boolean)] = { ): Result[(Transaction, Boolean, ImmArray[(Tx.NodeId, crypto.Hash)])] = {
val machine = Machine val machine = Machine
.build( .build(
checkSubmitterInMaintainers = checkSubmitterInMaintainers, checkSubmitterInMaintainers = checkSubmitterInMaintainers,
sexpr = Compiler(compiledPackages.packages).compile(commands), sexpr = Compiler(compiledPackages.packages).compile(commands),
compiledPackages = _compiledPackages, compiledPackages = compiledPackages,
submissionTime, submissionTime = submissionTime,
speedy.InitialSeeding(transactionSeed) seeds = seeding,
) )
.copy(validating = validating, committers = submitters) .copy(validating = validating, committers = submitters)
interpretLoop(machine, ledgerTime) interpretLoop(machine, ledgerTime)
@ -276,7 +263,7 @@ final class Engine {
private[engine] def interpretLoop( private[engine] def interpretLoop(
machine: Machine, machine: Machine,
time: Time.Timestamp time: Time.Timestamp
): Result[(Transaction.Transaction, Boolean)] = { ): Result[(Tx.Transaction, Boolean, ImmArray[(Tx.NodeId, crypto.Hash)])] = {
while (!machine.isFinal) { while (!machine.isFinal) {
machine.step() match { machine.step() match {
case SResultContinue => case SResultContinue =>
@ -293,9 +280,9 @@ final class Engine {
return Result.needPackage( return Result.needPackage(
pkgId, pkgId,
pkg => { pkg => {
_compiledPackages.addPackage(pkgId, pkg).flatMap { compiledPackages.addPackage(pkgId, pkg).flatMap {
case _ => case _ =>
callback(_compiledPackages) callback(compiledPackages)
interpretLoop(machine, time) interpretLoop(machine, time)
} }
} }
@ -345,17 +332,17 @@ final class Engine {
machine.ptx.finish match { machine.ptx.finish match {
case Left(p) => case Left(p) =>
ResultError(Error(s"Interpretation error: ended with partial result: $p")) ResultError(Error(s"Interpretation error: ended with partial result: $p"))
case Right(t) => ResultDone(t -> machine.dependsOnTime) case Right(t) => ResultDone((t, machine.dependsOnTime, machine.ptx.nodeSeeds.toImmArray))
} }
} }
def clearPackages(): Unit = _compiledPackages.clear() def clearPackages(): Unit = compiledPackages.clear()
/** Note: it's important we return a [[com.daml.lf.CompiledPackages]], /** Note: it's important we return a [[com.daml.lf.CompiledPackages]],
* and not a [[ConcurrentCompiledPackages]], otherwise people would be able * and not a [[ConcurrentCompiledPackages]], otherwise people would be able
* to modify them. * to modify them.
*/ */
def compiledPackages(): CompiledPackages = _compiledPackages def compiledPackages(): CompiledPackages = compiledPackages
/** This function can be used to give a package to the engine pre-emptively, /** This function can be used to give a package to the engine pre-emptively,
* rather than having the engine to ask about it through * rather than having the engine to ask about it through
@ -365,9 +352,22 @@ final class Engine {
* be loaded. * be loaded.
*/ */
def preloadPackage(pkgId: PackageId, pkg: Package): Result[Unit] = def preloadPackage(pkgId: PackageId, pkg: Package): Result[Unit] =
_compiledPackages.addPackage(pkgId, pkg) compiledPackages.addPackage(pkgId, pkg)
} }
object Engine { object Engine {
def apply(): Engine = new Engine() def apply(): Engine = new Engine()
def initialSeeding(
submissionSeed: Option[crypto.Hash],
participant: Ref.ParticipantId,
submissionTime: Time.Timestamp,
): InitialSeeding =
submissionSeed match {
case None =>
InitialSeeding.NoSeed
case Some(seed) =>
InitialSeeding.TransactionSeed(
crypto.Hash.deriveTransactionSeed(seed, participant, submissionTime))
}
} }

View File

@ -145,17 +145,9 @@ private[engine] final class Preprocessor(compiledPackages: MutableCompiledPackag
private def getTemplateId(node: Node.GenNode.WithTxValue[Transaction.NodeId, _]) = private def getTemplateId(node: Node.GenNode.WithTxValue[Transaction.NodeId, _]) =
node match { node match {
case Node.NodeCreate( case Node.NodeCreate(coid @ _, coinst, optLoc @ _, sigs @ _, stks @ _, key @ _) =>
nodeSeed @ _,
coid @ _,
coinst,
optLoc @ _,
sigs @ _,
stks @ _,
key @ _) =>
coinst.template coinst.template
case Node.NodeExercises( case Node.NodeExercises(
nodeSeed @ _,
coid @ _, coid @ _,
templateId, templateId,
choice @ _, choice @ _,

View File

@ -48,20 +48,12 @@ private[preprocessing] final class TransactionPreprocessor(
): speedy.Command = { ): speedy.Command = {
node match { node match {
case Node.NodeCreate( case Node.NodeCreate(coid @ _, coinst, optLoc @ _, sigs @ _, stks @ _, key @ _) =>
nodeSeed @ _,
coid @ _,
coinst,
optLoc @ _,
sigs @ _,
stks @ _,
key @ _) =>
val identifier = coinst.template val identifier = coinst.template
val arg = unsafeAsValueWithAbsoluteContractIds(coinst.arg.value) val arg = unsafeAsValueWithAbsoluteContractIds(coinst.arg.value)
commandPreprocessor.unsafePreprocessCreate(identifier, arg) commandPreprocessor.unsafePreprocessCreate(identifier, arg)
case Node.NodeExercises( case Node.NodeExercises(
nodeSeed @ _,
coid, coid,
template, template,
choice, choice,

View File

@ -17,7 +17,7 @@ import com.daml.lf.transaction.Node._
import com.daml.lf.transaction.{GenTransaction => GenTx, Transaction => Tx} import com.daml.lf.transaction.{GenTransaction => GenTx, Transaction => Tx}
import com.daml.lf.value.Value import com.daml.lf.value.Value
import Value._ import Value._
import com.daml.lf.speedy.{SValue, svalue} import com.daml.lf.speedy.{InitialSeeding, SValue, svalue}
import com.daml.lf.speedy.SValue._ import com.daml.lf.speedy.SValue._
import com.daml.lf.command._ import com.daml.lf.command._
import com.daml.lf.value.ValueVersions.assertAsVersionedValue import com.daml.lf.value.ValueVersions.assertAsVersionedValue
@ -61,36 +61,6 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
private val (basicTestsPkgId, basicTestsPkg, allPackages) = loadPackage( private val (basicTestsPkgId, basicTestsPkg, allPackages) = loadPackage(
"daml-lf/tests/BasicTests.dar") "daml-lf/tests/BasicTests.dar")
def lookupContract(@deprecated("shut up unused arguments warning", "blah") id: AbsoluteContractId)
: Option[ContractInst[Tx.Value[AbsoluteContractId]]] = {
Some(
ContractInst(
TypeConName(basicTestsPkgId, "BasicTests:Simple"),
assertAsVersionedValue(
ValueRecord(
Some(Identifier(basicTestsPkgId, "BasicTests:Simple")),
ImmArray((Some[Name]("p"), ValueParty(party))))),
""
))
}
def lookupContractForPayout(
@deprecated("shut up unused arguments warning", "blah") id: AbsoluteContractId)
: Option[ContractInst[Tx.Value[AbsoluteContractId]]] = {
Some(
ContractInst(
TypeConName(basicTestsPkgId, "BasicTests:CallablePayout"),
assertAsVersionedValue(
ValueRecord(
Some(Identifier(basicTestsPkgId, "BasicTests:CallablePayout")),
ImmArray(
(Some("giver"), ValueParty(alice)),
(Some("receiver"), ValueParty(bob))
))),
""
))
}
val withKeyTemplate = "BasicTests:WithKey" val withKeyTemplate = "BasicTests:WithKey"
val BasicTests_WithKey = Identifier(basicTestsPkgId, withKeyTemplate) val BasicTests_WithKey = Identifier(basicTestsPkgId, withKeyTemplate)
val withKeyContractInst: ContractInst[Tx.Value[AbsoluteContractId]] = val withKeyContractInst: ContractInst[Tx.Value[AbsoluteContractId]] =
@ -106,11 +76,35 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
"" ""
) )
def lookupContractWithKey( val defaultContracts =
@deprecated("shut up unused arguments warning", "blah") id: AbsoluteContractId) Map(
: Option[ContractInst[Tx.Value[AbsoluteContractId]]] = { toContractId("#BasicTests:Simple:1") ->
Some(withKeyContractInst) ContractInst(
} TypeConName(basicTestsPkgId, "BasicTests:Simple"),
assertAsVersionedValue(
ValueRecord(
Some(Identifier(basicTestsPkgId, "BasicTests:Simple")),
ImmArray((Some[Name]("p"), ValueParty(party))))),
""
),
toContractId("#BasicTests:CallablePayout:1") ->
ContractInst(
TypeConName(basicTestsPkgId, "BasicTests:CallablePayout"),
assertAsVersionedValue(
ValueRecord(
Some(Identifier(basicTestsPkgId, "BasicTests:CallablePayout")),
ImmArray(
(Some[Ref.Name]("giver"), ValueParty(alice)),
(Some[Ref.Name]("receiver"), ValueParty(bob))
)
)),
""
),
toContractId("#BasicTests:WithKey:1") ->
withKeyContractInst
)
val lookupContract = defaultContracts.get(_)
def lookupPackage(pkgId: PackageId): Option[Package] = { def lookupPackage(pkgId: PackageId): Option[Package] = {
allPackages.get(pkgId) allPackages.get(pkgId)
@ -122,7 +116,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
BasicTests_WithKey, BasicTests_WithKey,
ValueRecord(_, ImmArray((_, ValueParty(`alice`)), (_, ValueInt64(42)))), ValueRecord(_, ImmArray((_, ValueParty(`alice`)), (_, ValueInt64(42)))),
) => ) =>
Some(toContractId("#1")) Some(toContractId("#BasicTests:WithKey:1"))
case _ => case _ =>
None None
} }
@ -198,7 +192,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
} }
"translate exercise commands argument including labels" in { "translate exercise commands argument including labels" in {
val originalCoid = toContractId("#1") val originalCoid = toContractId("#BasicTests:CallablePayout:1")
val templateId = Identifier(basicTestsPkgId, "BasicTests:CallablePayout") val templateId = Identifier(basicTestsPkgId, "BasicTests:CallablePayout")
val command = ExerciseCommand( val command = ExerciseCommand(
templateId, templateId,
@ -208,12 +202,12 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val res = preprocessor val res = preprocessor
.preprocessCommands(ImmArray(command)) .preprocessCommands(ImmArray(command))
.consume(lookupContractForPayout, lookupPackage, lookupKey) .consume(lookupContract, lookupPackage, lookupKey)
res shouldBe 'right res shouldBe 'right
} }
"translate exercise commands argument without labels" in { "translate exercise commands argument without labels" in {
val originalCoid = toContractId("#1") val originalCoid = toContractId("#BasicTests:CallablePayout:1")
val templateId = Identifier(basicTestsPkgId, "BasicTests:CallablePayout") val templateId = Identifier(basicTestsPkgId, "BasicTests:CallablePayout")
val command = ExerciseCommand( val command = ExerciseCommand(
templateId, templateId,
@ -223,7 +217,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val res = preprocessor val res = preprocessor
.preprocessCommands(ImmArray(command)) .preprocessCommands(ImmArray(command))
.consume(lookupContractForPayout, lookupPackage, lookupKey) .consume(lookupContract, lookupPackage, lookupKey)
res shouldBe 'right res shouldBe 'right
} }
@ -238,7 +232,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val res = preprocessor val res = preprocessor
.preprocessCommands(ImmArray(command)) .preprocessCommands(ImmArray(command))
.consume(lookupContractForPayout, lookupPackage, lookupKey) .consume(lookupContract, lookupPackage, lookupKey)
res shouldBe 'right res shouldBe 'right
} }
@ -253,7 +247,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val res = preprocessor val res = preprocessor
.preprocessCommands(ImmArray(command)) .preprocessCommands(ImmArray(command))
.consume(lookupContractForPayout, lookupPackage, lookupKey) .consume(lookupContract, lookupPackage, lookupKey)
res shouldBe 'right res shouldBe 'right
} }
@ -268,7 +262,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val res = preprocessor val res = preprocessor
.preprocessCommands(ImmArray(command)) .preprocessCommands(ImmArray(command))
.consume(lookupContractForPayout, lookupPackage, lookupKey) .consume(lookupContract, lookupPackage, lookupKey)
res.left.value.msg should startWith("Missing record label n for record") res.left.value.msg should startWith("Missing record label n for record")
} }
@ -283,7 +277,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val res = preprocessor val res = preprocessor
.preprocessCommands(ImmArray(command)) .preprocessCommands(ImmArray(command))
.consume(lookupContractForPayout, lookupPackage, lookupKey) .consume(lookupContract, lookupPackage, lookupKey)
res.left.value.msg should startWith( res.left.value.msg should startWith(
"Impossible to exercise by key, no key is defined for template") "Impossible to exercise by key, no key is defined for template")
} }
@ -299,7 +293,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val res = preprocessor val res = preprocessor
.preprocessCommands(ImmArray(command)) .preprocessCommands(ImmArray(command))
.consume(lookupContractForPayout, lookupPackage, lookupKey) .consume(lookupContract, lookupPackage, lookupKey)
res.left.value.msg should startWith("mismatching type") res.left.value.msg should startWith("mismatching type")
} }
@ -318,7 +312,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val res = preprocessor val res = preprocessor
.preprocessCommands(ImmArray(command)) .preprocessCommands(ImmArray(command))
.consume(lookupContractForPayout, lookupPackage, lookupKey) .consume(lookupContract, lookupPackage, lookupKey)
res shouldBe 'right res shouldBe 'right
} }
@ -439,14 +433,19 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
"reinterpret to the same result" in { "reinterpret to the same result" in {
val Right((tx, txMeta)) = interpretResult val Right((tx, txMeta)) = interpretResult
val txRoots = tx.roots.map(id => tx.nodes(id)).toSeq val txRoots = tx.roots.map(id => tx.nodes(id))
val txSeed = val nodeSeedMap = txMeta.nodeSeeds.toSeq.toMap
Some(crypto.Hash.deriveTransactionSeed(submissionSeed, participant, txMeta.submissionTime))
val Right((rtx, _)) = val Right((rtx, _, _)) =
engine reinterpret(
.reinterpret(txMeta.submissionTime, txSeed, Set(party), txRoots, let) engine,
.consume(lookupContract, lookupPackage, lookupKey) Set(party),
txRoots,
tx.roots.map(nodeSeedMap.get),
txMeta.submissionTime,
let,
lookupPackage
)
(tx isReplayedBy rtx) shouldBe true (tx isReplayedBy rtx) shouldBe true
} }
@ -474,13 +473,10 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val templateId = Identifier(basicTestsPkgId, "BasicTests:Simple") val templateId = Identifier(basicTestsPkgId, "BasicTests:Simple")
val hello = Identifier(basicTestsPkgId, "BasicTests:Hello") val hello = Identifier(basicTestsPkgId, "BasicTests:Hello")
val let = Time.Timestamp.now() val let = Time.Timestamp.now()
val txSeed = crypto.Hash.deriveTransactionSeed(submissionSeed, participant, let) val seeding = Engine.initialSeeding(Some(submissionSeed), participant, let)
val cid = toContractId("#BasicTests:Simple:1")
val command = val command =
ExerciseCommand( ExerciseCommand(templateId, cid, "Hello", ValueRecord(Some(hello), ImmArray.empty))
templateId,
toContractId("#1"),
"Hello",
ValueRecord(Some(hello), ImmArray.empty))
val res = preprocessor val res = preprocessor
.preprocessCommands(ImmArray(command)) .preprocessCommands(ImmArray(command))
@ -498,10 +494,10 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
commands = r, commands = r,
ledgerTime = let, ledgerTime = let,
submissionTime = let, submissionTime = let,
transactionSeed = Some(txSeed) seeding = seeding,
) )
.consume(lookupContract, lookupPackage, lookupKey)) .consume(lookupContract, lookupPackage, lookupKey))
val Right((tx, _)) = interpretResult val Right((tx, _, nodeSeeds)) = interpretResult
"be translated" in { "be translated" in {
val Right((rtx, _)) = engine val Right((rtx, _)) = engine
@ -511,11 +507,21 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
} }
"reinterpret to the same result" in { "reinterpret to the same result" in {
val txRoots = tx.roots.map(id => tx.nodes(id)).toSeq val nodeSeedMap = HashMap(nodeSeeds.toSeq: _*)
val roots = tx.roots.map(tx.nodes)
val rootSeeds = tx.roots.map(nodeSeedMap.get)
val Right((rtx, _)) = engine val Right((rtx, _, _)) =
.reinterpret(let, Some(txSeed), Set(party), txRoots, let) reinterpret(
.consume(lookupContract, lookupPackage, lookupKey) engine,
Set(party),
roots,
rootSeeds,
let,
let,
lookupPackage,
defaultContracts
)
(tx isReplayedBy rtx) shouldBe true (tx isReplayedBy rtx) shouldBe true
} }
@ -556,13 +562,13 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val res = preprocessor val res = preprocessor
.preprocessCommands(ImmArray(command)) .preprocessCommands(ImmArray(command))
.consume(lookupContractWithKey, lookupPackage, lookupKey) .consume(lookupContract, lookupPackage, lookupKey)
res shouldBe 'right res shouldBe 'right
"fail at submission" in { "fail at submission" in {
val submitResult = engine val submitResult = engine
.submit(Commands(alice, ImmArray(command), let, "test"), participant, Some(submissionSeed)) .submit(Commands(alice, ImmArray(command), let, "test"), participant, Some(submissionSeed))
.consume(lookupContractWithKey, lookupPackage, lookupKey) .consume(lookupContract, lookupPackage, lookupKey)
submitResult.left.value.msg should startWith("dependency error: couldn't find key") submitResult.left.value.msg should startWith("dependency error: couldn't find key")
} }
} }
@ -571,7 +577,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val submissionSeed = hash("exercise-by-key command with existing key") val submissionSeed = hash("exercise-by-key command with existing key")
val templateId = Identifier(basicTestsPkgId, "BasicTests:WithKey") val templateId = Identifier(basicTestsPkgId, "BasicTests:WithKey")
val let = Time.Timestamp.now() val let = Time.Timestamp.now()
val txSeed = crypto.Hash.deriveTransactionSeed(submissionSeed, participant, let) val seeding = Engine.initialSeeding(Some(submissionSeed), participant, let)
val command = ExerciseByKeyCommand( val command = ExerciseByKeyCommand(
templateId, templateId,
ValueRecord(None, ImmArray((None, ValueParty(alice)), (None, ValueInt64(42)))), ValueRecord(None, ImmArray((None, ValueParty(alice)), (None, ValueInt64(42)))),
@ -581,7 +587,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val res = preprocessor val res = preprocessor
.preprocessCommands(ImmArray(command)) .preprocessCommands(ImmArray(command))
.consume(lookupContractWithKey, lookupPackage, lookupKey) .consume(lookupContract, lookupPackage, lookupKey)
res shouldBe 'right res shouldBe 'right
val result = val result =
res res
@ -595,34 +601,34 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
commands = r, commands = r,
ledgerTime = let, ledgerTime = let,
submissionTime = let, submissionTime = let,
transactionSeed = Some(txSeed) seeding = seeding,
) )
.consume(lookupContractWithKey, lookupPackage, lookupKey)) .consume(lookupContract, lookupPackage, lookupKey))
.map(_._1) val Right((tx, _, nodeSeeds)) = result
val tx = result.right.value
"be translated" in { "be translated" in {
val submitResult = engine val submitResult = engine
.submit(Commands(alice, ImmArray(command), let, "test"), participant, Some(submissionSeed)) .submit(Commands(alice, ImmArray(command), let, "test"), participant, Some(submissionSeed))
.consume(lookupContractWithKey, lookupPackage, lookupKey) .consume(lookupContract, lookupPackage, lookupKey)
.map(_._1) .map(_._1)
(result |@| submitResult)(_ isReplayedBy _) shouldBe Right(true) (result.map(_._1) |@| submitResult)(_ isReplayedBy _) shouldBe Right(true)
} }
"reinterpret to the same result" in { "reinterpret to the same result" in {
val txRoots = tx.roots.map(id => tx.nodes(id)).toSeq val roots = tx.roots.map(id => tx.nodes(id))
val nodeSeedMap = HashMap(nodeSeeds.toSeq: _*)
val rootSeeds = tx.roots.map(nodeSeedMap.get)
val reinterpretResult = val reinterpretResult =
engine reinterpret(engine, Set(alice), roots, rootSeeds, let, let, lookupPackage, defaultContracts)
.reinterpret(let, Some(txSeed), Set(alice), txRoots, let)
.consume(lookupContractWithKey, lookupPackage, lookupKey)
.map(_._1) .map(_._1)
(result |@| reinterpretResult)(_ isReplayedBy _) shouldBe Right(true) (result.map(_._1) |@| reinterpretResult)(_ isReplayedBy _) shouldBe Right(true)
} }
"be validated" in { "be validated" in {
val validated = engine val validated = engine
.validate(tx, let, participant, let, Some(submissionSeed)) .validate(tx, let, participant, let, Some(submissionSeed))
.consume(lookupContractWithKey, lookupPackage, lookupKey) .consume(lookupContract, lookupPackage, lookupKey)
validated match { validated match {
case Left(e) => case Left(e) =>
fail(e.msg) fail(e.msg)
@ -673,11 +679,11 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
commands = r, commands = r,
ledgerTime = let, ledgerTime = let,
submissionTime = let, submissionTime = let,
transactionSeed = Some(txSeed), seeding = InitialSeeding.TransactionSeed(txSeed),
) )
.consume(lookupContract, lookupPackage, lookupKey)) .consume(lookupContract, lookupPackage, lookupKey))
.map(_._1)
val Right(tx) = interpretResult val Right((tx, _, nodeSeeds)) = interpretResult
"be translated" in { "be translated" in {
tx.roots should have length 2 tx.roots should have length 2
@ -688,13 +694,14 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
} }
"reinterpret to the same result" in { "reinterpret to the same result" in {
val txRoots = tx.roots.map(id => tx.nodes(id)).toSeq val nodeSeedMap = HashMap(nodeSeeds.toSeq: _*)
val roots = tx.roots.map(tx.nodes)
val rootSeeds = tx.roots.map(nodeSeedMap.get)
val reinterpretResult = val reinterpretResult =
engine reinterpret(engine, Set(party), roots, rootSeeds, let, let, lookupPackage)
.reinterpret(let, Some(txSeed), Set(party), txRoots, let)
.consume(lookupContract, lookupPackage, lookupKey)
.map(_._1) .map(_._1)
(interpretResult |@| reinterpretResult)(_ isReplayedBy _) shouldBe Right(true) (interpretResult.map(_._1) |@| reinterpretResult)(_ isReplayedBy _) shouldBe Right(true)
} }
"be validated" in { "be validated" in {
@ -873,7 +880,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
"exercise callable command" should { "exercise callable command" should {
val submissionSeed = hash("exercise callable command") val submissionSeed = hash("exercise callable command")
val originalCoid = toContractId("#1") val originalCoid = toContractId("#BasicTests:CallablePayout:1")
val templateId = Identifier(basicTestsPkgId, "BasicTests:CallablePayout") val templateId = Identifier(basicTestsPkgId, "BasicTests:CallablePayout")
// we need to fix time as cid are depending on it // we need to fix time as cid are depending on it
val let = Time.Timestamp.assertFromString("1969-07-20T20:17:00Z") val let = Time.Timestamp.assertFromString("1969-07-20T20:17:00Z")
@ -883,18 +890,18 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
"Transfer", "Transfer",
ValueRecord(None, ImmArray((Some[Name]("newReceiver"), ValueParty(clara))))) ValueRecord(None, ImmArray((Some[Name]("newReceiver"), ValueParty(clara)))))
val Right((tx, meta)) = engine val Right((tx, txMeta)) = engine
.submit(Commands(bob, ImmArray(command), let, "test"), participant, Some(submissionSeed)) .submit(Commands(bob, ImmArray(command), let, "test"), participant, Some(submissionSeed))
.consume(lookupContractForPayout, lookupPackage, lookupKey) .consume(lookupContract, lookupPackage, lookupKey)
val submissionTime = meta.submissionTime val submissionTime = txMeta.submissionTime
val txSeed = val txSeed =
crypto.Hash.deriveTransactionSeed(submissionSeed, participant, submissionTime) crypto.Hash.deriveTransactionSeed(submissionSeed, participant, submissionTime)
val Right(cmds) = preprocessor val Right(cmds) = preprocessor
.preprocessCommands(ImmArray(command)) .preprocessCommands(ImmArray(command))
.consume(lookupContractForPayout, lookupPackage, lookupKey) .consume(lookupContract, lookupPackage, lookupKey)
val Right((rtx, _)) = engine val Right((rtx, _, _)) = engine
.interpretCommands( .interpretCommands(
validating = false, validating = false,
checkSubmitterInMaintainers = true, checkSubmitterInMaintainers = true,
@ -902,9 +909,9 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
commands = cmds, commands = cmds,
ledgerTime = let, ledgerTime = let,
submissionTime = submissionTime, submissionTime = submissionTime,
transactionSeed = Some(txSeed), seeding = InitialSeeding.TransactionSeed(txSeed)
) )
.consume(lookupContractForPayout, lookupPackage, lookupKey) .consume(lookupContract, lookupPackage, lookupKey)
"be translated" in { "be translated" in {
(rtx isReplayedBy tx) shouldBe true (rtx isReplayedBy tx) shouldBe true
@ -914,11 +921,20 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
Blinding.checkAuthorizationAndBlind(tx, Set(bob)) Blinding.checkAuthorizationAndBlind(tx, Set(bob))
"reinterpret to the same result" in { "reinterpret to the same result" in {
val txRoots = tx.roots.map(id => tx.nodes(id)).toSeq val roots = tx.roots.map(id => tx.nodes(id))
val Right((rtx, _)) = val nodeSeedMap = HashMap(txMeta.nodeSeeds.toSeq: _*)
engine val rootSeeds = tx.roots.map(nodeSeedMap.get)
.reinterpret(submissionTime, Some(txSeed), Set(bob), txRoots, let)
.consume(lookupContractForPayout, lookupPackage, lookupKey) val Right((rtx, _, _)) =
reinterpret(
engine,
Set(bob),
roots,
rootSeeds,
submissionTime,
let,
lookupPackage,
defaultContracts)
(rtx isReplayedBy tx) shouldBe true (rtx isReplayedBy tx) shouldBe true
} }
@ -929,7 +945,6 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
bobView.nodes.size shouldBe 2 bobView.nodes.size shouldBe 2
findNodeByIdx(bobView.nodes, 0).getOrElse(fail("node not found")) match { findNodeByIdx(bobView.nodes, 0).getOrElse(fail("node not found")) match {
case NodeExercises( case NodeExercises(
nodeSeed @ _,
coid, coid,
_, _,
choice, choice,
@ -952,7 +967,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
} }
findNodeByIdx(bobView.nodes, 1).getOrElse(fail("node not found")) match { findNodeByIdx(bobView.nodes, 1).getOrElse(fail("node not found")) match {
case NodeCreate(nodeSeed @ _, _, coins, _, _, stakeholders, _) => case NodeCreate(_, coins, _, _, stakeholders, _) =>
coins.template shouldBe templateId coins.template shouldBe templateId
stakeholders shouldBe Set(alice, clara) stakeholders shouldBe Set(alice, clara)
case _ => fail("create event is expected") case _ => fail("create event is expected")
@ -963,7 +978,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
claraView.nodes.size shouldBe 1 claraView.nodes.size shouldBe 1
findNodeByIdx(claraView.nodes, 1).getOrElse(fail("node not found")) match { findNodeByIdx(claraView.nodes, 1).getOrElse(fail("node not found")) match {
case NodeCreate(nodeSeed @ _, _, coins, _, _, stakeholders, _) => case NodeCreate(_, coins, _, _, stakeholders, _) =>
coins.template shouldBe templateId coins.template shouldBe templateId
stakeholders shouldBe Set(alice, clara) stakeholders shouldBe Set(alice, clara)
case _ => fail("create event is expected") case _ => fail("create event is expected")
@ -976,7 +991,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val transactionSeed = val transactionSeed =
crypto.Hash.deriveTransactionSeed(submissionSeed, participant, submissionTime) crypto.Hash.deriveTransactionSeed(submissionSeed, participant, submissionTime)
val Right((tx, _)) = val Right((tx, _, _)) =
engine engine
.interpretCommands( .interpretCommands(
validating = false, validating = false,
@ -985,9 +1000,9 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
commands = cmds, commands = cmds,
ledgerTime = let, ledgerTime = let,
submissionTime = submissionTime, submissionTime = submissionTime,
transactionSeed = Some(transactionSeed) seeding = InitialSeeding.TransactionSeed(transactionSeed),
) )
.consume(lookupContractForPayout, lookupPackage, lookupKey) .consume(lookupContract, lookupPackage, lookupKey)
val Seq(_, noid1) = tx.nodes.keys.toSeq.sortBy(_.index) val Seq(_, noid1) = tx.nodes.keys.toSeq.sortBy(_.index)
val Right(blindingInfo) = val Right(blindingInfo) =
Blinding.checkAuthorizationAndBlind(tx, Set(bob)) Blinding.checkAuthorizationAndBlind(tx, Set(bob))
@ -1086,7 +1101,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
} }
val let = Time.Timestamp.now() val let = Time.Timestamp.now()
val transactionSeed = crypto.Hash.deriveTransactionSeed(submissionSeed, participant, let) val seeding = Engine.initialSeeding(Some(submissionSeed), participant, let)
def actFetchActors[Nid, Cid, Val](n: GenNode[Nid, Cid, Val]): Set[Party] = { def actFetchActors[Nid, Cid, Val](n: GenNode[Nid, Cid, Val]): Set[Party] = {
n match { n match {
@ -1122,38 +1137,40 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
commands = r, commands = r,
ledgerTime = let, ledgerTime = let,
submissionTime = let, submissionTime = let,
transactionSeed = Some(transactionSeed), seeding = seeding,
) )
.consume(lookupContract, lookupPackage, lookupKey)) .consume(lookupContract, lookupPackage, lookupKey))
.map(_._1)
} }
"propagate the parent's signatories and actors (but not observers) when stakeholders" in { "propagate the parent's signatories and actors (but not observers) when stakeholders" in {
val Right(tx) = runExample(fetcher1Cid, clara) val Right((tx, _, _)) = runExample(fetcher1Cid, clara)
txFetchActors(tx) shouldBe Set(alice, clara) txFetchActors(tx) shouldBe Set(alice, clara)
} }
"not propagate the parent's signatories nor actors when not stakeholders" in { "not propagate the parent's signatories nor actors when not stakeholders" in {
val Right(tx) = runExample(fetcher2Cid, party) val Right((tx, _, _)) = runExample(fetcher2Cid, party)
txFetchActors(tx) shouldBe Set() txFetchActors(tx) shouldBe Set()
} }
"be retained when reinterpreting single fetch nodes" in { "be retained when reinterpreting single fetch nodes" in {
val Right(tx) = runExample(fetcher1Cid, clara) val Right((tx, _, nodeSeeds)) = runExample(fetcher1Cid, clara)
val nodeSeedMap = HashMap(nodeSeeds.toSeq: _*)
val fetchNodes = val fetchNodes =
tx.fold(Seq[(NodeId, GenNode.WithTxValue[NodeId, ContractId])]()) { tx.fold(Seq[(NodeId, GenNode.WithTxValue[NodeId, ContractId])]()) {
case (ns, (nid, n @ NodeFetch(_, _, _, _, _, _, _))) => ns :+ ((nid, n)) case (ns, (nid, n @ NodeFetch(_, _, _, _, _, _, _))) => ns :+ ((nid, n))
case (ns, _) => ns case (ns, _) => ns
} }
fetchNodes.foreach { fetchNodes.foreach {
case (nid, n) => case (nid, n) =>
val fetchTx = GenTx(HashMap(nid -> n), ImmArray(nid)) val fetchTx = GenTx(HashMap(nid -> n), ImmArray(nid))
val Right((reinterpreted, _)) = engine val Right((reinterpreted, _, _)) =
.reinterpret(let, None, n.requiredAuthorizers, Seq(n), let) engine
.consume(lookupContract, lookupPackage, lookupKey) .reinterpret(n.requiredAuthorizers, n, nodeSeedMap.get(nid), let, let)
.consume(lookupContract, lookupPackage, lookupKey)
(fetchTx isReplayedBy reinterpreted) shouldBe true (fetchTx isReplayedBy reinterpreted) shouldBe true
} }
} }
@ -1161,8 +1178,6 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
"reinterpreting fetch nodes" should { "reinterpreting fetch nodes" should {
val submissionSeed = hash("reinterpreting fetch nodes")
val fetchedCid = toContractId("#1") val fetchedCid = toContractId("#1")
val fetchedStrTid = "BasicTests:Fetched" val fetchedStrTid = "BasicTests:Fetched"
val fetchedTid = Identifier(basicTestsPkgId, fetchedStrTid) val fetchedTid = Identifier(basicTestsPkgId, fetchedStrTid)
@ -1203,11 +1218,11 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
) )
val let = Time.Timestamp.now() val let = Time.Timestamp.now()
val txSeed = crypto.Hash.deriveTransactionSeed(submissionSeed, participant, let)
val reinterpreted = engine val reinterpreted =
.reinterpret(let, Some(txSeed), Set.empty, Seq(fetchNode), let) engine
.consume(lookupContract, lookupPackage, lookupKey) .reinterpret(Set.empty, fetchNode, None, let, let)
.consume(lookupContract, lookupPackage, lookupKey)
reinterpreted shouldBe 'right reinterpreted shouldBe 'right
} }
@ -1247,9 +1262,10 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
) )
def firstLookupNode[Nid, Cid, Val]( def firstLookupNode[Nid, Cid, Val](
tx: GenTx[Nid, Cid, Val]): Option[NodeLookupByKey[Cid, Val]] = tx: GenTx[Nid, Cid, Val],
tx.nodes.values.collectFirst { ): Option[(Nid, NodeLookupByKey[Cid, Val])] =
case nl @ NodeLookupByKey(_, _, _, _) => nl tx.nodes.collectFirst {
case (nid, nl @ NodeLookupByKey(_, _, _, _)) => nid -> nl
} }
val now = Time.Timestamp.now() val now = Time.Timestamp.now()
@ -1266,25 +1282,23 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
participant, participant,
Some(submissionSeed)) Some(submissionSeed))
.consume(lookupContractMap.get, lookupPackage, lookupKey) .consume(lookupContractMap.get, lookupPackage, lookupKey)
val nodeSeedMap = HashMap(txMeta.nodeSeeds.toSeq: _*)
val txSeed = val Some((nid, lookupNode)) = firstLookupNode(tx)
crypto.Hash.deriveTransactionSeed(submissionSeed, participant, txMeta.submissionTime)
val Some(lookupNode) = firstLookupNode(tx)
lookupNode.result shouldBe Some(lookedUpCid) lookupNode.result shouldBe Some(lookedUpCid)
val freshEngine = Engine() val Right((reinterpreted, _, _)) =
val Right((reinterpreted, dependsOnTime @ _)) = freshEngine Engine()
.reinterpret( .reinterpret(
txMeta.submissionTime, Set.empty,
Some(txSeed), lookupNode,
Set.empty, nodeSeedMap.get(nid),
Seq(lookupNode), txMeta.submissionTime,
now, now,
) )
.consume(lookupContract, lookupPackage, lookupKey) .consume(lookupContract, lookupPackage, lookupKey)
firstLookupNode(reinterpreted) shouldEqual Some(lookupNode) firstLookupNode(reinterpreted).map(_._2) shouldEqual Some(lookupNode)
} }
"reinterpret to the same node when lookup doesn't find a contract" in { "reinterpret to the same node when lookup doesn't find a contract" in {
@ -1299,18 +1313,18 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
participant, participant,
Some(submissionSeed)) Some(submissionSeed))
.consume(lookupContractMap.get, lookupPackage, lookupKey) .consume(lookupContractMap.get, lookupPackage, lookupKey)
val txSeed =
crypto.Hash.deriveTransactionSeed(submissionSeed, participant, txMeta.submissionTime)
val Some(lookupNode) = firstLookupNode(tx) val nodeSeedMap = HashMap(txMeta.nodeSeeds.toSeq: _*)
val Some((nid, lookupNode)) = firstLookupNode(tx)
lookupNode.result shouldBe None lookupNode.result shouldBe None
val freshEngine = Engine() val Right((reinterpreted, _, _)) =
val Right((reinterpreted, dependsOnTime @ _)) = freshEngine Engine()
.reinterpret(now, Some(txSeed), Set.empty, Seq(lookupNode), now) .reinterpret(Set.empty, lookupNode, nodeSeedMap.get(nid), txMeta.submissionTime, now)
.consume(lookupContract, lookupPackage, lookupKey) .consume(lookupContract, lookupPackage, lookupKey)
firstLookupNode(reinterpreted) shouldEqual Some(lookupNode) firstLookupNode(reinterpreted).map(_._2) shouldEqual Some(lookupNode)
} }
} }
@ -1349,8 +1363,8 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val cmd = speedy.Command.Fetch(BasicTests_WithKey, SValue.SContractId(fetchedCid)) val cmd = speedy.Command.Fetch(BasicTests_WithKey, SValue.SContractId(fetchedCid))
val Right((tx, dependsOnTime @ _)) = engine val Right((tx, _, _)) = engine
.interpretCommands(false, false, Set(alice), ImmArray(cmd), now, now, None) .interpretCommands(false, false, Set(alice), ImmArray(cmd), now, now, InitialSeeding.NoSeed)
.consume(lookupContractMap.get, lookupPackage, lookupKey) .consume(lookupContractMap.get, lookupPackage, lookupKey)
tx.nodes.values.headOption match { tx.nodes.values.headOption match {
@ -1403,8 +1417,8 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
)) ))
.consume(lookupContractMap.get, lookupPackage, lookupKey) .consume(lookupContractMap.get, lookupPackage, lookupKey)
val Right((tx, dependsOnTime @ _)) = engine val Right((tx, _, _)) = engine
.interpretCommands(false, false, Set(alice), cmds, now, now, None) .interpretCommands(false, false, Set(alice), cmds, now, now, InitialSeeding.NoSeed)
.consume(lookupContractMap.get, lookupPackage, lookupKey) .consume(lookupContractMap.get, lookupPackage, lookupKey)
tx.nodes.values.collectFirst { tx.nodes.values.collectFirst {
@ -1467,21 +1481,22 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
} }
"be partially reinterpretable" in { "be partially reinterpretable" in {
val Right((tx, metaData)) = run(3) val Right((tx, txMeta)) = run(3)
val ImmArray(_, exeNode1) = tx.roots val ImmArray(_, exeNode1) = tx.roots
val NodeExercises(Some(exeNode1Seed), _, _, _, _, _, _, _, _, _, _, children, _, _) = val NodeExercises(_, _, _, _, _, _, _, _, _, _, children, _, _) =
tx.nodes(exeNode1) tx.nodes(exeNode1)
val ImmArray(createNode2, exeNode2, _, _) = children val nids = children.toSeq.take(2).toImmArray
val nodeSeedMap = HashMap(txMeta.nodeSeeds.toSeq: _*)
engine reinterpret(
.reinterpret( engine,
metaData.submissionTime, Set(party),
Some(exeNode1Seed), nids.map(tx.nodes),
Set(party), nids.map(nodeSeedMap.get),
List(createNode2, exeNode2).map(tx.nodes), txMeta.submissionTime,
let, let,
) lookupPackage,
.consume(_ => None, lookupPackage, _ => None) shouldBe 'right ) shouldBe 'right
} }
} }
@ -1515,4 +1530,92 @@ object EngineTest {
case _ => false case _ => false
} }
private def reinterpret(
engine: Engine,
submitters: Set[Party],
nodes: ImmArray[GenNode.WithTxValue[Value.NodeId, Value.ContractId]],
nodeSeeds: ImmArray[Option[crypto.Hash]],
submissionTime: Time.Timestamp,
ledgerEffectiveTime: Time.Timestamp,
lookupPackages: PackageId => Option[Package],
contracts: Map[AbsoluteContractId, Tx.ContractInst[AbsoluteContractId]] = Map.empty,
): Either[Error, (Tx.Transaction, Boolean, ImmArray[(NodeId, crypto.Hash)])] = {
type Acc =
(
HashMap[NodeId, Tx.Node],
BackStack[NodeId],
Boolean,
BackStack[(NodeId, crypto.Hash)],
Map[AbsoluteContractId, Tx.ContractInst[AbsoluteContractId]],
Map[GlobalKey, AbsoluteContractId],
)
val iterate =
(nodes zip nodeSeeds).foldLeft[Either[Error, Acc]](
Right((HashMap.empty, BackStack.empty, false, BackStack.empty, contracts, Map.empty))) {
case (acc, (node, nodeSeed)) =>
for {
previousStep <- acc
(nodes, roots, dependOnTime, nodeSeeds, contracts0, keys0) = previousStep
currentStep <- engine
.reinterpret(submitters, node, nodeSeed, submissionTime, ledgerEffectiveTime)
.consume(contracts0.get, lookupPackages, keys0.get)
(tr1, dependOnTime1, nodeSeeds1) = currentStep
(contracts1, keys1) = tr1.fold((contracts0, keys0)) {
case (
(contracts, keys),
(
_,
NodeExercises(
targetCoid: AbsoluteContractId,
_,
_,
_,
true,
_,
_,
_,
_,
_,
_,
_,
_))) =>
(contracts - targetCoid, keys)
case (
(contracts, keys),
(_, NodeCreate(cid: AbsoluteContractId, coinst, _, _, _, key))) =>
(
contracts.updated(
cid,
coinst.assertNoRelCid(cid => s"unexpected relative contract ID $cid")),
key.fold(keys)(
k =>
keys.updated(
GlobalKey(
coinst.template,
k.key.value.assertNoCid(cid => s"unexpected relative contract ID $cid")),
cid))
)
case (acc, _) => acc
}
n = nodes.size
nodeRenaming = (nid: NodeId) => NodeId(nid.index + n)
tr = tr1.mapNodeId(nodeRenaming)
} yield
(
nodes ++ tr.nodes,
roots :++ tr.roots,
dependOnTime || dependOnTime1,
nodeSeeds :++ nodeSeeds1.map { case (nid, seed) => nodeRenaming(nid) -> seed },
contracts1,
keys1,
)
}
iterate.map {
case (nodes, roots, dependOnTime, nodeSeeds, _, _) =>
(GenTx(nodes, roots.toImmArray), dependOnTime, nodeSeeds.toImmArray)
}
}
} }

View File

@ -92,9 +92,9 @@ class LargeTransactionTest extends WordSpec with Matchers with BazelRunfiles {
cmdReference = "create RangeOfInts", cmdReference = "create RangeOfInts",
seed = hash("testLargeTransactionOneContract:create", txSize)) seed = hash("testLargeTransactionOneContract:create", txSize))
val contractId: AbsoluteContractId = firstRootNode(createCmdTx) match { val contractId: AbsoluteContractId = firstRootNode(createCmdTx) match {
case N.NodeCreate(_, x: RelativeContractId, _, _, _, _, _) => case N.NodeCreate(x: RelativeContractId, _, _, _, _, _) =>
AbsoluteContractId.V0(pcs.toContractIdString(pcs.transactionCounter - 1)(x)) AbsoluteContractId.V0(pcs.toContractIdString(pcs.transactionCounter - 1)(x))
case N.NodeCreate(_, x: AbsoluteContractId, _, _, _, _, _) => x case N.NodeCreate(x: AbsoluteContractId, _, _, _, _, _) => x
case n @ _ => fail(s"Expected NodeCreate, but got: $n") case n @ _ => fail(s"Expected NodeCreate, but got: $n")
} }
val exerciseCmd = toListContainerExerciseCmd(rangeOfIntsTemplateId, contractId) val exerciseCmd = toListContainerExerciseCmd(rangeOfIntsTemplateId, contractId)
@ -121,9 +121,9 @@ class LargeTransactionTest extends WordSpec with Matchers with BazelRunfiles {
cmdReference = "create RangeOfInts", cmdReference = "create RangeOfInts",
seed = hash("testLargeTransactionManySmallContracts:create", num)) seed = hash("testLargeTransactionManySmallContracts:create", num))
val contractId: AbsoluteContractId = firstRootNode(createCmdTx) match { val contractId: AbsoluteContractId = firstRootNode(createCmdTx) match {
case N.NodeCreate(_, x: RelativeContractId, _, _, _, _, _) => case N.NodeCreate(x: RelativeContractId, _, _, _, _, _) =>
AbsoluteContractId.V0(pcs.toContractIdString(pcs.transactionCounter - 1)(x)) AbsoluteContractId.V0(pcs.toContractIdString(pcs.transactionCounter - 1)(x))
case N.NodeCreate(_, x: AbsoluteContractId, _, _, _, _, _) => x case N.NodeCreate(x: AbsoluteContractId, _, _, _, _, _) => x
case n @ _ => fail(s"Expected NodeCreate, but got: $n") case n @ _ => fail(s"Expected NodeCreate, but got: $n")
} }
val exerciseCmd = toListOfIntContainers(rangeOfIntsTemplateId, contractId) val exerciseCmd = toListOfIntContainers(rangeOfIntsTemplateId, contractId)
@ -150,9 +150,9 @@ class LargeTransactionTest extends WordSpec with Matchers with BazelRunfiles {
cmdReference = "create ListUtil", cmdReference = "create ListUtil",
seed = hash("testLargeChoiceArgument:create", size)) seed = hash("testLargeChoiceArgument:create", size))
val contractId: AbsoluteContractId = firstRootNode(createCmdTx) match { val contractId: AbsoluteContractId = firstRootNode(createCmdTx) match {
case N.NodeCreate(_, x: RelativeContractId, _, _, _, _, _) => case N.NodeCreate(x: RelativeContractId, _, _, _, _, _) =>
AbsoluteContractId.V0(pcs.toContractIdString(pcs.transactionCounter - 1)(x)) AbsoluteContractId.V0(pcs.toContractIdString(pcs.transactionCounter - 1)(x))
case N.NodeCreate(_, x: AbsoluteContractId, _, _, _, _, _) => x case N.NodeCreate(x: AbsoluteContractId, _, _, _, _, _) => x
case n @ _ => fail(s"Expected NodeCreate, but got: $n") case n @ _ => fail(s"Expected NodeCreate, but got: $n")
} }
val exerciseCmd = sizeExerciseCmd(listUtilTemplateId, contractId)(size) val exerciseCmd = sizeExerciseCmd(listUtilTemplateId, contractId)(size)
@ -195,7 +195,7 @@ class LargeTransactionTest extends WordSpec with Matchers with BazelRunfiles {
} }
newContracts.count { newContracts.count {
case N.NodeCreate(_, _, _, _, _, _, _) => true case N.NodeCreate(_, _, _, _, _, _) => true
case n @ _ => fail(s"Unexpected match: $n") case n @ _ => fail(s"Unexpected match: $n")
} shouldBe expectedNumberOfContracts } shouldBe expectedNumberOfContracts
} }
@ -320,7 +320,7 @@ class LargeTransactionTest extends WordSpec with Matchers with BazelRunfiles {
} }
createNode match { createNode match {
case N.NodeCreate(_, _, x: ContractInst[_], _, _, _, _) => x case N.NodeCreate(_, x: ContractInst[_], _, _, _, _) => x
case n @ _ => fail(s"Unexpected match: $n") case n @ _ => fail(s"Unexpected match: $n")
} }
} }

View File

@ -631,4 +631,12 @@ object Speedy {
/** Internal exception thrown when a continuation result needs to be returned. */ /** Internal exception thrown when a continuation result needs to be returned. */
final case class SpeedyHungry(result: SResult) extends RuntimeException with NoStackTrace final case class SpeedyHungry(result: SResult) extends RuntimeException with NoStackTrace
def deriveTransactionSeed(
submissionSeed: Option[crypto.Hash],
participant: Ref.ParticipantId,
submissionTime: Time.Timestamp,
): InitialSeeding =
InitialSeeding(
submissionSeed.map(crypto.Hash.deriveTransactionSeed(_, participant, submissionTime)))
} }

View File

@ -103,6 +103,7 @@ object PartialTransaction {
submissionTime = submissionTime, submissionTime = submissionTime,
nextNodeIdx = 0, nextNodeIdx = 0,
nodes = HashMap.empty, nodes = HashMap.empty,
nodeSeeds = BackStack.empty,
consumedBy = Map.empty, consumedBy = Map.empty,
context = Context(initialSeeds), context = Context(initialSeeds),
aborted = None, aborted = None,
@ -146,6 +147,7 @@ case class PartialTransaction(
submissionTime: Time.Timestamp, submissionTime: Time.Timestamp,
nextNodeIdx: Int, nextNodeIdx: Int,
nodes: HashMap[Value.NodeId, Tx.Node], nodes: HashMap[Value.NodeId, Tx.Node],
nodeSeeds: BackStack[(Value.NodeId, crypto.Hash)],
consumedBy: Map[Value.ContractId, Value.NodeId], consumedBy: Map[Value.ContractId, Value.NodeId],
context: PartialTransaction.Context, context: PartialTransaction.Context,
aborted: Option[Tx.TransactionError], aborted: Option[Tx.TransactionError],
@ -285,7 +287,6 @@ case class PartialTransaction(
Value.RelativeContractId(Value.NodeId(nextNodeIdx)) Value.RelativeContractId(Value.NodeId(nextNodeIdx))
)(Value.AbsoluteContractId.V1(_)) )(Value.AbsoluteContractId.V1(_))
val createNode = Node.NodeCreate( val createNode = Node.NodeCreate(
nodeSeed,
cid, cid,
coinst, coinst,
optLocation, optLocation,
@ -298,6 +299,7 @@ case class PartialTransaction(
nextNodeIdx = nextNodeIdx + 1, nextNodeIdx = nextNodeIdx + 1,
context = context.addChild(nid), context = context.addChild(nid),
nodes = nodes.updated(nid, createNode), nodes = nodes.updated(nid, createNode),
nodeSeeds = nodeSeed.fold(nodeSeeds)(s => nodeSeeds :+ (nid -> s)),
localContracts = localContracts.updated(cid, nid) localContracts = localContracts.updated(cid, nid)
) )
@ -409,7 +411,6 @@ case class PartialTransaction(
context.exeContext match { context.exeContext match {
case Some(ec) => case Some(ec) =>
val exerciseNode = Node.NodeExercises( val exerciseNode = Node.NodeExercises(
nodeSeed = ec.parent.nextChildrenSeed,
targetCoid = ec.targetId, targetCoid = ec.targetId,
templateId = ec.templateId, templateId = ec.templateId,
choiceId = ec.choiceId, choiceId = ec.choiceId,
@ -425,7 +426,12 @@ case class PartialTransaction(
key = ec.contractKey, key = ec.contractKey,
) )
val nodeId = ec.nodeId val nodeId = ec.nodeId
copy(context = ec.parent.addChild(nodeId), nodes = nodes.updated(nodeId, exerciseNode)) val nodeSeed = ec.parent.nextChildrenSeed
copy(
context = ec.parent.addChild(nodeId),
nodes = nodes.updated(nodeId, exerciseNode),
nodeSeeds = nodeSeed.fold(nodeSeeds)(s => nodeSeeds :+ (nodeId -> s)),
)
case None => case None =>
noteAbort(Tx.EndExerciseInRootContext) noteAbort(Tx.EndExerciseInRootContext)
} }
@ -461,7 +467,7 @@ case class PartialTransaction(
} }
sealed abstract class InitialSeeding sealed abstract class InitialSeeding extends Product with Serializable
object InitialSeeding { object InitialSeeding {
def apply(transactionSeed: Option[crypto.Hash]): InitialSeeding = def apply(transactionSeed: Option[crypto.Hash]): InitialSeeding =

View File

@ -243,7 +243,7 @@ object ValueGenerators {
signatories <- genNonEmptyParties signatories <- genNonEmptyParties
stakeholders <- genNonEmptyParties stakeholders <- genNonEmptyParties
key <- Gen.option(keyWithMaintainersGen) key <- Gen.option(keyWithMaintainersGen)
} yield NodeCreate(None, coid, coinst, None, signatories, stakeholders, key) } yield NodeCreate(coid, coinst, None, signatories, stakeholders, key)
} }
val fetchNodeGen: Gen[NodeFetch.WithTxValue[ContractId]] = { val fetchNodeGen: Gen[NodeFetch.WithTxValue[ContractId]] = {
@ -278,7 +278,6 @@ object ValueGenerators {
maintainers <- genNonEmptyParties maintainers <- genNonEmptyParties
} yield } yield
NodeExercises( NodeExercises(
None,
targetCoid, targetCoid,
templateId, templateId,
choiceId, choiceId,

View File

@ -60,7 +60,6 @@ object Node {
f3: A3 => B3, f3: A3 => B3,
): GenNode[A1, A2, A3] => GenNode[B1, B2, B3] = { ): GenNode[A1, A2, A3] => GenNode[B1, B2, B3] = {
case NodeCreate( case NodeCreate(
nodeSeed,
coid, coid,
coinst, coinst,
optLocation, optLocation,
@ -69,7 +68,6 @@ object Node {
key, key,
) => ) =>
NodeCreate( NodeCreate(
nodeSeed = nodeSeed,
coid = f2(coid), coid = f2(coid),
coinst = value.Value.ContractInst.map1(f3)(coinst), coinst = value.Value.ContractInst.map1(f3)(coinst),
optLocation = optLocation, optLocation = optLocation,
@ -96,7 +94,6 @@ object Node {
key = key.map(KeyWithMaintainers.map1(f3)), key = key.map(KeyWithMaintainers.map1(f3)),
) )
case NodeExercises( case NodeExercises(
nodeSeed,
targetCoid, targetCoid,
templateId, templateId,
choiceId, choiceId,
@ -112,7 +109,6 @@ object Node {
key, key,
) => ) =>
NodeExercises( NodeExercises(
nodeSeed = nodeSeed,
targetCoid = f2(targetCoid), targetCoid = f2(targetCoid),
templateId = templateId, templateId = templateId,
choiceId = choiceId, choiceId = choiceId,
@ -149,7 +145,6 @@ object Node {
/** Denotes the creation of a contract instance. */ /** Denotes the creation of a contract instance. */
final case class NodeCreate[+Cid, +Val]( final case class NodeCreate[+Cid, +Val](
nodeSeed: Option[crypto.Hash],
coid: Cid, coid: Cid,
coinst: ContractInst[Val], coinst: ContractInst[Val],
optLocation: Option[Location], // Optional location of the create expression optLocation: Option[Location], // Optional location of the create expression
@ -181,7 +176,6 @@ object Node {
* ledgers. * ledgers.
*/ */
final case class NodeExercises[+Nid, +Cid, +Val]( final case class NodeExercises[+Nid, +Cid, +Val](
nodeSeed: Option[crypto.Hash],
targetCoid: Cid, targetCoid: Cid,
templateId: Identifier, templateId: Identifier,
choiceId: ChoiceName, choiceId: ChoiceName,
@ -210,7 +204,6 @@ object Node {
* apply method enforces it. * apply method enforces it.
*/ */
def apply[Nid, Cid, Val]( def apply[Nid, Cid, Val](
nodeSeed: Option[crypto.Hash] = None,
targetCoid: Cid, targetCoid: Cid,
templateId: Identifier, templateId: Identifier,
choiceId: ChoiceName, choiceId: ChoiceName,
@ -225,7 +218,6 @@ object Node {
key: Option[KeyWithMaintainers[Val]], key: Option[KeyWithMaintainers[Val]],
): NodeExercises[Nid, Cid, Val] = ): NodeExercises[Nid, Cid, Val] =
NodeExercises( NodeExercises(
nodeSeed,
targetCoid, targetCoid,
templateId, templateId,
choiceId, choiceId,
@ -287,7 +279,7 @@ object Node {
): Boolean = ): Boolean =
ScalazEqual.match2[recorded.type, isReplayedBy.type, Boolean](fallback = false) { ScalazEqual.match2[recorded.type, isReplayedBy.type, Boolean](fallback = false) {
case nc: NodeCreate[Cid, Val] => { case nc: NodeCreate[Cid, Val] => {
case NodeCreate(_, coid2, coinst2, optLocation2 @ _, signatories2, stakeholders2, key2) => case NodeCreate(coid2, coinst2, optLocation2 @ _, signatories2, stakeholders2, key2) =>
import nc._ import nc._
// NOTE(JM): Do not compare location annotations as they may differ due to // NOTE(JM): Do not compare location annotations as they may differ due to
// differing update expression constructed from the root node. // differing update expression constructed from the root node.
@ -313,7 +305,6 @@ object Node {
} }
case ne: NodeExercises[Nothing, Cid, Val] => { case ne: NodeExercises[Nothing, Cid, Val] => {
case NodeExercises( case NodeExercises(
_,
targetCoid2, targetCoid2,
templateId2, templateId2,
choiceId2, choiceId2,

View File

@ -207,7 +207,7 @@ final case class GenTransaction[Nid, +Cid, +Val](
def localContracts[Cid2 >: Cid]: Map[Cid2, Nid] = def localContracts[Cid2 >: Cid]: Map[Cid2, Nid] =
fold(Map.empty[Cid2, Nid]) { fold(Map.empty[Cid2, Nid]) {
case (acc, (nid, create @ Node.NodeCreate(_, _, _, _, _, _, _))) => case (acc, (nid, create @ Node.NodeCreate(_, _, _, _, _, _))) =>
acc.updated(create.coid, nid) acc.updated(create.coid, nid)
case (acc, _) => acc case (acc, _) => acc
} }
@ -217,7 +217,7 @@ final case class GenTransaction[Nid, +Cid, +Val](
*/ */
def inputContracts[Cid2 >: Cid]: Set[Cid2] = def inputContracts[Cid2 >: Cid]: Set[Cid2] =
fold(Set.empty[Cid2]) { fold(Set.empty[Cid2]) {
case (acc, (_, Node.NodeExercises(_, coid, _, _, _, _, _, _, _, _, _, _, _, _))) => case (acc, (_, Node.NodeExercises(coid, _, _, _, _, _, _, _, _, _, _, _, _))) =>
acc + coid acc + coid
case (acc, (_, Node.NodeFetch(coid, _, _, _, _, _, _))) => case (acc, (_, Node.NodeFetch(coid, _, _, _, _, _, _))) =>
acc + coid acc + coid
@ -392,9 +392,11 @@ object Transaction {
* time. * time.
*/ */
final case class Metadata( final case class Metadata(
submissionSeed: Option[crypto.Hash],
submissionTime: Time.Timestamp, submissionTime: Time.Timestamp,
usedPackages: Set[PackageId], usedPackages: Set[PackageId],
dependsOnTime: Boolean dependsOnTime: Boolean,
nodeSeeds: ImmArray[(Value.NodeId, crypto.Hash)],
) )
type AbsTransaction = GenTransaction.WithTxValue[NodeId, Value.AbsoluteContractId] type AbsTransaction = GenTransaction.WithTxValue[NodeId, Value.AbsoluteContractId]

View File

@ -147,7 +147,7 @@ object TransactionCoder {
minContractKeyInFetch, minContractKeyInFetch,
} }
node match { node match {
case nc @ NodeCreate(_, _, _, _, _, _, _) => case nc @ NodeCreate(_, _, _, _, _, _) =>
val createBuilder = val createBuilder =
TransactionOuterClass.NodeCreate TransactionOuterClass.NodeCreate
.newBuilder() .newBuilder()
@ -211,7 +211,7 @@ object TransactionCoder {
nodeBuilder.setFetch(fetchBuilder).build() nodeBuilder.setFetch(fetchBuilder).build()
} }
case ne @ NodeExercises(_, _, _, _, _, _, _, _, _, _, _, _, _, _) => case ne @ NodeExercises(_, _, _, _, _, _, _, _, _, _, _, _, _) =>
for { for {
argValue <- encodeValue(encodeCid, ne.chosenValue) argValue <- encodeValue(encodeCid, ne.chosenValue)
(vversion, arg) = argValue (vversion, arg) = argValue
@ -344,7 +344,7 @@ object TransactionCoder {
else if (txVersion precedes minKeyOrLookupByKey) else if (txVersion precedes minKeyOrLookupByKey)
Left(DecodeError(s"$txVersion is too old to support NodeCreate's `key` field")) Left(DecodeError(s"$txVersion is too old to support NodeCreate's `key` field"))
else decodeKeyWithMaintainers(decodeCid, protoCreate.getKeyWithMaintainers).map(Some(_)) else decodeKeyWithMaintainers(decodeCid, protoCreate.getKeyWithMaintainers).map(Some(_))
} yield (ni, NodeCreate(None, c, ci, None, signatories, stakeholders, key)) } yield (ni, NodeCreate(c, ci, None, signatories, stakeholders, key))
case NodeTypeCase.FETCH => case NodeTypeCase.FETCH =>
val protoFetch = protoNode.getFetch val protoFetch = protoNode.getFetch
for { for {
@ -434,7 +434,6 @@ object TransactionCoder {
( (
ni, ni,
NodeExercises( NodeExercises(
None,
targetCoid = targetCoid, targetCoid = targetCoid,
templateId = templateId, templateId = templateId,
choiceId = choiceName, choiceId = choiceName,

View File

@ -304,7 +304,6 @@ class TransactionCoderSpec
"do tx with a lot of root nodes" in { "do tx with a lot of root nodes" in {
val node = val node =
Node.NodeCreate[Value.AbsoluteContractId, Value.VersionedValue[Value.AbsoluteContractId]]( Node.NodeCreate[Value.AbsoluteContractId, Value.VersionedValue[Value.AbsoluteContractId]](
nodeSeed = None,
coid = absCid("#test-cid"), coid = absCid("#test-cid"),
coinst = ContractInst( coinst = ContractInst(
Identifier( Identifier(

View File

@ -170,7 +170,6 @@ object TransactionSpec {
hasExerciseResult: Boolean = true, hasExerciseResult: Boolean = true,
): NodeExercises[V.NodeId, V.ContractId, Value] = ): NodeExercises[V.NodeId, V.ContractId, Value] =
NodeExercises( NodeExercises(
nodeSeed = None,
targetCoid = toCid(cid), targetCoid = toCid(cid),
templateId = Ref.Identifier( templateId = Ref.Identifier(
Ref.PackageId.assertFromString("-dummyPkg-"), Ref.PackageId.assertFromString("-dummyPkg-"),
@ -191,7 +190,6 @@ object TransactionSpec {
def dummyCreateNode(cid: String): NodeCreate[V.ContractId, Value] = def dummyCreateNode(cid: String): NodeCreate[V.ContractId, Value] =
NodeCreate( NodeCreate(
nodeSeed = None,
coid = toCid(cid), coid = toCid(cid),
coinst = V.ContractInst( coinst = V.ContractInst(
Ref.Identifier( Ref.Identifier(

View File

@ -86,13 +86,13 @@ private[kvutils] object InputsAndEffects {
GlobalKey(fetch.templateId, forceNoContractIds(keyWithMaintainers.key.value))) GlobalKey(fetch.templateId, forceNoContractIds(keyWithMaintainers.key.value)))
} }
case create @ NodeCreate(_, _, _, _, _, _, _) => case create @ NodeCreate(_, _, _, _, _, _) =>
create.key.foreach { keyWithMaintainers => create.key.foreach { keyWithMaintainers =>
inputs += globalKeyToStateKey( inputs += globalKeyToStateKey(
GlobalKey(create.coinst.template, forceNoContractIds(keyWithMaintainers.key.value))) GlobalKey(create.coinst.template, forceNoContractIds(keyWithMaintainers.key.value)))
} }
case exe @ NodeExercises(_, _, _, _, _, _, _, _, _, _, _, _, _, _) => case exe @ NodeExercises(_, _, _, _, _, _, _, _, _, _, _, _, _) =>
addContractInput(exe.targetCoid) addContractInput(exe.targetCoid)
case lookup @ NodeLookupByKey(_, _, _, _) => case lookup @ NodeLookupByKey(_, _, _, _) =>
@ -118,7 +118,7 @@ private[kvutils] object InputsAndEffects {
node match { node match {
case fetch @ NodeFetch(_, _, _, _, _, _, _) => case fetch @ NodeFetch(_, _, _, _, _, _, _) =>
effects effects
case create @ NodeCreate(_, _, _, _, _, _, _) => case create @ NodeCreate(_, _, _, _, _, _) =>
effects.copy( effects.copy(
createdContracts = contractIdToStateKey(create.coid) -> create :: effects.createdContracts, createdContracts = contractIdToStateKey(create.coid) -> create :: effects.createdContracts,
updatedContractKeys = create.key updatedContractKeys = create.key
@ -141,7 +141,7 @@ private[kvutils] object InputsAndEffects {
) )
) )
case exe @ NodeExercises(_, _, _, _, _, _, _, _, _, _, _, _, _, _) => case exe @ NodeExercises(_, _, _, _, _, _, _, _, _, _, _, _, _) =>
if (exe.consuming) { if (exe.consuming) {
effects.copy( effects.copy(
consumedContracts = contractIdToStateKey(exe.targetCoid) :: effects.consumedContracts, consumedContracts = contractIdToStateKey(exe.targetCoid) :: effects.consumedContracts,

View File

@ -252,13 +252,13 @@ private[kvutils] class ProcessTransactionSubmission(
.fold((true, startingKeys)) { .fold((true, startingKeys)) {
case ( case (
(allUnique, existingKeys), (allUnique, existingKeys),
(_, exe @ Node.NodeExercises(_, _, _, _, _, _, _, _, _, _, _, _, _, _))) (_, exe @ Node.NodeExercises(_, _, _, _, _, _, _, _, _, _, _, _, _)))
if exe.key.isDefined && exe.consuming => if exe.key.isDefined && exe.consuming =>
val stateKey = Conversions.globalKeyToStateKey( val stateKey = Conversions.globalKeyToStateKey(
Node.GlobalKey(exe.templateId, Conversions.forceNoContractIds(exe.key.get.key.value))) Node.GlobalKey(exe.templateId, Conversions.forceNoContractIds(exe.key.get.key.value)))
(allUnique, existingKeys - stateKey) (allUnique, existingKeys - stateKey)
case ((allUnique, existingKeys), (_, create @ Node.NodeCreate(_, _, _, _, _, _, _))) case ((allUnique, existingKeys), (_, create @ Node.NodeCreate(_, _, _, _, _, _)))
if create.key.isDefined => if create.key.isDefined =>
val stateKey = Conversions.globalKeyToStateKey( val stateKey = Conversions.globalKeyToStateKey(
Node.GlobalKey( Node.GlobalKey(

View File

@ -25,7 +25,6 @@ class ProjectionsSpec extends WordSpec with Matchers {
def makeCreateNode(cid: ContractId, signatories: Set[Party], stakeholders: Set[Party]) = def makeCreateNode(cid: ContractId, signatories: Set[Party], stakeholders: Set[Party]) =
Node.NodeCreate( Node.NodeCreate(
nodeSeed = None,
coid = cid, coid = cid,
coinst = ContractInst( coinst = ContractInst(
Identifier( Identifier(
@ -47,7 +46,6 @@ class ProjectionsSpec extends WordSpec with Matchers {
stakeholders: Set[Party], stakeholders: Set[Party],
children: ImmArray[NodeId]) = children: ImmArray[NodeId]) =
Node.NodeExercises( Node.NodeExercises(
nodeSeed = None,
targetCoid = target, targetCoid = target,
templateId = Identifier( templateId = Identifier(
PackageId.assertFromString("some-package"), PackageId.assertFromString("some-package"),

View File

@ -55,7 +55,7 @@ class V5_1__Populate_Event_Data extends BaseJavaMigration {
val data = txs.flatMap { val data = txs.flatMap {
case (txId, tx) => case (txId, tx) =>
tx.nodes.collect { tx.nodes.collect {
case (eventId, NodeCreate(nodeSeed @ _, cid, _, _, signatories, stakeholders, _)) => case (eventId, NodeCreate(cid, _, _, signatories, stakeholders, _)) =>
(cid, eventId, signatories, stakeholders -- signatories) (cid, eventId, signatories, stakeholders -- signatories)
} }
} }

View File

@ -55,7 +55,7 @@ class V10_1__Populate_Event_Data extends BaseJavaMigration {
val data = txs.flatMap { val data = txs.flatMap {
case (txId, tx) => case (txId, tx) =>
tx.nodes.collect { tx.nodes.collect {
case (eventId, NodeCreate(nodeSeed @ _, cid, _, _, signatories, stakeholders, _)) => case (eventId, NodeCreate(cid, _, _, signatories, stakeholders, _)) =>
(cid, eventId, signatories, stakeholders -- signatories) (cid, eventId, signatories, stakeholders -- signatories)
} }
} }

View File

@ -47,7 +47,6 @@ private[dao] trait JdbcLedgerDaoContractsSpec {
GenTransaction( GenTransaction(
HashMap( HashMap(
event1 -> NodeCreate( event1 -> NodeCreate(
nodeSeed = None,
coid = absCid, coid = absCid,
coinst = someContractInstance, coinst = someContractInstance,
optLocation = None, optLocation = None,

View File

@ -66,7 +66,6 @@ private[dao] trait JdbcLedgerDaoLedgerEntriesSpec extends LoneElement {
GenTransaction( GenTransaction(
HashMap( HashMap(
event1 -> NodeCreate( event1 -> NodeCreate(
nodeSeed = None,
coid = absCid, coid = absCid,
coinst = someContractInstance, coinst = someContractInstance,
optLocation = None, optLocation = None,
@ -114,7 +113,6 @@ private[dao] trait JdbcLedgerDaoLedgerEntriesSpec extends LoneElement {
GenTransaction( GenTransaction(
HashMap( HashMap(
event1 -> NodeCreate( event1 -> NodeCreate(
nodeSeed = None,
coid = absCid, coid = absCid,
coinst = someContractInstance, coinst = someContractInstance,
optLocation = None, optLocation = None,

View File

@ -110,7 +110,6 @@ private[dao] trait JdbcLedgerDaoSuite extends AkkaBeforeAndAfterAll with JdbcLed
absCid: AbsoluteContractId, absCid: AbsoluteContractId,
): NodeCreate.WithTxValue[AbsoluteContractId] = ): NodeCreate.WithTxValue[AbsoluteContractId] =
NodeCreate( NodeCreate(
nodeSeed = None,
coid = absCid, coid = absCid,
coinst = someContractInstance, coinst = someContractInstance,
optLocation = None, optLocation = None,
@ -123,7 +122,6 @@ private[dao] trait JdbcLedgerDaoSuite extends AkkaBeforeAndAfterAll with JdbcLed
targetCid: AbsoluteContractId, targetCid: AbsoluteContractId,
): NodeExercises.WithTxValue[EventId, AbsoluteContractId] = ): NodeExercises.WithTxValue[EventId, AbsoluteContractId] =
NodeExercises( NodeExercises(
nodeSeed = None,
targetCoid = targetCid, targetCoid = targetCid,
templateId = someTemplateId, templateId = someTemplateId,
choiceId = Ref.Name.assertFromString("choice"), choiceId = Ref.Name.assertFromString("choice"),
@ -425,7 +423,6 @@ private[dao] trait JdbcLedgerDaoSuite extends AkkaBeforeAndAfterAll with JdbcLed
GenTransaction( GenTransaction(
HashMap( HashMap(
event(s"transactionId$id", id) -> NodeCreate( event(s"transactionId$id", id) -> NodeCreate(
nodeSeed = None,
coid = AbsoluteContractId.assertFromString(s"#contractId$id"), coid = AbsoluteContractId.assertFromString(s"#contractId$id"),
coinst = someContractInstance, coinst = someContractInstance,
optLocation = None, optLocation = None,
@ -465,7 +462,6 @@ private[dao] trait JdbcLedgerDaoSuite extends AkkaBeforeAndAfterAll with JdbcLed
GenTransaction( GenTransaction(
HashMap( HashMap(
event(s"transactionId$id", id) -> NodeExercises( event(s"transactionId$id", id) -> NodeExercises(
nodeSeed = None,
targetCoid = AbsoluteContractId.assertFromString(s"#contractId${cid.toLong}"), targetCoid = AbsoluteContractId.assertFromString(s"#contractId${cid.toLong}"),
templateId = someTemplateId, templateId = someTemplateId,
choiceId = Ref.ChoiceName.assertFromString("Archive"), choiceId = Ref.ChoiceName.assertFromString("Archive"),

View File

@ -81,7 +81,6 @@ class ImplicitPartyAdditionIT
"create-signatory", "create-signatory",
"CmdId1", "CmdId1",
NodeCreate( NodeCreate(
nodeSeed = None,
coid = Value.AbsoluteContractId.assertFromString("#cId1"), coid = Value.AbsoluteContractId.assertFromString("#cId1"),
coinst = Value.ContractInst( coinst = Value.ContractInst(
templateId1, templateId1,
@ -99,7 +98,6 @@ class ImplicitPartyAdditionIT
"exercise-signatory", "exercise-signatory",
"CmdId2", "CmdId2",
NodeExercises( NodeExercises(
nodeSeed = None,
targetCoid = Value.AbsoluteContractId.assertFromString("#cId1"), targetCoid = Value.AbsoluteContractId.assertFromString("#cId1"),
templateId = templateId1, templateId = templateId1,
choiceId = Ref.ChoiceName.assertFromString("choice"), choiceId = Ref.ChoiceName.assertFromString("choice"),