Skip to content

Commit

Permalink
Move exercise context computation to the client side
Browse files Browse the repository at this point in the history
We probably want to start displaying the full ptx on the client side
but for now this at least moves things out of speedy and into the
rendering layer where they belong.

It also fixes IncompleteTransaction to unwind properly so the roots
are really the roots.

changelog_begin
changelog_end
  • Loading branch information
cocreature committed Jul 7, 2021
1 parent c8faed8 commit 1a92fd8
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 37 deletions.
52 changes: 51 additions & 1 deletion compiler/scenario-service/client/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

load("//bazel_tools:haskell.bzl", "da_haskell_library")
load("//bazel_tools:haskell.bzl", "da_haskell_library", "da_haskell_test")

da_haskell_library(
name = "client",
Expand Down Expand Up @@ -49,3 +49,53 @@ da_haskell_library(
"//libs-haskell/da-hs-base",
],
)

da_haskell_test(
name = "tests",
srcs = glob(["test/**/*.hs"]),
hackage_deps = [
"async",
"base",
"binary",
"blaze-html",
"bytestring",
"conduit-extra",
"conduit",
"containers",
"cryptonite",
"deepseq",
"directory",
"extra",
"filepath",
"grpc-haskell",
"grpc-haskell-core",
"hashable",
"lens",
"mtl",
"process",
"proto3-suite",
"proto3-wire",
"split",
"stm",
"system-filepath",
"text",
"tasty",
"tasty-hunit",
"time",
"transformers",
"uri-encode",
"vector",
],
main_function = "DA.Daml.LF.PrettyScenarioSpec.main",
visibility = ["//visibility:public"],
deps = [
":client",
"//compiler/daml-lf-ast",
"//compiler/daml-lf-proto",
"//compiler/damlc/daml-opts:daml-opts-types",
"//compiler/scenario-service/protos:scenario_service_haskell_proto",
"//daml-assistant:daml-project-config",
"//libs-haskell/bazel-runfiles",
"//libs-haskell/da-hs-base",
],
)
56 changes: 48 additions & 8 deletions compiler/scenario-service/client/src/DA/Daml/LF/PrettyScenario.hs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ module DA.Daml.LF.PrettyScenario
, scenarioNotInFileNote
, fileWScenarioNoLongerCompilesNote
, ModuleRef
-- Exposed for testing
, ptxExerciseContext
, ExerciseContext(..)
) where

import Control.Monad.Extra
Expand Down Expand Up @@ -204,6 +207,43 @@ prettyTraceMessage _world msg =
-- prettyMayLocation world (traceMessageLocation msg)
ltext (traceMessageMessage msg)

data ExerciseContext = ExerciseContext
{ targetId :: Maybe ContractRef
, choiceId :: TL.Text
, exerciseLocation :: Maybe Location
, chosenValue :: Maybe Value
} deriving (Eq, Show)

ptxExerciseContext :: PartialTransaction -> Maybe ExerciseContext
ptxExerciseContext PartialTransaction{..} = go Nothing partialTransactionRoots
where go :: Maybe ExerciseContext -> V.Vector NodeId -> Maybe ExerciseContext
go acc children
| V.null children = acc
| otherwise = do
n <- nodeNode =<< MS.lookup (V.last children) nodeMap
case n of
NodeNodeCreate _ -> acc
NodeNodeFetch _ -> acc
NodeNodeLookupByKey _ -> acc
NodeNodeExercise Node_Exercise{..}
| Nothing <- node_ExerciseExerciseResult ->
let ctx = ExerciseContext
{ targetId = Just ContractRef
{ contractRefContractId = node_ExerciseTargetContractId
, contractRefTemplateId = node_ExerciseTemplateId
}
, choiceId = node_ExerciseChoiceId
, exerciseLocation = Nothing
, chosenValue = node_ExerciseChosenValue
}
in go (Just ctx) node_ExerciseChildren
| otherwise -> acc
NodeNodeRollback _ ->
-- do not decend in rollback. If we aborted within a try, this will not produce
-- a rollback node.
acc
nodeMap = MS.fromList [ (nodeId, node) | node <- V.toList partialTransactionNodes, Just nodeId <- [nodeNodeId node] ]

prettyScenarioErrorError :: Maybe ScenarioErrorError -> M (Doc SyntaxClass)
prettyScenarioErrorError Nothing = pure $ text "<missing error details>"
prettyScenarioErrorError (Just err) = do
Expand Down Expand Up @@ -690,34 +730,34 @@ prettyNode Node{..}
archivedSC = annotateSC PredicateSC -- Magenta

prettyPartialTransaction :: PartialTransaction -> M (Doc SyntaxClass)
prettyPartialTransaction PartialTransaction{..} = do
prettyPartialTransaction ptx@PartialTransaction{..} = do
world <- askWorld
let ppNodes =
runM partialTransactionNodes world
$ fmap vsep
$ mapM (lookupNode >=> prettyNode)
(V.toList partialTransactionRoots)
pure $ vcat
[ case partialTransactionExerciseContext of
[ case ptxExerciseContext ptx of
Nothing -> mempty
Just ExerciseContext{..} ->
text "Failed exercise"
<-> parens (prettyMayLocation world exerciseContextExerciseLocation) <> ":"
<-> parens (prettyMayLocation world exerciseLocation) <> ":"
$$ nest 2 (
keyword_ "exercises"
<-> prettyMay "<missing template id>"
(\tid ->
prettyChoiceId world tid exerciseContextChoiceId)
(contractRefTemplateId <$> exerciseContextTargetId)
prettyChoiceId world tid choiceId)
(contractRefTemplateId <$> targetId)
<-> keyword_ "on"
<-> prettyMay "<missing>"
(prettyContractRef world)
exerciseContextTargetId
targetId
$$ keyword_ "with"
$$ ( nest 2
$ prettyMay "<missing>"
(prettyValue' False 0 world)
exerciseContextChosenValue)
chosenValue)
)

, if V.null partialTransactionRoots
Expand Down Expand Up @@ -827,7 +867,7 @@ revealLocationUri fp sline eline =
encodeURI = Network.URI.Encode.encodeText

prettyContractRef :: LF.World -> ContractRef -> Doc SyntaxClass
prettyContractRef world (ContractRef _relative coid tid) =
prettyContractRef world (ContractRef coid tid) =
hsep
[ prettyContractId coid
, parens (prettyMay "<missing template id>" (prettyDefName world) tid)
Expand Down
117 changes: 117 additions & 0 deletions compiler/scenario-service/client/test/DA/Daml/LF/PrettyScenarioSpec.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
-- Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0

module DA.Daml.LF.PrettyScenarioSpec (main) where

import DA.Daml.LF.PrettyScenario
import qualified ScenarioService as S

import Control.Monad.State.Strict
import Data.Bifunctor
import qualified Data.Text.Lazy as TL
import qualified Data.Vector as V
import Test.Tasty
import Test.Tasty.HUnit

main :: IO ()
main = defaultMain $ testGroup "PrettyScenario"
[ ptxExerciseContextTests
]

ctx :: String -> ExerciseContext
ctx choice = ExerciseContext
{ targetId = Just (S.ContractRef "#0" Nothing)
, choiceId = TL.pack choice
, exerciseLocation = Nothing
, chosenValue = Nothing
}

ptxExerciseContextTests :: TestTree
ptxExerciseContextTests = testGroup "ptxExerciseContext"
[ testCase "returns the last root exercise node" $ do
ptxExerciseContext (toPtx [Exercise "1" False [], Exercise "2" False []]) @?= Just (ctx "2")
, testCase "ignores complete exercise nodes" $ do
ptxExerciseContext (toPtx [Exercise "1" False [], Exercise "2" True []]) @?= Nothing
, testCase "ignores create, fetch and lookup nodes" $ do
ptxExerciseContext (toPtx [Create, Fetch, Lookup]) @?= Nothing
, testCase "does not decend in rollback node" $ do
ptxExerciseContext (toPtx [Rollback [Exercise "1" False []]]) @?= Nothing
, testCase "decends in exercise" $ do
ptxExerciseContext (toPtx [Exercise "0" False [Exercise "1" False []]]) @?= Just (ctx "1")
]

type Transaction = [Node]

data Node
= Create
| Lookup
| Fetch
| Exercise String Bool [Node]
| Rollback [Node]

toPtx :: Transaction -> S.PartialTransaction
toPtx nodes = case runState (mapM go nodes) (0, []) of
(roots, (_, nodes)) -> S.PartialTransaction
{ partialTransactionRoots = V.fromList roots
, partialTransactionNodes = V.fromList (reverse nodes)
}
where
go :: Node -> State (Int, [S.Node]) S.NodeId
go n = do
nid <- nextNodeId
nodeNode <- case n of
Create -> pure $ S.NodeNodeCreate S.Node_Create
{ node_CreateContractInstance = Nothing
, node_CreateSignatories = V.empty
, node_CreateStakeholders = V.empty
, node_CreateKeyWithMaintainers = Nothing
}
Fetch -> pure $ S.NodeNodeFetch S.Node_Fetch
{ node_FetchContractId = "#0"
, node_FetchTemplateId = Nothing
, node_FetchSignatories = V.empty
, node_FetchStakeholders = V.empty
}
Lookup -> pure $ S.NodeNodeLookupByKey S.Node_LookupByKey
{ node_LookupByKeyTemplateId = Nothing
, node_LookupByKeyKeyWithMaintainers = Nothing
, node_LookupByKeyContractId = "#0"
}
Rollback children -> do
children' <- mapM go children
pure $ S.NodeNodeRollback (S.Node_Rollback (V.fromList children'))
Exercise choice complete children -> do
children' <- mapM go children
pure $ S.NodeNodeExercise S.Node_Exercise
{ node_ExerciseTargetContractId = "#0"
, node_ExerciseTemplateId = Nothing
, node_ExerciseChoiceId = TL.pack choice
, node_ExerciseActingParties = V.empty
, node_ExerciseChosenValue = Nothing
, node_ExerciseObservers = V.empty
, node_ExerciseSignatories = V.empty
, node_ExerciseStakeholders = V.empty
, node_ExerciseChildren = V.fromList children'
, node_ExerciseExerciseResult = if complete then Just (S.Value (Just (S.ValueSumUnit S.Empty))) else Nothing
, node_ExerciseConsuming = False
}
let node = S.Node
{ nodeNodeId = Just nid
, nodeNode = Just nodeNode
, nodeEffectiveAt = 0
, nodeDisclosures = V.empty
, nodeReferencedBy = V.empty
, nodeConsumedBy = Nothing
, nodeRolledbackBy = Nothing
, nodeParent = Nothing
, nodeLocation = Nothing
}
modify' $ second (node :)
pure nid
nextNodeId :: State (Int, b) S.NodeId
nextNodeId = do
(i, nodes) <- get
let !i' = i + 1
put (i', nodes)
pure (S.NodeId (TL.pack $ show i))

10 changes: 1 addition & 9 deletions compiler/scenario-service/protos/scenario_service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,6 @@ message Location {

// Reference to a contract instance.
message ContractRef {
bool relative = 1; // True if the contract id is relative.
string contract_id = 2;
Identifier template_id = 3;
}
Expand Down Expand Up @@ -282,14 +281,6 @@ message ScenarioError {
message PartialTransaction {
repeated Node nodes = 1;
repeated NodeId roots = 2;
ExerciseContext exercise_context = 3; // Top-most exercise context if any.
}

message ExerciseContext {
ContractRef target_id = 1;
string choice_id = 2;
Location exercise_location = 3; // Location of the 'exercise' expression
Value chosen_value = 4;
}

message Field {
Expand Down Expand Up @@ -565,6 +556,7 @@ message Node {
repeated Party signatories = 8;
repeated Party stakeholders = 9;
repeated NodeId children = 11;
Value exercise_result = 12; // None for incomplete/aborted exercise nodes.
}

message LookupByKey {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,6 @@ final class Conversions(

def mkContractRef(coid: V.ContractId, templateId: Ref.Identifier): proto.ContractRef =
proto.ContractRef.newBuilder
.setRelative(false)
.setContractId(coidToEventId(coid).toLedgerString)
.setTemplateId(convertIdentifier(templateId))
.build
Expand Down Expand Up @@ -434,14 +433,6 @@ final class Conversions(
.addAllNodes(tx.nodes.map(convertNode).asJava)
.addAllRoots(tx.roots.toList.map(convertTxNodeId).asJava)

incomplete.exerciseContextMaybe.foreach { exe =>
val ecBuilder = proto.ExerciseContext.newBuilder
.setTargetId(mkContractRef(exe.targetCoid, exe.templateId))
.setChoiceId(exe.choiceId)
.setChosenValue(convertValue(exe.chosenValue))
exe.optLocation.map(loc => ecBuilder.setExerciseLocation(convertLocation(loc)))
builder.setExerciseContext(ecBuilder.build)
}
builder.build
}

Expand Down Expand Up @@ -508,7 +499,7 @@ final class Conversions(
)
case ex: N.NodeExercises[NodeId, V.ContractId] =>
ex.optLocation.map(loc => builder.setLocation(convertLocation(loc)))
builder.setExercise(
val exerciseBuilder =
proto.Node.Exercise.newBuilder
.setTargetContractId(coidToEventId(ex.targetCoid).toLedgerString)
.setTemplateId(convertIdentifier(ex.templateId))
Expand All @@ -524,8 +515,12 @@ final class Conversions(
.toSeq
.asJava
)
.build
)

ex.exerciseResult.foreach { result =>
exerciseBuilder.setExerciseResult(convertValue(result))
}

builder.setExercise(exerciseBuilder.build)

case lbk: N.NodeLookupByKey[V.ContractId] =>
lbk.optLocation.foreach(loc => builder.setLocation(convertLocation(loc)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ private[lf] object PartialTransaction {
private type ExerciseNode = Node.NodeExercises[NodeId, Value.ContractId]

private final case class IncompleteTxImpl(
val transaction: TX,
val exerciseContextMaybe: Option[ExerciseNode],
val transaction: TX
) extends TxIncompleteTransaction

sealed abstract class ContextInfo {
Expand Down Expand Up @@ -359,12 +358,13 @@ private[lf] case class PartialTransaction(
case _: PartialTransaction.RootContextInfo => None
}

val ptx = unwind

IncompleteTxImpl(
GenTransaction(
nodes,
ImmArray(context.children.toImmArray.toSeq.sortBy(_.index)),
),
unwindToExercise(context.info).map(makeExNode(_)),
ptx.nodes,
ImmArray(ptx.context.children.toImmArray.toSeq.sortBy(_.index)),
)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,4 @@ trait IncompleteTransaction {
type ExerciseNode = Node.NodeExercises[Nid, Cid]

def transaction: TX
def exerciseContextMaybe: Option[ExerciseNode]
}

0 comments on commit 1a92fd8

Please sign in to comment.