Engine: Clean the output of interpretation loop. (#5597)

CHANGELOG_BEGIN
CHANGELOG_END
This commit is contained in:
Remy 2020-04-17 12:31:45 +02:00 committed by GitHub
parent 5f7d6fd6cc
commit e03bb85bac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 93 additions and 90 deletions

View File

@ -13,7 +13,6 @@ import com.daml.lf.speedy.Speedy.Machine
import com.daml.lf.speedy.SResult._
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
/**
@ -97,7 +96,7 @@ final class Engine {
submissionTime = submissionTime,
seeding = Engine.initialSeeding(submissionSeed, participantId, submissionTime),
) map {
case (tx, dependsOnTime, nodeSeeds) =>
case (tx, meta) =>
// 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
@ -111,13 +110,7 @@ final class Engine {
sys.error(s"INTERNAL ERROR: Missing dependencies of package $pkgId"))
(pkgIds + pkgId) union transitiveDeps
}
tx -> Tx.Metadata(
submissionSeed,
submissionTime,
deps,
dependsOnTime,
nodeSeeds,
)
tx -> meta.copy(submissionSeed = submissionSeed, usedPackages = deps)
}
}
}
@ -131,6 +124,9 @@ final class Engine {
* [[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.
*
* The reinterpretation does not recompute the package dependencies, so the field `usedPackages` in the
* `Tx.MetaData` component of the output is always set to `empty`.
*/
def reinterpret(
submitters: Set[Party],
@ -138,7 +134,7 @@ final class Engine {
nodeSeed: Option[crypto.Hash],
submissionTime: Time.Timestamp,
ledgerEffectiveTime: Time.Timestamp,
): Result[(Tx.Transaction, Boolean, ImmArray[(NodeId, crypto.Hash)])] =
): Result[(Tx.Transaction, Tx.Metadata)] =
for {
command <- preprocessor.translateNode(node)
checkSubmitterInMaintainers <- ShouldCheckSubmitterInMaintainers(
@ -218,7 +214,7 @@ final class Engine {
submissionTime = submissionTime,
seeding = Engine.initialSeeding(submissionSeed, participantId, submissionTime),
)
(rtx, _, _) = result
(rtx, _) = result
validationResult <- if (tx isReplayedBy rtx) {
ResultDone(())
} else {
@ -245,7 +241,7 @@ final class Engine {
ledgerTime: Time.Timestamp,
submissionTime: Time.Timestamp,
seeding: speedy.InitialSeeding,
): Result[(Transaction, Boolean, ImmArray[(Tx.NodeId, crypto.Hash)])] = {
): Result[(Tx.Transaction, Tx.Metadata)] = {
val machine = Machine
.build(
checkSubmitterInMaintainers = checkSubmitterInMaintainers,
@ -263,7 +259,7 @@ final class Engine {
private[engine] def interpretLoop(
machine: Machine,
time: Time.Timestamp
): Result[(Tx.Transaction, Boolean, ImmArray[(Tx.NodeId, crypto.Hash)])] = {
): Result[(Tx.Transaction, Tx.Metadata)] = {
while (!machine.isFinal) {
machine.step() match {
case SResultContinue =>
@ -332,7 +328,18 @@ 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, machine.ptx.nodeSeeds.toImmArray))
case Right(t) =>
ResultDone(
(
t,
Tx.Metadata(
submissionSeed = None,
submissionTime = machine.ptx.submissionTime,
usedPackages = Set.empty,
dependsOnTime = machine.dependsOnTime,
nodeSeeds = machine.ptx.nodeSeeds.toImmArray,
)))
}
}

View File

@ -433,16 +433,14 @@ 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))
val nodeSeedMap = txMeta.nodeSeeds.toSeq.toMap
val Right((rtx, _, _)) =
val Right((rtx, _)) =
reinterpret(
engine,
Set(party),
txRoots,
tx.roots.map(nodeSeedMap.get),
txMeta.submissionTime,
tx.roots,
tx,
txMeta,
let,
lookupPackage
)
@ -497,7 +495,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
seeding = seeding,
)
.consume(lookupContract, lookupPackage, lookupKey))
val Right((tx, _, nodeSeeds)) = interpretResult
val Right((tx, txMeta)) = interpretResult
"be translated" in {
val Right((rtx, _)) = engine
@ -507,17 +505,13 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
}
"reinterpret to the same result" in {
val nodeSeedMap = HashMap(nodeSeeds.toSeq: _*)
val roots = tx.roots.map(tx.nodes)
val rootSeeds = tx.roots.map(nodeSeedMap.get)
val Right((rtx, _, _)) =
val Right((rtx, _)) =
reinterpret(
engine,
Set(party),
roots,
rootSeeds,
let,
tx.roots,
tx,
txMeta,
let,
lookupPackage,
defaultContracts
@ -604,7 +598,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
seeding = seeding,
)
.consume(lookupContract, lookupPackage, lookupKey))
val Right((tx, _, nodeSeeds)) = result
val Right((tx, txMeta)) = result
"be translated" in {
val submitResult = engine
@ -615,12 +609,9 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
}
"reinterpret to the same result" in {
val roots = tx.roots.map(id => tx.nodes(id))
val nodeSeedMap = HashMap(nodeSeeds.toSeq: _*)
val rootSeeds = tx.roots.map(nodeSeedMap.get)
val reinterpretResult =
reinterpret(engine, Set(alice), roots, rootSeeds, let, let, lookupPackage, defaultContracts)
reinterpret(engine, Set(alice), tx.roots, tx, txMeta, let, lookupPackage, defaultContracts)
.map(_._1)
(result.map(_._1) |@| reinterpretResult)(_ isReplayedBy _) shouldBe Right(true)
}
@ -683,7 +674,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
)
.consume(lookupContract, lookupPackage, lookupKey))
val Right((tx, _, nodeSeeds)) = interpretResult
val Right((tx, txMeta)) = interpretResult
"be translated" in {
tx.roots should have length 2
@ -694,12 +685,9 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
}
"reinterpret to the same result" in {
val nodeSeedMap = HashMap(nodeSeeds.toSeq: _*)
val roots = tx.roots.map(tx.nodes)
val rootSeeds = tx.roots.map(nodeSeedMap.get)
val reinterpretResult =
reinterpret(engine, Set(party), roots, rootSeeds, let, let, lookupPackage)
reinterpret(engine, Set(party), tx.roots, tx, txMeta, let, lookupPackage)
.map(_._1)
(interpretResult.map(_._1) |@| reinterpretResult)(_ isReplayedBy _) shouldBe Right(true)
}
@ -901,7 +889,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val Right(cmds) = preprocessor
.preprocessCommands(ImmArray(command))
.consume(lookupContract, lookupPackage, lookupKey)
val Right((rtx, _, _)) = engine
val Right((rtx, _)) = engine
.interpretCommands(
validating = false,
checkSubmitterInMaintainers = true,
@ -921,20 +909,9 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
Blinding.checkAuthorizationAndBlind(tx, Set(bob))
"reinterpret to the same result" in {
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)
val Right((rtx, _)) =
reinterpret(engine, Set(bob), tx.roots, tx, txMeta, let, lookupPackage, defaultContracts)
(rtx isReplayedBy tx) shouldBe true
}
@ -991,7 +968,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,
@ -1145,19 +1122,18 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
"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, _, nodeSeeds)) = runExample(fetcher1Cid, clara)
val nodeSeedMap = HashMap(nodeSeeds.toSeq: _*)
val Right((tx, txMeta)) = runExample(fetcher1Cid, clara)
val fetchNodes =
tx.fold(Seq[(NodeId, GenNode.WithTxValue[NodeId, ContractId])]()) {
case (ns, (nid, n @ NodeFetch(_, _, _, _, _, _, _))) => ns :+ ((nid, n))
@ -1167,9 +1143,11 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
fetchNodes.foreach {
case (nid, n) =>
val fetchTx = GenTx(HashMap(nid -> n), ImmArray(nid))
val Right((reinterpreted, _, _)) =
val Right((reinterpreted, _)) =
engine
.reinterpret(n.requiredAuthorizers, n, nodeSeedMap.get(nid), let, let)
.reinterpret(n.requiredAuthorizers, n, txMeta.nodeSeeds.toSeq.collectFirst {
case (`nid`, seed) => seed
}, txMeta.submissionTime, let)
.consume(lookupContract, lookupPackage, lookupKey)
(fetchTx isReplayedBy reinterpreted) shouldBe true
}
@ -1287,7 +1265,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val Some((nid, lookupNode)) = firstLookupNode(tx)
lookupNode.result shouldBe Some(lookedUpCid)
val Right((reinterpreted, _, _)) =
val Right((reinterpreted, _)) =
Engine()
.reinterpret(
Set.empty,
@ -1319,7 +1297,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val Some((nid, lookupNode)) = firstLookupNode(tx)
lookupNode.result shouldBe None
val Right((reinterpreted, _, _)) =
val Right((reinterpreted, _)) =
Engine()
.reinterpret(Set.empty, lookupNode, nodeSeedMap.get(nid), txMeta.submissionTime, now)
.consume(lookupContract, lookupPackage, lookupKey)
@ -1363,7 +1341,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val cmd = speedy.Command.Fetch(BasicTests_WithKey, SValue.SContractId(fetchedCid))
val Right((tx, _, _)) = engine
val Right((tx, _)) = engine
.interpretCommands(false, false, Set(alice), ImmArray(cmd), now, now, InitialSeeding.NoSeed)
.consume(lookupContractMap.get, lookupPackage, lookupKey)
@ -1417,7 +1395,7 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
))
.consume(lookupContractMap.get, lookupPackage, lookupKey)
val Right((tx, _, _)) = engine
val Right((tx, _)) = engine
.interpretCommands(false, false, Set(alice), cmds, now, now, InitialSeeding.NoSeed)
.consume(lookupContractMap.get, lookupPackage, lookupKey)
@ -1486,14 +1464,13 @@ class EngineTest extends WordSpec with Matchers with EitherValues with BazelRunf
val NodeExercises(_, _, _, _, _, _, _, _, _, _, children, _, _) =
tx.nodes(exeNode1)
val nids = children.toSeq.take(2).toImmArray
val nodeSeedMap = HashMap(txMeta.nodeSeeds.toSeq: _*)
reinterpret(
engine,
Set(party),
nids.map(tx.nodes),
nids.map(nodeSeedMap.get),
txMeta.submissionTime,
nids,
tx,
txMeta,
let,
lookupPackage,
) shouldBe 'right
@ -1533,13 +1510,13 @@ object EngineTest {
private def reinterpret(
engine: Engine,
submitters: Set[Party],
nodes: ImmArray[GenNode.WithTxValue[Value.NodeId, Value.ContractId]],
nodeSeeds: ImmArray[Option[crypto.Hash]],
submissionTime: Time.Timestamp,
nodes: ImmArray[Tx.NodeId],
tx: Tx.Transaction,
txMeta: Tx.Metadata,
ledgerEffectiveTime: Time.Timestamp,
lookupPackages: PackageId => Option[Package],
contracts: Map[AbsoluteContractId, Tx.ContractInst[AbsoluteContractId]] = Map.empty,
): Either[Error, (Tx.Transaction, Boolean, ImmArray[(NodeId, crypto.Hash)])] = {
): Either[Error, (Tx.Transaction, Tx.Metadata)] = {
type Acc =
(
HashMap[NodeId, Tx.Node],
@ -1549,18 +1526,24 @@ object EngineTest {
Map[AbsoluteContractId, Tx.ContractInst[AbsoluteContractId]],
Map[GlobalKey, AbsoluteContractId],
)
val nodeSeedMap = txMeta.nodeSeeds.toSeq.toMap
val iterate =
(nodes zip nodeSeeds).foldLeft[Either[Error, Acc]](
nodes.foldLeft[Either[Error, Acc]](
Right((HashMap.empty, BackStack.empty, false, BackStack.empty, contracts, Map.empty))) {
case (acc, (node, nodeSeed)) =>
case (acc, nodeId) =>
for {
previousStep <- acc
(nodes, roots, dependOnTime, nodeSeeds, contracts0, keys0) = previousStep
(nodes, roots, dependsOnTime, nodeSeeds, contracts0, keys0) = previousStep
currentStep <- engine
.reinterpret(submitters, node, nodeSeed, submissionTime, ledgerEffectiveTime)
.reinterpret(
submitters,
tx.nodes(nodeId),
nodeSeedMap.get(nodeId),
txMeta.submissionTime,
ledgerEffectiveTime)
.consume(contracts0.get, lookupPackages, keys0.get)
(tr1, dependOnTime1, nodeSeeds1) = currentStep
(tr1, meta1) = currentStep
(contracts1, keys1) = tr1.fold((contracts0, keys0)) {
case (
(contracts, keys),
@ -1605,16 +1588,24 @@ object EngineTest {
(
nodes ++ tr.nodes,
roots :++ tr.roots,
dependOnTime || dependOnTime1,
nodeSeeds :++ nodeSeeds1.map { case (nid, seed) => nodeRenaming(nid) -> seed },
dependsOnTime || meta1.dependsOnTime,
nodeSeeds :++ meta1.nodeSeeds.map { case (nid, seed) => nodeRenaming(nid) -> seed },
contracts1,
keys1,
)
}
iterate.map {
case (nodes, roots, dependOnTime, nodeSeeds, _, _) =>
(GenTx(nodes, roots.toImmArray), dependOnTime, nodeSeeds.toImmArray)
case (nodes, roots, dependsOnTime, nodeSeeds, _, _) =>
(
GenTx(nodes, roots.toImmArray),
Tx.Metadata(
submissionSeed = None,
submissionTime = txMeta.submissionTime,
usedPackages = Set.empty,
dependsOnTime = dependsOnTime,
nodeSeeds = nodeSeeds.toImmArray,
))
}
}

View File

@ -382,15 +382,20 @@ object Transaction {
*/
type Transaction = GenTransaction.WithTxValue[NodeId, TContractId]
/* Transaction meta data
* @param submissionTime: submission time
* @param usedPackages The set of packages used during command processing.
* This is a hint for what packages are required to validate
* the transaction using the current interpreter.
* The used packages are not serialized using [[TransactionCoder]].
* @dependsOnTime: indicate the transaction computation depends on ledger
* time.
*/
/** Transaction meta data
* @param submissionSeed: the submission seed used to derive the contract IDs.
* If undefined no seed has been used (the legacy contract ID scheme
* have been used) or it is unknown (output of partial reinterpretation).
* @param submissionTime: the submission time
* @param usedPackages The set of packages used during command processing.
* This is a hint for what packages are required to validate
* the transaction using the current interpreter.
* If set to `empty` the package dependency have not be computed.
* @param dependsOnTime: indicate the transaction computation depends on ledger
* time.
* @param nodeSeeds: An association list that maps to each ID of create and exercise
* nodes its seeds.
*/
final case class Metadata(
submissionSeed: Option[crypto.Hash],
submissionTime: Time.Timestamp,