Hi there,
Apologies for the basic question and if this has been asked/explained before.
I still can’t wrap my head around the rationale behind “Observers see consuming choices but not non-consuming ones”. Take the simple example below:
module Basic where
import Daml.Script
template Master
with
sig : Party
obs : Party
msg : Text
where
signatory sig
observer obs
choice
CreateChildConsuming : ContractId Child
controller sig
do
create Child with msg = msg <> " C", ..
nonconsuming choice
CreateChildNonconsuming : ContractId Child
controller sig
do
create Child with msg = msg <> " NC", ..
template Child
with
sig : Party
msg : Text
where
signatory sig
setup : Script ()
setup = script do
alice <- allocateParty "Alice"
bob <- allocateParty "Bob"
amcid <- submit alice do createCmd Master with sig = alice, obs = bob, msg = "Alice master"
-- bob learns nothing about the consequences of the exercise
submit alice do exerciseCmd amcid CreateChildNonconsuming
-- bob is witness of the consequences of the exercise
submit alice do exerciseCmd amcid CreateChildConsuming
return ()
The 2 choices are essentially identical, but with different consuming behavior.
Why do observers need to know (“witness”) the creation of the Child contract when the exercise is consuming but learn nothing when the exercise is non-consuming? To me it would seem logical that observers learns nothing about the created Child contract in either cases as they have nothing to do with the exercise action and the creation of the new contract - and they would learn about the archival of the Master contract when, well, it is archived as a consequence of the consuming “CreateChild” choice.
Many thanks!
Hi @davide_ooz,
The no-qualifier choice behaviour (observers can see consequences) historically precedes the qualified ones (observers don’t see consequences), and changing it at this point would be a massive backwards-compatibility headache.
My recommendation for new code would be to always put a qualifier, unless you explicitly want to opt-in to the observer-observing-consequences behaviour, and in those cases carefully document it.
Note that preconsuming
and postconsuming
have the same behaviour as nonconsuming
when it comes to observing consequences. Also note that you can explicitly call archive
within a nonconsuming
choice (though from a code readability standpoint I’d strongly recommend using either preconsuming
or postconsuming
in that case).
Thank you @Gary_Verhaegen, it now makes total sense. It is actually all explained in the choices documentation and in the original blog post about pre-/post- consumability, albeit more as a “side effect” that is easy to overlook. TBH I would emphasize this more in the docs (esp. because of its implications with divulgence) and somehow explicitly recommend using the annotations in new code. I imagine the vast majority of new users would expect/want the “new” behavior, but they would also likely go for the non-annotated choices by default because it seems simpler and there are lots of examples using that semantic. Thank you!
To understand the current behavior it’s useful to look at what the primitives are. There are two main choices here:
- Add non-consuming choices and primitive archives. In that case, observers naturally see the archive but not non-consuming choices.
- Add non-consuming choices and consuming choices. There is no primitive archive, the only way to archive a contract is via a consuming choice.
Daml currently uses 2. If you call archive
you’re not calling some primitive to archive the contract but an autogenerated choice Archive
which the compiler adds to every template. Observers have to see the archive which in this setting happens via the consuming exercise. And because privacy is at a subtransaction level, they see the children of that exercise as well.
pre and postconsuming are syntactic sugar for a non-consuming choice which exercises the archive choice at the beginning or end of the choice body.
As Gary mentioned, 1 may have been the better option wtr to chosing the primitives in retrospect.