LF: Refactor PartialTransaction Context (#8804)

This prepares the introduction of rollback node.

This is part of #8020.

CHANGELOG_BEGIN
CHANGELOG_END
This commit is contained in:
Remy 2021-02-10 14:12:57 +01:00 committed by GitHub
parent 8c45fd6afd
commit c5f0b3636c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 76 additions and 58 deletions

View File

@ -392,15 +392,15 @@ final class Conversions(
ptx.context.children.toImmArray.toSeq.sortBy(_.index).map(convertTxNodeId).asJava ptx.context.children.toImmArray.toSeq.sortBy(_.index).map(convertTxNodeId).asJava
) )
ptx.context.exeContext match { ptx.context.info match {
case None => case ctx: SPartialTransaction.ExercisesContextInfo =>
case Some(ctx) =>
val ecBuilder = proto.ExerciseContext.newBuilder val ecBuilder = proto.ExerciseContext.newBuilder
.setTargetId(mkContractRef(ctx.targetId, ctx.templateId)) .setTargetId(mkContractRef(ctx.targetId, ctx.templateId))
.setChoiceId(ctx.choiceId) .setChoiceId(ctx.choiceId)
.setChosenValue(convertValue(ctx.chosenValue)) .setChosenValue(convertValue(ctx.chosenValue))
ctx.optLocation.map(loc => ecBuilder.setExerciseLocation(convertLocation(loc))) ctx.optLocation.map(loc => ecBuilder.setExerciseLocation(convertLocation(loc)))
builder.setExerciseContext(ecBuilder.build) builder.setExerciseContext(ecBuilder.build)
case _: SPartialTransaction.RootContextInfo =>
} }
builder.build builder.build
} }

View File

@ -65,6 +65,14 @@ final class ImmArray[+A] private (
uncheckedGet(idx) uncheckedGet(idx)
} }
/** O(1) */
def get(idx: Int): Option[A] =
if (idx >= length) {
None
} else {
Some(uncheckedGet(idx))
}
private def uncheckedGet(idx: Int): A = array(start + idx) private def uncheckedGet(idx: Int): A = array(start + idx)
/** O(n) */ /** O(n) */

View File

@ -12,6 +12,7 @@ import com.daml.lf.language.Ast._
import com.daml.lf.language.{Util => AstUtil} import com.daml.lf.language.{Util => AstUtil}
import com.daml.lf.ledger.{Authorize, CheckAuthorizationMode} import com.daml.lf.ledger.{Authorize, CheckAuthorizationMode}
import com.daml.lf.speedy.Compiler.{CompilationError, PackageNotFound} import com.daml.lf.speedy.Compiler.{CompilationError, PackageNotFound}
import com.daml.lf.speedy.PartialTransaction.ExercisesContextInfo
import com.daml.lf.speedy.SError._ import com.daml.lf.speedy.SError._
import com.daml.lf.speedy.SExpr._ import com.daml.lf.speedy.SExpr._
import com.daml.lf.speedy.SResult._ import com.daml.lf.speedy.SResult._
@ -310,10 +311,10 @@ private[lf] object Speedy {
private[lf] def contextActors: Set[Party] = private[lf] def contextActors: Set[Party] =
withOnLedger("ptx") { onLedger => withOnLedger("ptx") { onLedger =>
onLedger.ptx.context.exeContext match { onLedger.ptx.context.info match {
case Some(ctx) => case ctx: ExercisesContextInfo =>
ctx.actingParties union ctx.signatories ctx.actingParties union ctx.signatories
case None => case _ =>
onLedger.committers onLedger.committers
} }
} }
@ -629,7 +630,7 @@ private[lf] object Speedy {
private[lf] def clearCommit: Unit = withOnLedger("clearCommit") { onLedger => private[lf] def clearCommit: Unit = withOnLedger("clearCommit") { onLedger =>
val freshSeed = val freshSeed =
crypto.Hash.deriveTransactionSeed( crypto.Hash.deriveTransactionSeed(
onLedger.ptx.context.nextChildrenSeed, onLedger.ptx.context.nextChildSeed,
scenarioServiceParticipant, scenarioServiceParticipant,
onLedger.ptx.submissionTime, onLedger.ptx.submissionTime,
) )

View File

@ -8,7 +8,7 @@ import com.daml.lf.ledger.Authorize
import com.daml.lf.ledger.FailedAuthorization import com.daml.lf.ledger.FailedAuthorization
import com.daml.lf.transaction.Node.{NodeCreate, NodeFetch, NodeLookupByKey} import com.daml.lf.transaction.Node.{NodeCreate, NodeFetch, NodeLookupByKey}
import PartialTransaction.ExercisesContext import PartialTransaction.ExercisesContextInfo
private[lf] object CheckAuthorization { private[lf] object CheckAuthorization {
@ -83,7 +83,7 @@ private[lf] object CheckAuthorization {
) )
} }
private[lf] def authorizeExercise(ex: ExercisesContext)( private[lf] def authorizeExercise(ex: ExercisesContextInfo)(
auth: Authorize auth: Authorize
): List[FailedAuthorization] = { ): List[FailedAuthorization] = {
authorize( authorize(

View File

@ -27,18 +27,43 @@ private[lf] object PartialTransaction {
type Node = Node.GenNode[NodeId, Value.ContractId] type Node = Node.GenNode[NodeId, Value.ContractId]
type LeafNode = Node.LeafOnlyNode[Value.ContractId] type LeafNode = Node.LeafOnlyNode[Value.ContractId]
sealed abstract class ContextInfo {
val childSeed: Int => crypto.Hash
}
sealed abstract class RootContextInfo extends ContextInfo
private[PartialTransaction] final class SeededTransactionRootContext(seed: crypto.Hash)
extends RootContextInfo {
val childSeed = crypto.Hash.deriveNodeSeed(seed, _)
}
private[PartialTransaction] final class SeededPartialTransactionRootContext(
seeds: ImmArray[Option[crypto.Hash]]
) extends RootContextInfo {
override val childSeed: Int => crypto.Hash = { idx =>
seeds.get(idx) match {
case Some(Some(value)) =>
value
case _ =>
throw new RuntimeException(s"seed for ${idx}th root node not provided")
}
}
}
private[PartialTransaction] object NoneSeededTransactionRootContext extends RootContextInfo {
val childSeed: Any => Nothing = { _ =>
throw new IllegalStateException(s"the machine is not configure to create transaction")
}
}
/** Contexts of the transaction graph builder, which we use to record /** Contexts of the transaction graph builder, which we use to record
* the sub-transaction structure due to 'exercises' statements. * the sub-transaction structure due to 'exercises' statements.
*/ */
final class Context private ( final case class Context(info: ContextInfo, children: BackStack[NodeId]) {
val exeContext: Option[ExercisesContext], // empty if root context def addChild(child: NodeId): Context = Context(info, children :+ child)
val children: BackStack[NodeId], // needs to be lazy as info.childrenSeeds may be undefined on children.length
protected val childrenSeeds: Int => crypto.Hash, def nextChildSeed: crypto.Hash = info.childSeed(children.length)
) {
def addChild(child: NodeId): Context =
new Context(exeContext, children :+ child, childrenSeeds)
def nextChildrenSeed: crypto.Hash =
childrenSeeds(children.length)
} }
object Context { object Context {
@ -46,33 +71,12 @@ private[lf] object PartialTransaction {
def apply(initialSeeds: InitialSeeding): Context = def apply(initialSeeds: InitialSeeding): Context =
initialSeeds match { initialSeeds match {
case InitialSeeding.TransactionSeed(seed) => case InitialSeeding.TransactionSeed(seed) =>
new Context(None, BackStack.empty, childrenSeeds(seed)) Context(new SeededTransactionRootContext(seed), BackStack.empty)
case InitialSeeding.RootNodeSeeds(seeds) => case InitialSeeding.RootNodeSeeds(seeds) =>
new Context( Context(new SeededPartialTransactionRootContext(seeds), BackStack.empty)
None,
BackStack.empty,
i =>
(if (0 <= i && i < seeds.length) seeds(i) else None)
.getOrElse(throw new RuntimeException(s"seed for ${i}th root node not provided")),
)
case InitialSeeding.NoSeed => case InitialSeeding.NoSeed =>
new Context( Context(NoneSeededTransactionRootContext, BackStack.empty)
None,
BackStack.empty,
_ => throw new RuntimeException(s"the machine is not configure to create transaction"),
)
} }
private[PartialTransaction] def apply(exeContext: ExercisesContext) =
new Context(
Some(exeContext),
BackStack.empty,
childrenSeeds(exeContext.parent.nextChildrenSeed),
)
private[this] def childrenSeeds(seed: crypto.Hash)(i: Int) =
crypto.Hash.deriveNodeSeed(seed, i)
} }
/** Context information to remember when building a sub-transaction /** Context information to remember when building a sub-transaction
@ -96,7 +100,7 @@ private[lf] object PartialTransaction {
* happening. * happening.
* @param byKey True if the exercise is done "by key" * @param byKey True if the exercise is done "by key"
*/ */
case class ExercisesContext( final case class ExercisesContextInfo(
targetId: Value.ContractId, targetId: Value.ContractId,
templateId: TypeConName, templateId: TypeConName,
contractKey: Option[Node.KeyWithMaintainers[Value[Nothing]]], contractKey: Option[Node.KeyWithMaintainers[Value[Nothing]]],
@ -111,7 +115,10 @@ private[lf] object PartialTransaction {
nodeId: NodeId, nodeId: NodeId,
parent: Context, parent: Context,
byKey: Boolean, byKey: Boolean,
) ) extends ContextInfo {
val childSeed =
crypto.Hash.deriveNodeSeed(parent.nextChildSeed, _)
}
def initial( def initial(
pkg2TxVersion: Ref.PackageId => TxVersion, pkg2TxVersion: Ref.PackageId => TxVersion,
@ -227,14 +234,16 @@ private[lf] case class PartialTransaction(
* the `outputTransactionVersions`. * the `outputTransactionVersions`.
*/ */
def finish: PartialTransaction.Result = def finish: PartialTransaction.Result =
if (context.exeContext.isEmpty && aborted.isEmpty) { context.info match {
case _: RootContextInfo if aborted.isEmpty =>
CompleteTransaction( CompleteTransaction(
SubmittedTransaction( SubmittedTransaction(
TxVersion.asVersionedTransaction(context.children.toImmArray, nodes) TxVersion.asVersionedTransaction(context.children.toImmArray, nodes)
) )
) )
} else case _ =>
IncompleteTransaction(this) IncompleteTransaction(this)
}
/** Extend the 'PartialTransaction' with a node for creating a /** Extend the 'PartialTransaction' with a node for creating a
* contract instance. * contract instance.
@ -256,7 +265,7 @@ private[lf] case class PartialTransaction(
.mkString(",")}""" .mkString(",")}"""
) )
} else { } else {
val nodeSeed = context.nextChildrenSeed val nodeSeed = context.nextChildSeed
val discriminator = val discriminator =
crypto.Hash.deriveContractDiscriminator(nodeSeed, submissionTime, stakeholders) crypto.Hash.deriveContractDiscriminator(nodeSeed, submissionTime, stakeholders)
val cid = Value.ContractId.V1(discriminator) val cid = Value.ContractId.V1(discriminator)
@ -365,7 +374,7 @@ private[lf] case class PartialTransaction(
} else { } else {
val nid = NodeId(nextNodeIdx) val nid = NodeId(nextNodeIdx)
val ec = val ec =
ExercisesContext( ExercisesContextInfo(
targetId = targetId, targetId = targetId,
templateId = templateId, templateId = templateId,
contractKey = mbKey, contractKey = mbKey,
@ -388,7 +397,7 @@ private[lf] case class PartialTransaction(
templateId, templateId,
copy( copy(
nextNodeIdx = nextNodeIdx + 1, nextNodeIdx = nextNodeIdx + 1,
context = Context(ec), context = Context(ec, BackStack.empty),
// important: the semantics of DAML dictate that contracts are immediately // important: the semantics of DAML dictate that contracts are immediately
// inactive as soon as you exercise it. therefore, mark it as consumed now. // inactive as soon as you exercise it. therefore, mark it as consumed now.
consumedBy = if (consuming) consumedBy.updated(targetId, nid) else consumedBy, consumedBy = if (consuming) consumedBy.updated(targetId, nid) else consumedBy,
@ -404,8 +413,8 @@ private[lf] case class PartialTransaction(
} }
def endExercises(value: Value[Value.ContractId]): PartialTransaction = def endExercises(value: Value[Value.ContractId]): PartialTransaction =
context.exeContext match { context.info match {
case Some(ec) => case ec: ExercisesContextInfo =>
val exerciseNode = Node.NodeExercises( val exerciseNode = Node.NodeExercises(
targetCoid = ec.targetId, targetCoid = ec.targetId,
templateId = ec.templateId, templateId = ec.templateId,
@ -424,13 +433,13 @@ private[lf] case class PartialTransaction(
version = packageToTransactionVersion(ec.templateId.packageId), version = packageToTransactionVersion(ec.templateId.packageId),
) )
val nodeId = ec.nodeId val nodeId = ec.nodeId
val nodeSeed = ec.parent.nextChildrenSeed val nodeSeed = ec.parent.nextChildSeed
copy( copy(
context = ec.parent.addChild(nodeId), context = ec.parent.addChild(nodeId),
nodes = nodes.updated(nodeId, exerciseNode), nodes = nodes.updated(nodeId, exerciseNode),
nodeSeeds = nodeSeeds :+ (nodeId -> nodeSeed), nodeSeeds = nodeSeeds :+ (nodeId -> nodeSeed),
) )
case None => case _ =>
noteAbort(Tx.EndExerciseInRootContext) noteAbort(Tx.EndExerciseInRootContext)
} }