mirror of
https://github.com/digital-asset/daml.git
synced 2024-11-09 15:37:05 +03:00
Refactor and simplification of Transaction#processNodes
* fixes #14183 CHANGELOG_BEGIN - [DAML Studio] Refactor and simplification of Transaction#processNodes to avoid need for custom state during processing. Refactor of Transaction#processTransaction to make processing workflow more transparent and easier to unit test. See https://github.com/digital-asset/daml/issues/14183 for details. CHANGELOG_END
This commit is contained in:
parent
8c02057eb8
commit
6ed88105bb
@ -263,8 +263,7 @@ prettyErr lfVersion err = case err of
|
|||||||
prettyResult :: SS.ScenarioResult -> DA.Pretty.Doc Pretty.SyntaxClass
|
prettyResult :: SS.ScenarioResult -> DA.Pretty.Doc Pretty.SyntaxClass
|
||||||
prettyResult result =
|
prettyResult result =
|
||||||
let nTx = length (SS.scenarioResultScenarioSteps result)
|
let nTx = length (SS.scenarioResultScenarioSteps result)
|
||||||
activeContracts = S.fromList (V.toList (SS.scenarioResultActiveContracts result))
|
nActive = length $ filter (SS.isActive (SS.activeContractsFromScenarioResult result)) (V.toList (SS.scenarioResultNodes result))
|
||||||
nActive = length $ filter (SS.isActive activeContracts) (V.toList (SS.scenarioResultNodes result))
|
|
||||||
in DA.Pretty.typeDoc_ "ok, "
|
in DA.Pretty.typeDoc_ "ok, "
|
||||||
<> DA.Pretty.int nActive <> DA.Pretty.typeDoc_ " active contracts, "
|
<> DA.Pretty.int nActive <> DA.Pretty.typeDoc_ " active contracts, "
|
||||||
<> DA.Pretty.int nTx <> DA.Pretty.typeDoc_ " transactions."
|
<> DA.Pretty.int nTx <> DA.Pretty.typeDoc_ " transactions."
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
-- | Pretty-printing of scenario results
|
-- | Pretty-printing of scenario results
|
||||||
module DA.Daml.LF.PrettyScenario
|
module DA.Daml.LF.PrettyScenario
|
||||||
( prettyScenarioResult
|
( activeContractsFromScenarioResult
|
||||||
|
, prettyScenarioResult
|
||||||
, prettyScenarioError
|
, prettyScenarioError
|
||||||
, prettyBriefScenarioError
|
, prettyBriefScenarioError
|
||||||
, prettyWarningMessage
|
, prettyWarningMessage
|
||||||
@ -132,6 +133,14 @@ parseNodeId =
|
|||||||
where
|
where
|
||||||
dropHash s = fromMaybe s $ stripPrefix "#" s
|
dropHash s = fromMaybe s $ stripPrefix "#" s
|
||||||
|
|
||||||
|
activeContractsFromScenarioResult :: ScenarioResult -> S.Set TL.Text
|
||||||
|
activeContractsFromScenarioResult result =
|
||||||
|
S.fromList (V.toList (scenarioResultActiveContracts result))
|
||||||
|
|
||||||
|
activeContractsFromScenarioError :: ScenarioError -> S.Set TL.Text
|
||||||
|
activeContractsFromScenarioError err =
|
||||||
|
S.fromList (V.toList (scenarioErrorActiveContracts err))
|
||||||
|
|
||||||
prettyScenarioResult
|
prettyScenarioResult
|
||||||
:: LF.World -> S.Set TL.Text -> ScenarioResult -> Doc SyntaxClass
|
:: LF.World -> S.Set TL.Text -> ScenarioResult -> Doc SyntaxClass
|
||||||
prettyScenarioResult world activeContracts (ScenarioResult steps nodes retValue _finaltime traceLog warnings _) =
|
prettyScenarioResult world activeContracts (ScenarioResult steps nodes retValue _finaltime traceLog warnings _) =
|
||||||
@ -1094,9 +1103,8 @@ renderScenarioError world err = TL.toStrict $ Blaze.renderHtml $ do
|
|||||||
H.style $ H.text Pretty.highlightStylesheet
|
H.style $ H.text Pretty.highlightStylesheet
|
||||||
H.script "" H.! A.src "$webviewSrc"
|
H.script "" H.! A.src "$webviewSrc"
|
||||||
H.link H.! A.rel "stylesheet" H.! A.href "$webviewCss"
|
H.link H.! A.rel "stylesheet" H.! A.href "$webviewCss"
|
||||||
let activeContracts = S.fromList (V.toList (scenarioErrorActiveContracts err))
|
|
||||||
let tableView = do
|
let tableView = do
|
||||||
table <- renderTableView world activeContracts (scenarioErrorNodes err)
|
table <- renderTableView world (activeContractsFromScenarioError err) (scenarioErrorNodes err)
|
||||||
pure $ H.div H.! A.class_ "table" $ do
|
pure $ H.div H.! A.class_ "table" $ do
|
||||||
Pretty.renderHtml 128 $ annotateSC ErrorSC "Script execution failed, displaying state before failing transaction"
|
Pretty.renderHtml 128 $ annotateSC ErrorSC "Script execution failed, displaying state before failing transaction"
|
||||||
table
|
table
|
||||||
|
@ -21,7 +21,6 @@ import Value._
|
|||||||
|
|
||||||
import com.daml.scalautil.Statement.discard
|
import com.daml.scalautil.Statement.discard
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
|
||||||
import scala.collection.immutable
|
import scala.collection.immutable
|
||||||
|
|
||||||
/** An in-memory representation of a ledger for scenarios */
|
/** An in-memory representation of a ledger for scenarios */
|
||||||
@ -375,142 +374,25 @@ object ScenarioLedger {
|
|||||||
|
|
||||||
case class UniqueKeyViolation(gk: GlobalKey)
|
case class UniqueKeyViolation(gk: GlobalKey)
|
||||||
|
|
||||||
private def processTransaction(
|
/** Functions for updating the ledger with new transactional information.
|
||||||
|
*
|
||||||
|
* @param trId transaction identity
|
||||||
|
* @param richTr (enriched) transaction
|
||||||
|
* @param locationInfo location map
|
||||||
|
*/
|
||||||
|
class TransactionProcessor(
|
||||||
trId: TransactionId,
|
trId: TransactionId,
|
||||||
richTr: RichTransaction,
|
richTr: RichTransaction,
|
||||||
locationInfo: Map[NodeId, Location],
|
locationInfo: Map[NodeId, Location],
|
||||||
ledgerData: LedgerData,
|
) {
|
||||||
): Either[UniqueKeyViolation, LedgerData] = {
|
|
||||||
|
|
||||||
final case class RollbackBeginState(
|
def duplicateKeyCheck(ledgerData: LedgerData): Either[UniqueKeyViolation, Unit] = {
|
||||||
activeContracts: Set[ContractId],
|
val inactiveKeys = richTr.transaction.contractKeyInputs
|
||||||
activeKeys: Map[GlobalKey, ContractId],
|
.fold(error => crash(s"$error: inconsistent transaction"), identity)
|
||||||
)
|
.collect { case (key, _: Tx.KeyInactive) =>
|
||||||
|
key
|
||||||
final case class ProcessingNode(
|
|
||||||
// The id of the direct parent or None.
|
|
||||||
mbParentId: Option[NodeId],
|
|
||||||
// The id of the nearest rollback ancestor. If this is
|
|
||||||
// a rollback node itself, it points to itself.
|
|
||||||
mbRollbackAncestorId: Option[NodeId],
|
|
||||||
children: List[NodeId],
|
|
||||||
// For rollback nodes, we store the previous state here and restore it.
|
|
||||||
// For exercise nodes, we don’t need to restore anything.
|
|
||||||
prevState: Option[RollbackBeginState],
|
|
||||||
)
|
|
||||||
|
|
||||||
@tailrec
|
|
||||||
def processNodes(
|
|
||||||
cache0: LedgerData,
|
|
||||||
enps: List[ProcessingNode],
|
|
||||||
): LedgerData = enps match {
|
|
||||||
case Nil => cache0
|
|
||||||
case ProcessingNode(_, _, Nil, optPrevState) :: restENPs => {
|
|
||||||
val cache1 = optPrevState.fold(cache0) { case prevState =>
|
|
||||||
cache0.copy(
|
|
||||||
activeContracts = prevState.activeContracts,
|
|
||||||
activeKeys = prevState.activeKeys,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
processNodes(cache1, restENPs)
|
|
||||||
}
|
|
||||||
case (processingNode @ ProcessingNode(
|
|
||||||
mbParentId,
|
|
||||||
mbRollbackAncestorId,
|
|
||||||
nodeId :: restOfNodeIds,
|
|
||||||
optPrevState,
|
|
||||||
)) :: restENPs =>
|
|
||||||
val eventId = EventId(trId.id, nodeId)
|
|
||||||
richTr.transaction.nodes.get(nodeId) match {
|
|
||||||
case None =>
|
|
||||||
crash(s"processTransaction: non-existent node '$eventId'.")
|
|
||||||
case Some(node) =>
|
|
||||||
val newLedgerNodeInfo = LedgerNodeInfo(
|
|
||||||
node = node,
|
|
||||||
optLocation = locationInfo.get(nodeId),
|
|
||||||
transaction = trId,
|
|
||||||
effectiveAt = richTr.effectiveAt,
|
|
||||||
disclosures = Map.empty,
|
|
||||||
referencedBy = Set.empty,
|
|
||||||
consumedBy = None,
|
|
||||||
rolledbackBy = mbRollbackAncestorId,
|
|
||||||
parent = mbParentId.map(EventId(trId.id, _)),
|
|
||||||
)
|
|
||||||
val newCache =
|
|
||||||
cache0.copy(nodeInfos = cache0.nodeInfos + (eventId -> newLedgerNodeInfo))
|
|
||||||
val idsToProcess = processingNode.copy(children = restOfNodeIds) :: restENPs
|
|
||||||
|
|
||||||
node match {
|
|
||||||
case rollback: Node.Rollback =>
|
|
||||||
val rollbackState =
|
|
||||||
RollbackBeginState(newCache.activeContracts, newCache.activeKeys)
|
|
||||||
processNodes(
|
|
||||||
newCache,
|
|
||||||
ProcessingNode(
|
|
||||||
Some(nodeId),
|
|
||||||
Some(nodeId),
|
|
||||||
rollback.children.toList,
|
|
||||||
Some(rollbackState),
|
|
||||||
) :: idsToProcess,
|
|
||||||
)
|
|
||||||
|
|
||||||
case nc: Node.Create =>
|
|
||||||
val newCache1 = newCache.createdIn(nc.coid, eventId)
|
|
||||||
processNodes(newCache1, idsToProcess)
|
|
||||||
|
|
||||||
case Node.Fetch(referencedCoid, templateId @ _, _, _, _, _, _, _) =>
|
|
||||||
val newCacheP =
|
|
||||||
newCache.updateLedgerNodeInfo(referencedCoid)(info =>
|
|
||||||
info.copy(referencedBy = info.referencedBy + eventId)
|
|
||||||
)
|
|
||||||
|
|
||||||
processNodes(newCacheP, idsToProcess)
|
|
||||||
|
|
||||||
case ex: Node.Exercise =>
|
|
||||||
val newCache0 =
|
|
||||||
newCache.updateLedgerNodeInfo(ex.targetCoid)(info =>
|
|
||||||
info.copy(
|
|
||||||
referencedBy = info.referencedBy + eventId,
|
|
||||||
consumedBy = optPrevState match {
|
|
||||||
// consuming exercise outside a rollback node
|
|
||||||
case None if ex.consuming => Some(eventId)
|
|
||||||
case _ => info.consumedBy
|
|
||||||
},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
processNodes(
|
|
||||||
newCache0,
|
|
||||||
ProcessingNode(
|
|
||||||
Some(nodeId),
|
|
||||||
mbRollbackAncestorId,
|
|
||||||
ex.children.toList,
|
|
||||||
None,
|
|
||||||
) :: idsToProcess,
|
|
||||||
)
|
|
||||||
|
|
||||||
case nlkup: Node.LookupByKey =>
|
|
||||||
nlkup.result match {
|
|
||||||
case None =>
|
|
||||||
processNodes(newCache, idsToProcess)
|
|
||||||
case Some(referencedCoid) =>
|
|
||||||
val newCacheP =
|
|
||||||
newCache.updateLedgerNodeInfo(referencedCoid)(info =>
|
|
||||||
info.copy(referencedBy = info.referencedBy + eventId)
|
|
||||||
)
|
|
||||||
|
|
||||||
processNodes(newCacheP, idsToProcess)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val inactiveKeys = richTr.transaction.contractKeyInputs
|
|
||||||
.fold(error => crash(s"$error: inconsistent transaction"), identity)
|
|
||||||
.collect { case (key, _: Tx.KeyInactive) =>
|
|
||||||
key
|
|
||||||
}
|
|
||||||
val duplicateKeyCheck: Either[UniqueKeyViolation, Unit] =
|
|
||||||
inactiveKeys.find(ledgerData.activeKeys.contains(_)) match {
|
inactiveKeys.find(ledgerData.activeKeys.contains(_)) match {
|
||||||
case Some(duplicateKey) =>
|
case Some(duplicateKey) =>
|
||||||
Left(UniqueKeyViolation(duplicateKey))
|
Left(UniqueKeyViolation(duplicateKey))
|
||||||
@ -518,45 +400,185 @@ object ScenarioLedger {
|
|||||||
case None =>
|
case None =>
|
||||||
Right(())
|
Right(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
def addNewLedgerNodes(historicalLedgerData: LedgerData): LedgerData =
|
||||||
_ <- duplicateKeyCheck
|
richTr.transaction.transaction.fold[LedgerData](historicalLedgerData) {
|
||||||
} yield {
|
case (ledgerData, (nodeId, node)) =>
|
||||||
val cacheAfterProcess = processNodes(
|
val eventId = EventId(trId.id, nodeId)
|
||||||
ledgerData,
|
val newLedgerNodeInfo = LedgerNodeInfo(
|
||||||
List(ProcessingNode(None, None, richTr.transaction.roots.toList, None)),
|
node = node,
|
||||||
|
optLocation = locationInfo.get(nodeId),
|
||||||
|
transaction = trId,
|
||||||
|
effectiveAt = richTr.effectiveAt,
|
||||||
|
// Following fields will be updated by additional calls to node processing code
|
||||||
|
disclosures = Map.empty,
|
||||||
|
referencedBy = Set.empty,
|
||||||
|
consumedBy = None,
|
||||||
|
rolledbackBy = None,
|
||||||
|
parent = None,
|
||||||
|
)
|
||||||
|
|
||||||
|
ledgerData.copy(nodeInfos = ledgerData.nodeInfos + (eventId -> newLedgerNodeInfo))
|
||||||
|
}
|
||||||
|
|
||||||
|
def createdInAndReferenceByUpdates(historicalLedgerData: LedgerData): LedgerData =
|
||||||
|
richTr.transaction.transaction.fold[LedgerData](historicalLedgerData) {
|
||||||
|
case (ledgerData, (nodeId, createNode: Node.Create)) =>
|
||||||
|
ledgerData.createdIn(createNode.coid, EventId(trId.id, nodeId))
|
||||||
|
|
||||||
|
case (ledgerData, (nodeId, exerciseNode: Node.Exercise)) =>
|
||||||
|
ledgerData.updateLedgerNodeInfo(exerciseNode.targetCoid)(ledgerNodeInfo =>
|
||||||
|
ledgerNodeInfo.copy(referencedBy =
|
||||||
|
ledgerNodeInfo.referencedBy + EventId(trId.id, nodeId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
case (ledgerData, (nodeId, fetchNode: Node.Fetch)) =>
|
||||||
|
ledgerData.updateLedgerNodeInfo(fetchNode.coid)(ledgerNodeInfo =>
|
||||||
|
ledgerNodeInfo.copy(referencedBy =
|
||||||
|
ledgerNodeInfo.referencedBy + EventId(trId.id, nodeId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
case (ledgerData, (nodeId, lookupNode: Node.LookupByKey)) =>
|
||||||
|
lookupNode.result match {
|
||||||
|
case None =>
|
||||||
|
ledgerData
|
||||||
|
|
||||||
|
case Some(referencedCoid) =>
|
||||||
|
ledgerData.updateLedgerNodeInfo(referencedCoid)(ledgerNodeInfo =>
|
||||||
|
ledgerNodeInfo.copy(referencedBy =
|
||||||
|
ledgerNodeInfo.referencedBy + EventId(trId.id, nodeId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
case (ledgerData, (_, _: Node)) =>
|
||||||
|
ledgerData
|
||||||
|
}
|
||||||
|
|
||||||
|
def parentUpdates(historicalLedgerData: LedgerData): LedgerData =
|
||||||
|
richTr.transaction.transaction.fold[LedgerData](historicalLedgerData) {
|
||||||
|
case (ledgerData, (nodeId, exerciseNode: Node.Exercise)) =>
|
||||||
|
exerciseNode.children.foldLeft[LedgerData](ledgerData) {
|
||||||
|
case (updatedLedgerData, childNodeId) =>
|
||||||
|
updatedLedgerData.updateLedgerNodeInfo(EventId(trId.id, childNodeId))(
|
||||||
|
ledgerNodeInfo => ledgerNodeInfo.copy(parent = Some(EventId(trId.id, nodeId)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
case (ledgerData, (nodeId, rollbackNode: Node.Rollback)) =>
|
||||||
|
rollbackNode.children.foldLeft[LedgerData](ledgerData) {
|
||||||
|
case (updatedLedgerData, childNodeId) =>
|
||||||
|
updatedLedgerData.updateLedgerNodeInfo(EventId(trId.id, childNodeId))(
|
||||||
|
ledgerNodeInfo => ledgerNodeInfo.copy(parent = Some(EventId(trId.id, nodeId)))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
case (ledgerData, (_, _: Node)) =>
|
||||||
|
ledgerData
|
||||||
|
}
|
||||||
|
|
||||||
|
def consumedByUpdates(ledgerData: LedgerData): LedgerData = {
|
||||||
|
var ledgerDataResult = ledgerData
|
||||||
|
|
||||||
|
for ((contractId, nodeId) <- richTr.transaction.transaction.consumedBy) {
|
||||||
|
ledgerDataResult = ledgerDataResult.updateLedgerNodeInfo(contractId) { ledgerNodeInfo =>
|
||||||
|
ledgerNodeInfo.copy(consumedBy = Some(EventId(trId.id, nodeId)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ledgerDataResult
|
||||||
|
}
|
||||||
|
|
||||||
|
def rolledbackByUpdates(ledgerData: LedgerData): LedgerData = {
|
||||||
|
var ledgerDataResult = ledgerData
|
||||||
|
|
||||||
|
for ((nodeId, rollbackNodeId) <- richTr.transaction.transaction.rolledbackBy) {
|
||||||
|
ledgerDataResult = ledgerDataResult.updateLedgerNodeInfo(EventId(trId.id, nodeId)) {
|
||||||
|
ledgerNodeInfo =>
|
||||||
|
ledgerNodeInfo.copy(rolledbackBy = Some(rollbackNodeId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ledgerDataResult
|
||||||
|
}
|
||||||
|
|
||||||
|
def activeContractAndKeyUpdates(ledgerData: LedgerData): LedgerData = {
|
||||||
|
ledgerData.copy(
|
||||||
|
activeContracts =
|
||||||
|
ledgerData.activeContracts ++ richTr.transaction.localContracts.keySet -- richTr.transaction.inactiveContracts,
|
||||||
|
activeKeys = richTr.transaction.updatedContractKeys.foldLeft(ledgerData.activeKeys) {
|
||||||
|
case (activeKeys, (key, Some(cid))) =>
|
||||||
|
activeKeys + (key -> cid)
|
||||||
|
|
||||||
|
case (activeKeys, (key, None)) =>
|
||||||
|
activeKeys - key
|
||||||
|
},
|
||||||
)
|
)
|
||||||
val cacheActiveness =
|
}
|
||||||
cacheAfterProcess.copy(
|
|
||||||
activeContracts =
|
def disclosureUpdates(ledgerData: LedgerData): LedgerData = {
|
||||||
cacheAfterProcess.activeContracts ++ richTr.transaction.localContracts.keySet -- richTr.transaction.inactiveContracts,
|
|
||||||
activeKeys =
|
|
||||||
richTr.transaction.updatedContractKeys.foldLeft(cacheAfterProcess.activeKeys) {
|
|
||||||
case (activeKs, (key, Some(cid))) =>
|
|
||||||
activeKs + (key -> cid)
|
|
||||||
case (activeKs, (key, None)) =>
|
|
||||||
activeKs - key
|
|
||||||
},
|
|
||||||
)
|
|
||||||
// NOTE(MH): Since `addDisclosures` is biased towards existing
|
// NOTE(MH): Since `addDisclosures` is biased towards existing
|
||||||
// disclosures, we need to add the "stronger" explicit ones first.
|
// disclosures, we need to add the "stronger" explicit ones first.
|
||||||
val cacheWithExplicitDisclosures =
|
richTr.blindingInfo.disclosure.foldLeft(ledgerData) { case (cacheP, (nodeId, witnesses)) =>
|
||||||
richTr.blindingInfo.disclosure.foldLeft(cacheActiveness) {
|
cacheP.updateLedgerNodeInfo(EventId(richTr.transactionId, nodeId))(
|
||||||
case (cacheP, (nodeId, witnesses)) =>
|
_.addDisclosures(witnesses.map(_ -> Disclosure(since = trId, explicit = true)).toMap)
|
||||||
cacheP.updateLedgerNodeInfo(EventId(richTr.transactionId, nodeId))(
|
)
|
||||||
_.addDisclosures(witnesses.map(_ -> Disclosure(since = trId, explicit = true)).toMap)
|
}
|
||||||
)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
richTr.blindingInfo.divulgence.foldLeft(cacheWithExplicitDisclosures) {
|
def divulgenceUpdates(ledgerData: LedgerData): LedgerData = {
|
||||||
case (cacheP, (coid, divulgees)) =>
|
richTr.blindingInfo.divulgence.foldLeft(ledgerData) { case (cacheP, (coid, divulgees)) =>
|
||||||
cacheP.updateLedgerNodeInfo(cacheWithExplicitDisclosures.coidToNodeId(coid))(
|
cacheP.updateLedgerNodeInfo(ledgerData.coidToNodeId(coid))(
|
||||||
_.addDisclosures(divulgees.map(_ -> Disclosure(since = trId, explicit = false)).toMap)
|
_.addDisclosures(divulgees.map(_ -> Disclosure(since = trId, explicit = false)).toMap)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Update the ledger (which records information on all historical transactions) with new transaction information.
|
||||||
|
*
|
||||||
|
* @param trId transaction identity
|
||||||
|
* @param richTr (enriched) transaction
|
||||||
|
* @param locationInfo location map
|
||||||
|
* @param ledgerData ledger recording all historical transaction that have been processed
|
||||||
|
* @return updated ledger with new transaction information
|
||||||
|
*/
|
||||||
|
private def processTransaction(
|
||||||
|
trId: TransactionId,
|
||||||
|
richTr: RichTransaction,
|
||||||
|
locationInfo: Map[NodeId, Location],
|
||||||
|
ledgerData: LedgerData,
|
||||||
|
): Either[UniqueKeyViolation, LedgerData] = {
|
||||||
|
|
||||||
|
val processor: TransactionProcessor = new TransactionProcessor(trId, richTr, locationInfo)
|
||||||
|
|
||||||
|
for {
|
||||||
|
_ <- processor.duplicateKeyCheck(ledgerData)
|
||||||
|
} yield {
|
||||||
|
// Update ledger data with new transaction node information *before* performing any other updates
|
||||||
|
var cachedLedgerData: LedgerData = processor.addNewLedgerNodes(ledgerData)
|
||||||
|
|
||||||
|
// Update ledger data with any new created in and referenced by information
|
||||||
|
cachedLedgerData = processor.createdInAndReferenceByUpdates(cachedLedgerData)
|
||||||
|
// Update ledger data with any new parent information
|
||||||
|
cachedLedgerData = processor.parentUpdates(cachedLedgerData)
|
||||||
|
// Update ledger data with any new consumed by information
|
||||||
|
cachedLedgerData = processor.consumedByUpdates(cachedLedgerData)
|
||||||
|
// Update ledger data with any new rolled back by information
|
||||||
|
cachedLedgerData = processor.rolledbackByUpdates(cachedLedgerData)
|
||||||
|
// Update ledger data with any new active contract information
|
||||||
|
cachedLedgerData = processor.activeContractAndKeyUpdates(cachedLedgerData)
|
||||||
|
// Update ledger data with any new disclosure information
|
||||||
|
cachedLedgerData = processor.disclosureUpdates(cachedLedgerData)
|
||||||
|
// Update ledger data with any new divulgence information
|
||||||
|
cachedLedgerData = processor.divulgenceUpdates(cachedLedgerData)
|
||||||
|
|
||||||
|
cachedLedgerData
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------
|
// ----------------------------------------------------------------
|
||||||
|
@ -52,6 +52,7 @@ final case class VersionedTransaction private[lf] (
|
|||||||
*
|
*
|
||||||
* @param nodes The nodes of this transaction.
|
* @param nodes The nodes of this transaction.
|
||||||
* @param roots References to the root nodes of the transaction.
|
* @param roots References to the root nodes of the transaction.
|
||||||
|
*
|
||||||
* Users of this class may assume that all instances are well-formed, i.e., `isWellFormed.isEmpty`.
|
* Users of this class may assume that all instances are well-formed, i.e., `isWellFormed.isEmpty`.
|
||||||
* For performance reasons, users are not required to call `isWellFormed`.
|
* For performance reasons, users are not required to call `isWellFormed`.
|
||||||
* Therefore, it is '''forbidden''' to create ill-formed instances, i.e., instances with `!isWellFormed.isEmpty`.
|
* Therefore, it is '''forbidden''' to create ill-formed instances, i.e., instances with `!isWellFormed.isEmpty`.
|
||||||
@ -255,7 +256,7 @@ sealed abstract class HasTxNodes {
|
|||||||
|
|
||||||
def roots: ImmArray[NodeId]
|
def roots: ImmArray[NodeId]
|
||||||
|
|
||||||
/** The union of the informees of a all the action nodes. */
|
/** The union of the informees of all the action nodes. */
|
||||||
lazy val informees: Set[Ref.Party] =
|
lazy val informees: Set[Ref.Party] =
|
||||||
nodes.values.foldLeft(Set.empty[Ref.Party]) {
|
nodes.values.foldLeft(Set.empty[Ref.Party]) {
|
||||||
case (acc, node: Node.Action) => acc | node.informeesOfNode
|
case (acc, node: Node.Action) => acc | node.informeesOfNode
|
||||||
@ -350,8 +351,8 @@ sealed abstract class HasTxNodes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the IDs of all the consumed contracts.
|
/** Returns the IDs of all the consumed contracts.
|
||||||
* This includes transient contracts but it does not include contracts
|
* This includes transient contracts but it does not include contracts
|
||||||
* consumed in rollback nodes.
|
* consumed in rollback nodes.
|
||||||
*/
|
*/
|
||||||
final def consumedContracts[Cid2 >: ContractId]: Set[Cid2] =
|
final def consumedContracts[Cid2 >: ContractId]: Set[Cid2] =
|
||||||
foldInExecutionOrder(Set.empty[Cid2])(
|
foldInExecutionOrder(Set.empty[Cid2])(
|
||||||
@ -465,6 +466,60 @@ sealed abstract class HasTxNodes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Keys are contracts (that have been consumed) and values are the nodes where the contract was consumed.
|
||||||
|
* Nodes under rollbacks (both exercises and creates) are ignored (as they have been rolled back).
|
||||||
|
* The result includes both local contracts created in the transaction (if they’ve been consumed) as well as global
|
||||||
|
* contracts created in previous transactions. It does not include local contracts created under a rollback.
|
||||||
|
*/
|
||||||
|
final def consumedBy: Map[ContractId, NodeId] =
|
||||||
|
foldInExecutionOrder[Map[ContractId, NodeId]](HashMap.empty)(
|
||||||
|
exerciseBegin = (consumedByMap, nodeId, exerciseNode) => {
|
||||||
|
if (exerciseNode.consuming) {
|
||||||
|
(consumedByMap + (exerciseNode.targetCoid -> nodeId), ChildrenRecursion.DoRecurse)
|
||||||
|
} else {
|
||||||
|
(consumedByMap, ChildrenRecursion.DoRecurse)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rollbackBegin = (consumedByMap, _, _) => {
|
||||||
|
(consumedByMap, ChildrenRecursion.DoNotRecurse)
|
||||||
|
},
|
||||||
|
leaf = (consumedByMap, _, _) => consumedByMap,
|
||||||
|
exerciseEnd = (consumedByMap, _, _) => consumedByMap,
|
||||||
|
rollbackEnd = (consumedByMap, _, _) => consumedByMap,
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Keys are nodes under a rollback and values are the "nearest" (i.e. most recent) rollback node.
|
||||||
|
*/
|
||||||
|
final def rolledbackBy: Map[NodeId, NodeId] = {
|
||||||
|
val rolledbackByMapUpdate
|
||||||
|
: ((Map[NodeId, NodeId], Seq[NodeId]), NodeId) => (Map[NodeId, NodeId], Seq[NodeId]) = {
|
||||||
|
case ((rolledbackMap, rollbackStack @ (rollbackNode +: _)), nodeId) =>
|
||||||
|
(rolledbackMap + (nodeId -> rollbackNode), rollbackStack)
|
||||||
|
|
||||||
|
case (state, _) =>
|
||||||
|
state
|
||||||
|
}
|
||||||
|
|
||||||
|
foldInExecutionOrder[(Map[NodeId, NodeId], Seq[NodeId])]((HashMap.empty, Vector.empty))(
|
||||||
|
exerciseBegin =
|
||||||
|
(state, nodeId, _) => (rolledbackByMapUpdate(state, nodeId), ChildrenRecursion.DoRecurse),
|
||||||
|
rollbackBegin = { case ((rolledbackMap, rollbackStack), nodeId, _) =>
|
||||||
|
((rolledbackMap, nodeId +: rollbackStack), ChildrenRecursion.DoRecurse)
|
||||||
|
},
|
||||||
|
leaf = (state, nodeId, _) => rolledbackByMapUpdate(state, nodeId),
|
||||||
|
exerciseEnd = (state, _, _) => state,
|
||||||
|
rollbackEnd = {
|
||||||
|
case ((rolledbackMap, _ +: rollbackStack), _, _) =>
|
||||||
|
(rolledbackMap, rollbackStack)
|
||||||
|
|
||||||
|
case _ =>
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Impossible case: rollbackBegin should already have pushed to the rollback stack"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)._1
|
||||||
|
}
|
||||||
|
|
||||||
/** Return the expected contract key inputs (i.e. the state before the transaction)
|
/** Return the expected contract key inputs (i.e. the state before the transaction)
|
||||||
* for this transaction or an error if the transaction contains a
|
* for this transaction or an error if the transaction contains a
|
||||||
* duplicate key error or has an inconsistent mapping for a key. For
|
* duplicate key error or has an inconsistent mapping for a key. For
|
||||||
|
@ -675,6 +675,363 @@ class TransactionSpec
|
|||||||
Map(key("key0") -> Some(cid0), key("key1") -> None, key("key2") -> Some(cid3))
|
Map(key("key0") -> Some(cid0), key("key1") -> None, key("key2") -> Some(cid3))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"consumedBy and rolledbackBy" - {
|
||||||
|
"non-consuming transaction with no rollbacks" - {
|
||||||
|
"no nodes" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe Map.empty
|
||||||
|
transaction.rolledbackBy shouldBe Map.empty
|
||||||
|
}
|
||||||
|
|
||||||
|
"one node" - {
|
||||||
|
"with local contracts" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val parties = Seq("Alice")
|
||||||
|
val (_, createNode0) = create(builder, parties, Some("key0"))
|
||||||
|
|
||||||
|
builder.add(createNode0)
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe Map.empty
|
||||||
|
transaction.rolledbackBy shouldBe Map.empty
|
||||||
|
}
|
||||||
|
|
||||||
|
"with global contracts" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val parties = Seq("Alice")
|
||||||
|
val (_, createNode0) = create(builder, parties, Some("key0"))
|
||||||
|
val fetchNode0 = builder.fetch(createNode0, true)
|
||||||
|
|
||||||
|
builder.add(fetchNode0)
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe Map.empty
|
||||||
|
transaction.rolledbackBy shouldBe Map.empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"multiple nodes" - {
|
||||||
|
"only create nodes" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val parties = Seq("Alice")
|
||||||
|
val (_, createNode0) = create(builder, parties, Some("key0"))
|
||||||
|
val (_, createNode1) = create(builder, parties, Some("key1"))
|
||||||
|
|
||||||
|
builder.add(createNode0)
|
||||||
|
builder.add(createNode1)
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe Map.empty
|
||||||
|
transaction.rolledbackBy shouldBe Map.empty
|
||||||
|
}
|
||||||
|
|
||||||
|
"create and non-consuming exercise nodes" - {
|
||||||
|
"with local contracts" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val parties = Seq("Alice")
|
||||||
|
val (_, createNode0) = create(builder, parties, Some("key0"))
|
||||||
|
|
||||||
|
builder.add(createNode0)
|
||||||
|
builder.add(exercise(builder, createNode0, parties, false))
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe Map.empty
|
||||||
|
transaction.rolledbackBy shouldBe Map.empty
|
||||||
|
}
|
||||||
|
|
||||||
|
"with global contracts" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val parties = Seq("Alice")
|
||||||
|
val (_, createNode0) = create(builder, parties, Some("key0"))
|
||||||
|
|
||||||
|
builder.add(exercise(builder, createNode0, parties, false))
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe Map.empty
|
||||||
|
transaction.rolledbackBy shouldBe Map.empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"consuming transaction with no rollbacks" - {
|
||||||
|
"one excercise" - {
|
||||||
|
"with local contracts" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val parties = Seq("Alice")
|
||||||
|
val (cid0, createNode0) = create(builder, parties, Some("key0"))
|
||||||
|
|
||||||
|
builder.add(createNode0)
|
||||||
|
val exerciseId0 = builder.add(exercise(builder, createNode0, parties, true))
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe
|
||||||
|
Map(cid0 -> exerciseId0)
|
||||||
|
transaction.rolledbackBy shouldBe Map.empty
|
||||||
|
}
|
||||||
|
|
||||||
|
"with global contracts" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val parties = Seq("Alice")
|
||||||
|
val (cid0, createNode0) = create(builder, parties, Some("key0"))
|
||||||
|
|
||||||
|
val exerciseId0 = builder.add(exercise(builder, createNode0, parties, true))
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe
|
||||||
|
Map(cid0 -> exerciseId0)
|
||||||
|
transaction.rolledbackBy shouldBe Map.empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"multiple exercises" - {
|
||||||
|
"with local contracts" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val parties = Seq("Alice")
|
||||||
|
val (cid0, createNode0) = create(builder, parties, Some("key0"))
|
||||||
|
val (cid1, createNode1) = create(builder, parties, Some("key1"))
|
||||||
|
|
||||||
|
builder.add(createNode0)
|
||||||
|
builder.add(createNode1)
|
||||||
|
val exerciseId0 = builder.add(exercise(builder, createNode0, parties, true))
|
||||||
|
val exerciseId1 = builder.add(exercise(builder, createNode1, parties, true))
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe
|
||||||
|
Map(cid0 -> exerciseId0, cid1 -> exerciseId1)
|
||||||
|
transaction.rolledbackBy shouldBe Map.empty
|
||||||
|
}
|
||||||
|
|
||||||
|
"with global contracts" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val parties = Seq("Alice")
|
||||||
|
val (cid0, createNode0) = create(builder, parties, Some("key0"))
|
||||||
|
val (cid1, createNode1) = create(builder, parties, Some("key1"))
|
||||||
|
|
||||||
|
val exerciseId0 = builder.add(exercise(builder, createNode0, parties, true))
|
||||||
|
val exerciseId1 = builder.add(exercise(builder, createNode1, parties, true))
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe
|
||||||
|
Map(cid0 -> exerciseId0, cid1 -> exerciseId1)
|
||||||
|
transaction.rolledbackBy shouldBe Map.empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"consuming transaction with rollbacks" - {
|
||||||
|
"one rollback" - {
|
||||||
|
"with local contracts" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val parties = Seq("Alice")
|
||||||
|
val (cid0, createNode0) = create(builder, parties, Some("key0"))
|
||||||
|
val (_, createNode1) = create(builder, parties, Some("key1"))
|
||||||
|
|
||||||
|
builder.add(createNode0)
|
||||||
|
builder.add(createNode1)
|
||||||
|
val nodeId0 = builder.add(exercise(builder, createNode0, parties, true))
|
||||||
|
val rollbackId = builder.add(builder.rollback())
|
||||||
|
val nodeId1 = builder.add(exercise(builder, createNode1, parties, true), rollbackId)
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe
|
||||||
|
Map(cid0 -> nodeId0)
|
||||||
|
transaction.rolledbackBy shouldBe
|
||||||
|
Map(nodeId1 -> rollbackId)
|
||||||
|
}
|
||||||
|
|
||||||
|
"with global contracts" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val parties = Seq("Alice")
|
||||||
|
val (cid0, createNode0) = create(builder, parties, Some("key0"))
|
||||||
|
val (_, createNode1) = create(builder, parties, Some("key1"))
|
||||||
|
|
||||||
|
val nodeId0 = builder.add(exercise(builder, createNode0, parties, true))
|
||||||
|
val rollbackId = builder.add(builder.rollback())
|
||||||
|
val nodeId1 = builder.add(exercise(builder, createNode1, parties, true), rollbackId)
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe
|
||||||
|
Map(cid0 -> nodeId0)
|
||||||
|
transaction.rolledbackBy shouldBe
|
||||||
|
Map(nodeId1 -> rollbackId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"multiple rollbacks" - {
|
||||||
|
"sequential rollbacks" - {
|
||||||
|
"with local wontracts" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val parties = Seq("Alice")
|
||||||
|
val (_, createNode0) = create(builder, parties, Some("key0"))
|
||||||
|
val (_, createNode1) = create(builder, parties, Some("key1"))
|
||||||
|
|
||||||
|
builder.add(createNode0)
|
||||||
|
builder.add(createNode1)
|
||||||
|
val rollbackId0 = builder.add(builder.rollback())
|
||||||
|
val nodeId0 = builder.add(exercise(builder, createNode0, parties, true), rollbackId0)
|
||||||
|
val rollbackId1 = builder.add(builder.rollback())
|
||||||
|
val nodeId1 = builder.add(exercise(builder, createNode1, parties, true), rollbackId1)
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe Map.empty
|
||||||
|
transaction.rolledbackBy shouldBe
|
||||||
|
Map(nodeId0 -> rollbackId0, nodeId1 -> rollbackId1)
|
||||||
|
}
|
||||||
|
|
||||||
|
"with global contracts" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val parties = Seq("Alice")
|
||||||
|
val (_, createNode0) = create(builder, parties, Some("key0"))
|
||||||
|
val (_, createNode1) = create(builder, parties, Some("key1"))
|
||||||
|
|
||||||
|
val rollbackId0 = builder.add(builder.rollback())
|
||||||
|
val nodeId0 = builder.add(exercise(builder, createNode0, parties, true), rollbackId0)
|
||||||
|
val rollbackId1 = builder.add(builder.rollback())
|
||||||
|
val nodeId1 = builder.add(exercise(builder, createNode1, parties, true), rollbackId1)
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe Map.empty
|
||||||
|
transaction.rolledbackBy shouldBe
|
||||||
|
Map(nodeId0 -> rollbackId0, nodeId1 -> rollbackId1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"nested rollbacks" - {
|
||||||
|
"2 deep and 2 rollbacks" - {
|
||||||
|
"with local contracts" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val parties = Seq("Alice")
|
||||||
|
val (_, createNode0) = create(builder, parties, Some("key0"))
|
||||||
|
val (_, createNode1) = create(builder, parties, Some("key1"))
|
||||||
|
|
||||||
|
builder.add(createNode0)
|
||||||
|
builder.add(createNode1)
|
||||||
|
val rollbackId0 = builder.add(builder.rollback())
|
||||||
|
val nodeId0 = builder.add(exercise(builder, createNode0, parties, true), rollbackId0)
|
||||||
|
val rollbackId1 = builder.add(builder.rollback(), rollbackId0)
|
||||||
|
val nodeId1 = builder.add(exercise(builder, createNode1, parties, true), rollbackId1)
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe Map.empty
|
||||||
|
transaction.rolledbackBy shouldBe
|
||||||
|
Map(nodeId0 -> rollbackId0, nodeId1 -> rollbackId1)
|
||||||
|
}
|
||||||
|
|
||||||
|
"with global contracts" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val parties = Seq("Alice")
|
||||||
|
val (_, createNode0) = create(builder, parties, Some("key0"))
|
||||||
|
val (_, createNode1) = create(builder, parties, Some("key1"))
|
||||||
|
|
||||||
|
val rollbackId0 = builder.add(builder.rollback())
|
||||||
|
val nodeId0 = builder.add(exercise(builder, createNode0, parties, true), rollbackId0)
|
||||||
|
val rollbackId1 = builder.add(builder.rollback(), rollbackId0)
|
||||||
|
val nodeId1 = builder.add(exercise(builder, createNode1, parties, true), rollbackId1)
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe Map.empty
|
||||||
|
transaction.rolledbackBy shouldBe
|
||||||
|
Map(nodeId0 -> rollbackId0, nodeId1 -> rollbackId1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"2 deep and 3 rollbacks" - {
|
||||||
|
"with local contracts" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val parties = Seq("Alice")
|
||||||
|
val (_, createNode0) = create(builder, parties, Some("key0"))
|
||||||
|
val (_, createNode1) = create(builder, parties, Some("key1"))
|
||||||
|
val (_, createNode2) = create(builder, parties, Some("key2"))
|
||||||
|
|
||||||
|
builder.add(createNode0)
|
||||||
|
builder.add(createNode1)
|
||||||
|
builder.add(createNode2)
|
||||||
|
val rollbackId0 = builder.add(builder.rollback())
|
||||||
|
val nodeId0 = builder.add(exercise(builder, createNode0, parties, true), rollbackId0)
|
||||||
|
val rollbackId1 = builder.add(builder.rollback(), rollbackId0)
|
||||||
|
val nodeId1 = builder.add(exercise(builder, createNode1, parties, true), rollbackId1)
|
||||||
|
val rollbackId2 = builder.add(builder.rollback(), rollbackId0)
|
||||||
|
val nodeId2 = builder.add(exercise(builder, createNode2, parties, true), rollbackId2)
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe Map.empty
|
||||||
|
transaction.rolledbackBy shouldBe
|
||||||
|
Map(nodeId0 -> rollbackId0, nodeId1 -> rollbackId1, nodeId2 -> rollbackId2)
|
||||||
|
}
|
||||||
|
|
||||||
|
"with global contracts" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val parties = Seq("Alice")
|
||||||
|
val (_, createNode0) = create(builder, parties, Some("key0"))
|
||||||
|
val (_, createNode1) = create(builder, parties, Some("key1"))
|
||||||
|
val (_, createNode2) = create(builder, parties, Some("key2"))
|
||||||
|
|
||||||
|
val rollbackId0 = builder.add(builder.rollback())
|
||||||
|
val nodeId0 = builder.add(exercise(builder, createNode0, parties, true), rollbackId0)
|
||||||
|
val rollbackId1 = builder.add(builder.rollback(), rollbackId0)
|
||||||
|
val nodeId1 = builder.add(exercise(builder, createNode1, parties, true), rollbackId1)
|
||||||
|
val rollbackId2 = builder.add(builder.rollback(), rollbackId0)
|
||||||
|
val nodeId2 = builder.add(exercise(builder, createNode2, parties, true), rollbackId2)
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe Map.empty
|
||||||
|
transaction.rolledbackBy shouldBe
|
||||||
|
Map(nodeId0 -> rollbackId0, nodeId1 -> rollbackId1, nodeId2 -> rollbackId2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"3 deep" - {
|
||||||
|
"with local contracts" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val parties = Seq("Alice")
|
||||||
|
val (_, createNode0) = create(builder, parties, Some("key0"))
|
||||||
|
val (_, createNode1) = create(builder, parties, Some("key1"))
|
||||||
|
val (_, createNode2) = create(builder, parties, Some("key2"))
|
||||||
|
|
||||||
|
builder.add(createNode0)
|
||||||
|
builder.add(createNode1)
|
||||||
|
builder.add(createNode2)
|
||||||
|
val rollbackId0 = builder.add(builder.rollback())
|
||||||
|
val nodeId0 = builder.add(exercise(builder, createNode0, parties, true), rollbackId0)
|
||||||
|
val rollbackId1 = builder.add(builder.rollback(), rollbackId0)
|
||||||
|
val nodeId1 = builder.add(exercise(builder, createNode1, parties, true), rollbackId1)
|
||||||
|
val rollbackId2 = builder.add(builder.rollback(), rollbackId1)
|
||||||
|
val nodeId2 = builder.add(exercise(builder, createNode2, parties, true), rollbackId2)
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe Map.empty
|
||||||
|
transaction.rolledbackBy shouldBe
|
||||||
|
Map(nodeId0 -> rollbackId0, nodeId1 -> rollbackId1, nodeId2 -> rollbackId2)
|
||||||
|
}
|
||||||
|
|
||||||
|
"with global contracts" in {
|
||||||
|
val builder = TransactionBuilder()
|
||||||
|
val parties = Seq("Alice")
|
||||||
|
val (_, createNode0) = create(builder, parties, Some("key0"))
|
||||||
|
val (_, createNode1) = create(builder, parties, Some("key1"))
|
||||||
|
val (_, createNode2) = create(builder, parties, Some("key2"))
|
||||||
|
|
||||||
|
val rollbackId0 = builder.add(builder.rollback())
|
||||||
|
val nodeId0 = builder.add(exercise(builder, createNode0, parties, true), rollbackId0)
|
||||||
|
val rollbackId1 = builder.add(builder.rollback(), rollbackId0)
|
||||||
|
val nodeId1 = builder.add(exercise(builder, createNode1, parties, true), rollbackId1)
|
||||||
|
val rollbackId2 = builder.add(builder.rollback(), rollbackId1)
|
||||||
|
val nodeId2 = builder.add(exercise(builder, createNode2, parties, true), rollbackId2)
|
||||||
|
val transaction = builder.build()
|
||||||
|
|
||||||
|
transaction.consumedBy shouldBe Map.empty
|
||||||
|
transaction.rolledbackBy shouldBe
|
||||||
|
Map(nodeId0 -> rollbackId0, nodeId1 -> rollbackId1, nodeId2 -> rollbackId2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object TransactionSpec {
|
object TransactionSpec {
|
||||||
|
Loading…
Reference in New Issue
Block a user