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.Ref.{PackageId, ParticipantId, Party}
import com.daml.lf.language.Ast._
import com.daml.lf.speedy.Compiler
import com.daml.lf.speedy.Pretty
import com.daml.lf.speedy.{Compiler, InitialSeeding, Pretty, Command => SpeedyCommand}
import com.daml.lf.speedy.Speedy.Machine
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.Transaction.{NodeId, Transaction}
import com.daml.lf.value.Value
import com.daml.lf.speedy.{Command => SpeedyCommand}
/**
* 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.
*/
final class Engine {
private[this] val _compiledPackages = ConcurrentCompiledPackages()
private[this] val _preprocessor = new preprocessing.Preprocessor(_compiledPackages)
private[this] val compiledPackages = ConcurrentCompiledPackages()
private[this] val preprocessor = new preprocessing.Preprocessor(compiledPackages)
/**
* Executes commands `cmds` under the authority of `cmds.submitter` and returns one of the following:
@ -82,12 +81,12 @@ final class Engine {
cmds: Commands,
participantId: ParticipantId,
submissionSeed: Option[crypto.Hash],
): Result[(Transaction.Transaction, Transaction.Metadata)] = {
): Result[(Tx.Transaction, Tx.Metadata)] = {
val submissionTime = cmds.ledgerEffectiveTime
_preprocessor
preprocessor
.preprocessCommands(cmds.commands)
.flatMap { processedCmds =>
ShouldCheckSubmitterInMaintainers(_compiledPackages, cmds).flatMap {
ShouldCheckSubmitterInMaintainers(compiledPackages, cmds).flatMap {
checkSubmitterInMaintainers =>
interpretCommands(
validating = false,
@ -96,10 +95,9 @@ final class Engine {
commands = processedCmds,
ledgerTime = cmds.ledgerEffectiveTime,
submissionTime = submissionTime,
transactionSeed = submissionSeed.map(
crypto.Hash.deriveTransactionSeed(_, participantId, submissionTime)),
seeding = Engine.initialSeeding(submissionSeed, participantId, submissionTime),
) map {
case (tx, dependsOnTime) =>
case (tx, dependsOnTime, nodeSeeds) =>
// Annotate the transaction with the package dependencies. Since
// all commands are actions on a contract template, with a fully typed
// 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 pkgId = cmd.templateId.packageId
val transitiveDeps =
_compiledPackages
compiledPackages
.getPackageDependencies(pkgId)
.getOrElse(
sys.error(s"INTERNAL ERROR: Missing dependencies of package $pkgId"))
(pkgIds + pkgId) union transitiveDeps
}
tx -> Transaction.Metadata(
submissionTime = submissionTime,
usedPackages = deps,
dependsOnTime = dependsOnTime,
tx -> Tx.Metadata(
submissionSeed,
submissionTime,
deps,
dependsOnTime,
nodeSeeds,
)
}
}
@ -124,47 +124,35 @@ final class Engine {
}
/**
* Behaves like `submit`, but it takes GenNode arguments instead of a Commands argument.
* That is, it can be used to reinterpret 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
* Behaves like `submit`, but it takes a GenNode argument instead of a Commands argument.
* That is, it can be used to reinterpret partially an already interpreted transaction (since it consists of GenNodes).
*
* 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`
* contain a relative contract ID, either as the target contract of a fetch, or as an argument to a
* create or an exercise choice.
*
* [[transactionSeed]] is the master hash te be used to derive node and contractId discriminator.
* If let undefined, no discriminator will be generated.
* [[nodeSeed]] is the seed of the Create and Exercise node as generated during submission.
* If undefined the contract IDs are derive using V0 scheme.
* The value of [[nodeSeed]] does not matter for other kind of nodes.
*/
def reinterpret(
submissionTime: Time.Timestamp,
transactionSeed: Option[crypto.Hash],
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,
): Result[(Transaction.Transaction, Boolean)] =
): Result[(Tx.Transaction, Boolean, ImmArray[(NodeId, crypto.Hash)])] =
for {
commands <- Result.sequence(ImmArray(nodes).map(_preprocessor.translateNode))
command <- preprocessor.translateNode(node)
checkSubmitterInMaintainers <- ShouldCheckSubmitterInMaintainers(
_compiledPackages,
commands.map(_.templateId))
compiledPackages,
ImmArray(command.templateId))
// reinterpret is never used for submission, only for validation.
result <- interpretCommands(
validating = true,
checkSubmitterInMaintainers = checkSubmitterInMaintainers,
submitters = submitters,
commands = commands,
commands = ImmArray(command),
ledgerTime = ledgerEffectiveTime,
submissionTime,
transactionSeed,
submissionTime = submissionTime,
seeding = InitialSeeding.RootNodeSeeds(ImmArray(nodeSeed)),
)
} yield result
@ -184,7 +172,7 @@ final class Engine {
* @param ledgerEffectiveTime time when the transaction is claimed to be submitted
*/
def validate(
tx: Transaction.Transaction,
tx: Tx.Transaction,
ledgerEffectiveTime: Time.Timestamp,
participantId: Ref.ParticipantId,
submissionTime: Time.Timestamp,
@ -217,9 +205,9 @@ final class Engine {
// For empty transactions, use an empty set of submitters
submitters = submittersOpt.getOrElse(Set.empty)
commands <- _preprocessor.translateTransactionRoots(tx)
commands <- preprocessor.translateTransactionRoots(tx)
checkSubmitterInMaintainers <- ShouldCheckSubmitterInMaintainers(
_compiledPackages,
compiledPackages,
commands.map(_._2.templateId))
result <- interpretCommands(
validating = true,
@ -227,10 +215,10 @@ final class Engine {
submitters = submitters,
commands = commands.map(_._2),
ledgerTime = ledgerEffectiveTime,
submissionTime,
submissionSeed.map(crypto.Hash.deriveTransactionSeed(_, participantId, submissionTime))
submissionTime = submissionTime,
seeding = Engine.initialSeeding(submissionSeed, participantId, submissionTime),
)
(rtx, _) = result
(rtx, _, _) = result
validationResult <- if (tx isReplayedBy rtx) {
ResultDone(())
} else {
@ -246,8 +234,7 @@ final class Engine {
* Submitters are a set, in order to support interpreting subtransactions
* (a subtransaction can be authorized by multiple parties).
*
* [[transactionSeed]] is the master hash used to derive node and contractId discriminator.
* If let undefined, no discriminator will be generated.
* [[seeding]] is seeding used to derive node seed and contractId discriminator.
*/
private[engine] def interpretCommands(
validating: Boolean,
@ -257,15 +244,15 @@ final class Engine {
commands: ImmArray[SpeedyCommand],
ledgerTime: Time.Timestamp,
submissionTime: Time.Timestamp,
transactionSeed: Option[crypto.Hash],
): Result[(Transaction.Transaction, Boolean)] = {
seeding: speedy.InitialSeeding,
): Result[(Transaction, Boolean, ImmArray[(Tx.NodeId, crypto.Hash)])] = {
val machine = Machine
.build(
checkSubmitterInMaintainers = checkSubmitterInMaintainers,
sexpr = Compiler(compiledPackages.packages).compile(commands),
compiledPackages = _compiledPackages,
submissionTime,
speedy.InitialSeeding(transactionSeed)
compiledPackages = compiledPackages,
submissionTime = submissionTime,
seeds = seeding,
)
.copy(validating = validating, committers = submitters)
interpretLoop(machine, ledgerTime)
@ -276,7 +263,7 @@ final class Engine {
private[engine] def interpretLoop(
machine: Machine,
time: Time.Timestamp
): Result[(Transaction.Transaction, Boolean)] = {
): Result[(Tx.Transaction, Boolean, ImmArray[(Tx.NodeId, crypto.Hash)])] = {
while (!machine.isFinal) {
machine.step() match {
case SResultContinue =>
@ -293,9 +280,9 @@ final class Engine {
return Result.needPackage(
pkgId,
pkg => {
_compiledPackages.addPackage(pkgId, pkg).flatMap {
compiledPackages.addPackage(pkgId, pkg).flatMap {
case _ =>
callback(_compiledPackages)
callback(compiledPackages)
interpretLoop(machine, time)
}
}
@ -345,17 +332,17 @@ final class Engine {
machine.ptx.finish match {
case Left(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]],
* and not a [[ConcurrentCompiledPackages]], otherwise people would be able
* 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,
* rather than having the engine to ask about it through
@ -365,9 +352,22 @@ final class Engine {
* be loaded.
*/
def preloadPackage(pkgId: PackageId, pkg: Package): Result[Unit] =
_compiledPackages.addPackage(pkgId, pkg)
compiledPackages.addPackage(pkgId, pkg)
}
object 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, _]) =
node match {
case Node.NodeCreate(
nodeSeed @ _,
coid @ _,
coinst,
optLoc @ _,
sigs @ _,
stks @ _,
key @ _) =>
case Node.NodeCreate(coid @ _, coinst, optLoc @ _, sigs @ _, stks @ _, key @ _) =>
coinst.template
case Node.NodeExercises(
nodeSeed @ _,
coid @ _,
templateId,
choice @ _,

View File

@ -48,20 +48,12 @@ private[preprocessing] final class TransactionPreprocessor(
): speedy.Command = {
node match {
case Node.NodeCreate(
nodeSeed @ _,
coid @ _,
coinst,
optLoc @ _,
sigs @ _,
stks @ _,
key @ _) =>
case Node.NodeCreate(coid @ _, coinst, optLoc @ _, sigs @ _, stks @ _, key @ _) =>
val identifier = coinst.template
val arg = unsafeAsValueWithAbsoluteContractIds(coinst.arg.value)
commandPreprocessor.unsafePreprocessCreate(identifier, arg)
case Node.NodeExercises(
nodeSeed @ _,
coid,
template,
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.value.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.command._
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(
"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 BasicTests_WithKey = Identifier(basicTestsPkgId, withKeyTemplate)
val withKeyContractInst: ContractInst[Tx.Value[AbsoluteContractId]] =
@ -106,11 +76,35 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
""
)
def lookupContractWithKey(
@deprecated("shut up unused arguments warning", "blah") id: AbsoluteContractId)
: Option[ContractInst[Tx.Value[AbsoluteContractId]]] = {
Some(withKeyContractInst)
}
val defaultContracts =
Map(
toContractId("#BasicTests:Simple:1") ->
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] = {
allPackages.get(pkgId)
@ -122,7 +116,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
BasicTests_WithKey,
ValueRecord(_, ImmArray((_, ValueParty(`alice`)), (_, ValueInt64(42)))),
) =>
Some(toContractId("#1"))
Some(toContractId("#BasicTests:WithKey:1"))
case _ =>
None
}
@ -198,7 +192,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
}
"translate exercise commands argument including labels" in {
val originalCoid = toContractId("#1")
val originalCoid = toContractId("#BasicTests:CallablePayout:1")
val templateId = Identifier(basicTestsPkgId, "BasicTests:CallablePayout")
val command = ExerciseCommand(
templateId,
@ -208,12 +202,12 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val res = preprocessor
.preprocessCommands(ImmArray(command))
.consume(lookupContractForPayout, lookupPackage, lookupKey)
.consume(lookupContract, lookupPackage, lookupKey)
res shouldBe 'right
}
"translate exercise commands argument without labels" in {
val originalCoid = toContractId("#1")
val originalCoid = toContractId("#BasicTests:CallablePayout:1")
val templateId = Identifier(basicTestsPkgId, "BasicTests:CallablePayout")
val command = ExerciseCommand(
templateId,
@ -223,7 +217,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val res = preprocessor
.preprocessCommands(ImmArray(command))
.consume(lookupContractForPayout, lookupPackage, lookupKey)
.consume(lookupContract, lookupPackage, lookupKey)
res shouldBe 'right
}
@ -238,7 +232,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val res = preprocessor
.preprocessCommands(ImmArray(command))
.consume(lookupContractForPayout, lookupPackage, lookupKey)
.consume(lookupContract, lookupPackage, lookupKey)
res shouldBe 'right
}
@ -253,7 +247,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val res = preprocessor
.preprocessCommands(ImmArray(command))
.consume(lookupContractForPayout, lookupPackage, lookupKey)
.consume(lookupContract, lookupPackage, lookupKey)
res shouldBe 'right
}
@ -268,7 +262,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val res = preprocessor
.preprocessCommands(ImmArray(command))
.consume(lookupContractForPayout, lookupPackage, lookupKey)
.consume(lookupContract, lookupPackage, lookupKey)
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
.preprocessCommands(ImmArray(command))
.consume(lookupContractForPayout, lookupPackage, lookupKey)
.consume(lookupContract, lookupPackage, lookupKey)
res.left.value.msg should startWith(
"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
.preprocessCommands(ImmArray(command))
.consume(lookupContractForPayout, lookupPackage, lookupKey)
.consume(lookupContract, lookupPackage, lookupKey)
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
.preprocessCommands(ImmArray(command))
.consume(lookupContractForPayout, lookupPackage, lookupKey)
.consume(lookupContract, lookupPackage, lookupKey)
res shouldBe 'right
}
@ -439,14 +433,19 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
"reinterpret to the same result" in {
val Right((tx, txMeta)) = interpretResult
val txRoots = tx.roots.map(id => tx.nodes(id)).toSeq
val txSeed =
Some(crypto.Hash.deriveTransactionSeed(submissionSeed, participant, txMeta.submissionTime))
val txRoots = tx.roots.map(id => tx.nodes(id))
val nodeSeedMap = txMeta.nodeSeeds.toSeq.toMap
val Right((rtx, _)) =
engine
.reinterpret(txMeta.submissionTime, txSeed, Set(party), txRoots, let)
.consume(lookupContract, lookupPackage, lookupKey)
val Right((rtx, _, _)) =
reinterpret(
engine,
Set(party),
txRoots,
tx.roots.map(nodeSeedMap.get),
txMeta.submissionTime,
let,
lookupPackage
)
(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 hello = Identifier(basicTestsPkgId, "BasicTests:Hello")
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 =
ExerciseCommand(
templateId,
toContractId("#1"),
"Hello",
ValueRecord(Some(hello), ImmArray.empty))
ExerciseCommand(templateId, cid, "Hello", ValueRecord(Some(hello), ImmArray.empty))
val res = preprocessor
.preprocessCommands(ImmArray(command))
@ -498,10 +494,10 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
commands = r,
ledgerTime = let,
submissionTime = let,
transactionSeed = Some(txSeed)
seeding = seeding,
)
.consume(lookupContract, lookupPackage, lookupKey))
val Right((tx, _)) = interpretResult
val Right((tx, _, nodeSeeds)) = interpretResult
"be translated" in {
val Right((rtx, _)) = engine
@ -511,11 +507,21 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
}
"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
.reinterpret(let, Some(txSeed), Set(party), txRoots, let)
.consume(lookupContract, lookupPackage, lookupKey)
val Right((rtx, _, _)) =
reinterpret(
engine,
Set(party),
roots,
rootSeeds,
let,
let,
lookupPackage,
defaultContracts
)
(tx isReplayedBy rtx) shouldBe true
}
@ -556,13 +562,13 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val res = preprocessor
.preprocessCommands(ImmArray(command))
.consume(lookupContractWithKey, lookupPackage, lookupKey)
.consume(lookupContract, lookupPackage, lookupKey)
res shouldBe 'right
"fail at submission" in {
val submitResult = engine
.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")
}
}
@ -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 templateId = Identifier(basicTestsPkgId, "BasicTests:WithKey")
val let = Time.Timestamp.now()
val txSeed = crypto.Hash.deriveTransactionSeed(submissionSeed, participant, let)
val seeding = Engine.initialSeeding(Some(submissionSeed), participant, let)
val command = ExerciseByKeyCommand(
templateId,
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
.preprocessCommands(ImmArray(command))
.consume(lookupContractWithKey, lookupPackage, lookupKey)
.consume(lookupContract, lookupPackage, lookupKey)
res shouldBe 'right
val result =
res
@ -595,34 +601,34 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
commands = r,
ledgerTime = let,
submissionTime = let,
transactionSeed = Some(txSeed)
seeding = seeding,
)
.consume(lookupContractWithKey, lookupPackage, lookupKey))
.map(_._1)
val tx = result.right.value
.consume(lookupContract, lookupPackage, lookupKey))
val Right((tx, _, nodeSeeds)) = result
"be translated" in {
val submitResult = engine
.submit(Commands(alice, ImmArray(command), let, "test"), participant, Some(submissionSeed))
.consume(lookupContractWithKey, lookupPackage, lookupKey)
.consume(lookupContract, lookupPackage, lookupKey)
.map(_._1)
(result |@| submitResult)(_ isReplayedBy _) shouldBe Right(true)
(result.map(_._1) |@| submitResult)(_ isReplayedBy _) shouldBe Right(true)
}
"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 =
engine
.reinterpret(let, Some(txSeed), Set(alice), txRoots, let)
.consume(lookupContractWithKey, lookupPackage, lookupKey)
reinterpret(engine, Set(alice), roots, rootSeeds, let, let, lookupPackage, defaultContracts)
.map(_._1)
(result |@| reinterpretResult)(_ isReplayedBy _) shouldBe Right(true)
(result.map(_._1) |@| reinterpretResult)(_ isReplayedBy _) shouldBe Right(true)
}
"be validated" in {
val validated = engine
.validate(tx, let, participant, let, Some(submissionSeed))
.consume(lookupContractWithKey, lookupPackage, lookupKey)
.consume(lookupContract, lookupPackage, lookupKey)
validated match {
case Left(e) =>
fail(e.msg)
@ -673,11 +679,11 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
commands = r,
ledgerTime = let,
submissionTime = let,
transactionSeed = Some(txSeed),
seeding = InitialSeeding.TransactionSeed(txSeed),
)
.consume(lookupContract, lookupPackage, lookupKey))
.map(_._1)
val Right(tx) = interpretResult
val Right((tx, _, nodeSeeds)) = interpretResult
"be translated" in {
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 {
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 =
engine
.reinterpret(let, Some(txSeed), Set(party), txRoots, let)
.consume(lookupContract, lookupPackage, lookupKey)
reinterpret(engine, Set(party), roots, rootSeeds, let, let, lookupPackage)
.map(_._1)
(interpretResult |@| reinterpretResult)(_ isReplayedBy _) shouldBe Right(true)
(interpretResult.map(_._1) |@| reinterpretResult)(_ isReplayedBy _) shouldBe Right(true)
}
"be validated" in {
@ -873,7 +880,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
"exercise callable command" should {
val submissionSeed = hash("exercise callable command")
val originalCoid = toContractId("#1")
val originalCoid = toContractId("#BasicTests:CallablePayout:1")
val templateId = Identifier(basicTestsPkgId, "BasicTests:CallablePayout")
// we need to fix time as cid are depending on it
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",
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))
.consume(lookupContractForPayout, lookupPackage, lookupKey)
.consume(lookupContract, lookupPackage, lookupKey)
val submissionTime = meta.submissionTime
val submissionTime = txMeta.submissionTime
val txSeed =
crypto.Hash.deriveTransactionSeed(submissionSeed, participant, submissionTime)
val Right(cmds) = preprocessor
.preprocessCommands(ImmArray(command))
.consume(lookupContractForPayout, lookupPackage, lookupKey)
val Right((rtx, _)) = engine
.consume(lookupContract, lookupPackage, lookupKey)
val Right((rtx, _, _)) = engine
.interpretCommands(
validating = false,
checkSubmitterInMaintainers = true,
@ -902,9 +909,9 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
commands = cmds,
ledgerTime = let,
submissionTime = submissionTime,
transactionSeed = Some(txSeed),
seeding = InitialSeeding.TransactionSeed(txSeed)
)
.consume(lookupContractForPayout, lookupPackage, lookupKey)
.consume(lookupContract, lookupPackage, lookupKey)
"be translated" in {
(rtx isReplayedBy tx) shouldBe true
@ -914,11 +921,20 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
Blinding.checkAuthorizationAndBlind(tx, Set(bob))
"reinterpret to the same result" in {
val txRoots = tx.roots.map(id => tx.nodes(id)).toSeq
val Right((rtx, _)) =
engine
.reinterpret(submissionTime, Some(txSeed), Set(bob), txRoots, let)
.consume(lookupContractForPayout, lookupPackage, lookupKey)
val roots = tx.roots.map(id => tx.nodes(id))
val nodeSeedMap = HashMap(txMeta.nodeSeeds.toSeq: _*)
val rootSeeds = tx.roots.map(nodeSeedMap.get)
val Right((rtx, _, _)) =
reinterpret(
engine,
Set(bob),
roots,
rootSeeds,
submissionTime,
let,
lookupPackage,
defaultContracts)
(rtx isReplayedBy tx) shouldBe true
}
@ -929,7 +945,6 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
bobView.nodes.size shouldBe 2
findNodeByIdx(bobView.nodes, 0).getOrElse(fail("node not found")) match {
case NodeExercises(
nodeSeed @ _,
coid,
_,
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 {
case NodeCreate(nodeSeed @ _, _, coins, _, _, stakeholders, _) =>
case NodeCreate(_, coins, _, _, stakeholders, _) =>
coins.template shouldBe templateId
stakeholders shouldBe Set(alice, clara)
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
findNodeByIdx(claraView.nodes, 1).getOrElse(fail("node not found")) match {
case NodeCreate(nodeSeed @ _, _, coins, _, _, stakeholders, _) =>
case NodeCreate(_, coins, _, _, stakeholders, _) =>
coins.template shouldBe templateId
stakeholders shouldBe Set(alice, clara)
case _ => fail("create event is expected")
@ -976,7 +991,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val transactionSeed =
crypto.Hash.deriveTransactionSeed(submissionSeed, participant, submissionTime)
val Right((tx, _)) =
val Right((tx, _, _)) =
engine
.interpretCommands(
validating = false,
@ -985,9 +1000,9 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
commands = cmds,
ledgerTime = let,
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 Right(blindingInfo) =
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 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] = {
n match {
@ -1122,38 +1137,40 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
commands = r,
ledgerTime = let,
submissionTime = let,
transactionSeed = Some(transactionSeed),
seeding = seeding,
)
.consume(lookupContract, lookupPackage, lookupKey))
.map(_._1)
}
"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)
}
"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()
}
"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 =
tx.fold(Seq[(NodeId, GenNode.WithTxValue[NodeId, ContractId])]()) {
case (ns, (nid, n @ NodeFetch(_, _, _, _, _, _, _))) => ns :+ ((nid, n))
case (ns, _) => ns
}
fetchNodes.foreach {
case (nid, n) =>
val fetchTx = GenTx(HashMap(nid -> n), ImmArray(nid))
val Right((reinterpreted, _)) = engine
.reinterpret(let, None, n.requiredAuthorizers, Seq(n), let)
.consume(lookupContract, lookupPackage, lookupKey)
val Right((reinterpreted, _, _)) =
engine
.reinterpret(n.requiredAuthorizers, n, nodeSeedMap.get(nid), let, let)
.consume(lookupContract, lookupPackage, lookupKey)
(fetchTx isReplayedBy reinterpreted) shouldBe true
}
}
@ -1161,8 +1178,6 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
"reinterpreting fetch nodes" should {
val submissionSeed = hash("reinterpreting fetch nodes")
val fetchedCid = toContractId("#1")
val fetchedStrTid = "BasicTests:Fetched"
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 txSeed = crypto.Hash.deriveTransactionSeed(submissionSeed, participant, let)
val reinterpreted = engine
.reinterpret(let, Some(txSeed), Set.empty, Seq(fetchNode), let)
.consume(lookupContract, lookupPackage, lookupKey)
val reinterpreted =
engine
.reinterpret(Set.empty, fetchNode, None, let, let)
.consume(lookupContract, lookupPackage, lookupKey)
reinterpreted shouldBe 'right
}
@ -1247,9 +1262,10 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
)
def firstLookupNode[Nid, Cid, Val](
tx: GenTx[Nid, Cid, Val]): Option[NodeLookupByKey[Cid, Val]] =
tx.nodes.values.collectFirst {
case nl @ NodeLookupByKey(_, _, _, _) => nl
tx: GenTx[Nid, Cid, Val],
): Option[(Nid, NodeLookupByKey[Cid, Val])] =
tx.nodes.collectFirst {
case (nid, nl @ NodeLookupByKey(_, _, _, _)) => nid -> nl
}
val now = Time.Timestamp.now()
@ -1266,25 +1282,23 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
participant,
Some(submissionSeed))
.consume(lookupContractMap.get, lookupPackage, lookupKey)
val nodeSeedMap = HashMap(txMeta.nodeSeeds.toSeq: _*)
val txSeed =
crypto.Hash.deriveTransactionSeed(submissionSeed, participant, txMeta.submissionTime)
val Some(lookupNode) = firstLookupNode(tx)
val Some((nid, lookupNode)) = firstLookupNode(tx)
lookupNode.result shouldBe Some(lookedUpCid)
val freshEngine = Engine()
val Right((reinterpreted, dependsOnTime @ _)) = freshEngine
.reinterpret(
txMeta.submissionTime,
Some(txSeed),
Set.empty,
Seq(lookupNode),
now,
)
.consume(lookupContract, lookupPackage, lookupKey)
val Right((reinterpreted, _, _)) =
Engine()
.reinterpret(
Set.empty,
lookupNode,
nodeSeedMap.get(nid),
txMeta.submissionTime,
now,
)
.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 {
@ -1299,18 +1313,18 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
participant,
Some(submissionSeed))
.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
val freshEngine = Engine()
val Right((reinterpreted, dependsOnTime @ _)) = freshEngine
.reinterpret(now, Some(txSeed), Set.empty, Seq(lookupNode), now)
.consume(lookupContract, lookupPackage, lookupKey)
val Right((reinterpreted, _, _)) =
Engine()
.reinterpret(Set.empty, lookupNode, nodeSeedMap.get(nid), txMeta.submissionTime, now)
.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 Right((tx, dependsOnTime @ _)) = engine
.interpretCommands(false, false, Set(alice), ImmArray(cmd), now, now, None)
val Right((tx, _, _)) = engine
.interpretCommands(false, false, Set(alice), ImmArray(cmd), now, now, InitialSeeding.NoSeed)
.consume(lookupContractMap.get, lookupPackage, lookupKey)
tx.nodes.values.headOption match {
@ -1403,8 +1417,8 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
))
.consume(lookupContractMap.get, lookupPackage, lookupKey)
val Right((tx, dependsOnTime @ _)) = engine
.interpretCommands(false, false, Set(alice), cmds, now, now, None)
val Right((tx, _, _)) = engine
.interpretCommands(false, false, Set(alice), cmds, now, now, InitialSeeding.NoSeed)
.consume(lookupContractMap.get, lookupPackage, lookupKey)
tx.nodes.values.collectFirst {
@ -1467,21 +1481,22 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
}
"be partially reinterpretable" in {
val Right((tx, metaData)) = run(3)
val Right((tx, txMeta)) = run(3)
val ImmArray(_, exeNode1) = tx.roots
val NodeExercises(Some(exeNode1Seed), _, _, _, _, _, _, _, _, _, _, children, _, _) =
val NodeExercises(_, _, _, _, _, _, _, _, _, _, children, _, _) =
tx.nodes(exeNode1)
val ImmArray(createNode2, exeNode2, _, _) = children
val nids = children.toSeq.take(2).toImmArray
val nodeSeedMap = HashMap(txMeta.nodeSeeds.toSeq: _*)
engine
.reinterpret(
metaData.submissionTime,
Some(exeNode1Seed),
Set(party),
List(createNode2, exeNode2).map(tx.nodes),
let,
)
.consume(_ => None, lookupPackage, _ => None) shouldBe 'right
reinterpret(
engine,
Set(party),
nids.map(tx.nodes),
nids.map(nodeSeedMap.get),
txMeta.submissionTime,
let,
lookupPackage,
) shouldBe 'right
}
}
@ -1515,4 +1530,92 @@ object EngineTest {
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",
seed = hash("testLargeTransactionOneContract:create", txSize))
val contractId: AbsoluteContractId = firstRootNode(createCmdTx) match {
case N.NodeCreate(_, x: RelativeContractId, _, _, _, _, _) =>
case N.NodeCreate(x: RelativeContractId, _, _, _, _, _) =>
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")
}
val exerciseCmd = toListContainerExerciseCmd(rangeOfIntsTemplateId, contractId)
@ -121,9 +121,9 @@ class LargeTransactionTest extends WordSpec with Matchers with BazelRunfiles {
cmdReference = "create RangeOfInts",
seed = hash("testLargeTransactionManySmallContracts:create", num))
val contractId: AbsoluteContractId = firstRootNode(createCmdTx) match {
case N.NodeCreate(_, x: RelativeContractId, _, _, _, _, _) =>
case N.NodeCreate(x: RelativeContractId, _, _, _, _, _) =>
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")
}
val exerciseCmd = toListOfIntContainers(rangeOfIntsTemplateId, contractId)
@ -150,9 +150,9 @@ class LargeTransactionTest extends WordSpec with Matchers with BazelRunfiles {
cmdReference = "create ListUtil",
seed = hash("testLargeChoiceArgument:create", size))
val contractId: AbsoluteContractId = firstRootNode(createCmdTx) match {
case N.NodeCreate(_, x: RelativeContractId, _, _, _, _, _) =>
case N.NodeCreate(x: RelativeContractId, _, _, _, _, _) =>
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")
}
val exerciseCmd = sizeExerciseCmd(listUtilTemplateId, contractId)(size)
@ -195,7 +195,7 @@ class LargeTransactionTest extends WordSpec with Matchers with BazelRunfiles {
}
newContracts.count {
case N.NodeCreate(_, _, _, _, _, _, _) => true
case N.NodeCreate(_, _, _, _, _, _) => true
case n @ _ => fail(s"Unexpected match: $n")
} shouldBe expectedNumberOfContracts
}
@ -320,7 +320,7 @@ class LargeTransactionTest extends WordSpec 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

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

View File

@ -243,7 +243,7 @@ object ValueGenerators {
signatories <- genNonEmptyParties
stakeholders <- genNonEmptyParties
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]] = {
@ -278,7 +278,6 @@ object ValueGenerators {
maintainers <- genNonEmptyParties
} yield
NodeExercises(
None,
targetCoid,
templateId,
choiceId,

View File

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

View File

@ -207,7 +207,7 @@ final case class GenTransaction[Nid, +Cid, +Val](
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
}
@ -217,7 +217,7 @@ final case class GenTransaction[Nid, +Cid, +Val](
*/
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, _, _, _, _, _, _))) =>
acc + coid
@ -392,9 +392,11 @@ object Transaction {
* time.
*/
final case class Metadata(
submissionSeed: Option[crypto.Hash],
submissionTime: Time.Timestamp,
usedPackages: Set[PackageId],
dependsOnTime: Boolean
dependsOnTime: Boolean,
nodeSeeds: ImmArray[(Value.NodeId, crypto.Hash)],
)
type AbsTransaction = GenTransaction.WithTxValue[NodeId, Value.AbsoluteContractId]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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