With contract keys there are two functions fetchByKey and lookupByKey, the latter of which allows me to handle negative lookups. I don't see a lookup : (Template t) => ContractId t -> Update (Optional t) function that does the same for contract Ids.
Nor do I see a mechanism for try-catch that would allow me to handle failing fetch calls.
How do I avoid failing transactions without reimplementing the entire DAML logic client-side?
There's quite a bit to unwrap in this question:
lookup?lookupByKey and the proposed lookup functions allow submitters of transactions to make non-existence assertions about contracts. A None result from lookupByKey asserts that a particular key doesn't exist, a None from lookup would assert that a particular ContractId doesn't exist. The big difference is that the contract key of a contract includes the key maintainers, the parties which validate lookups. Thus is is clear who has to authorise a lookupByKey, and who validates the negative result. With lookup, there are no parties associated to a ContractId so there are no sensible authorisation or validation rules.
A weaker version would be fetchIfNotArchived : (Template t) => ContractId t -> Update (Optional t), which fails if the id is unknown and returns None if the contract id is archived. DAML transactions are deterministic on current ledger state, which is currently the set of all active contracts. A function such as fetchIfNotArchived would introduce a distinction between inexistent contracts and archived contracts in the DAML ledger model, and thus the ledger state would change from all active contracts to the entire ledger from the beginning. DAML ledgers could no longer be truncated and grow linearly over time, which is not desirable.
With try-catch blocks we run into very similar issues. Suppose I had some sort of try-catch. I could write this function:
lookup : (Template t) => ContractId t -> Update (Optional t)
lookup cid = do
try do
c <- fetch cid
return (Some c)
catch
return None
As explained above, there is nobody that could reasonably authorise or validate that the submitter followed the right path. So such a try-catch would have to be "unchecked", meaning the submitter can freely choose whether to go down the try or the catch path. You can already mock that up by passing the existence in:
maybeLookup : (Template t) => ContractId t -> Bool -> Update (Optional t)
maybeLookup cid cidIsKnown = if cidIsKnown
then do
c <- fetch cid
return (Some c)
else return None
This requires you to pass in cidIsKnown from the outside world, which is annoying. One could imagine "unchecked queries" to help here. E.g. one could introduce a function uncheckedContractActive : (Template t) => ContractId t -> Update Bool, which checks whether the contract is active on the submitter end, and then merely includes the Bool in the transaction. It would be explicit that it's the submitter's free choice whether to run the transaction as if the cid exists, or the opposite.
Unchecked queries have so far not made it into DAML for the simple reason that they muddy the picture of what is shared, guaranteed, and validated logic, and what is the individual client's concern. In the current design, that split is exactly between DAML contracts and ledger clients.
Failing transactions are not actually that expensive as long as they fail on the submitter node during interpretation. The important thing is to design contract models and ledger clients in such a way that fetch calls only ever fail because of race conditions, and there is not so much contention on contracts that transactions fail a lot. You can try
Lock contract to ledger that indicates to the non-batch automation that it should pause, or protect choices with an Operation contract which gets revoked when the batch processing starts. The latter has stronger guarantees, but adds overhead as it adds a fetch to every choice. Once you have reduced contention to an acceptable level, failing fetch calls can be handled using retry logic in the ledger client.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With