type ProductKey = (Party, Text)
template Product
with
owner : Party
productId : Text
handler : Party
requestObserver : Party
where
signatory owner
observer requestObserver
key (owner, productId) : ProductKey
maintainer key._1
controller handler can
MakeHandoverRequest : (ContractId Product, ContractId HandoverRequest)
with
newHandler : Party
do
productCid <- create this with
requestObserver = newHandler
requestCid <- createHandoverRequest with
newHandler
productKey = (owner, productId)
productOwner = owner
oldHandler = handler
return (productCid, requestCid)
template HandoverRequest
with
oldHandler : Party
productOwner : Party
productKey : ProductKey
newHandler : Party
where
signatory productOwner, oldHandler
controller newHandler can
(productCid, product) <- fetchByKey @Product productKey
....
archive productCid
create product with ...
with script like:
...
(orangesCid) <- submit producer do
createCmd Product with
owner = producer
productId = "abc"
handler = producer
requestObserver = producer
(orangesCid, handoverRequestCid) <- submit producer do
exerciseCmd orangesCid MakeHandoverRequest with
newHandler = processor
(orangesCid, recordCid, oldRecordCids) <- submit processor do
exerciseCmd handoverRequestCid AcceptHandoverRequest with
recordId = "123"
recordTime = now
I get the error message:
Error: User abort: Submit failed with code 3: Command interpretation error in LF-DAMLe: dependency error: couldn’t find key com.daml.lf.transaction.Node$GlobalKey@4b148dba. Details: N/A.
I believe this occurs because newHandler cannot see the Product. The thing that confuses me is that I made newHandler an observer in the original choice. It works if I set newHandler = owner in the choice.
I suspect that there might be something else going on in your actual code. I’ve tried to isolate it to a standalone example. However, that actually works as expected. Can you share some more context on your example?
module Main where
type ProductKey = (Party, Text)
template Product
with
owner : Party
productId : Text
handler : Party
requestObserver : Party
where
signatory owner
observer requestObserver
key (owner, productId) : ProductKey
maintainer key._1
controller handler can
MakeHandoverRequest : (ContractId Product, ContractId HandoverRequest)
with
newHandler : Party
do
productCid <- create this with
requestObserver = newHandler
requestCid <- create HandoverRequest with
newHandler
productKey = (owner, productId)
productOwner = owner
oldHandler = handler
return (productCid, requestCid)
template HandoverRequest
with
oldHandler : Party
productOwner : Party
productKey : ProductKey
newHandler : Party
where
signatory productOwner, oldHandler
controller newHandler can
AcceptHandoverRequest : ContractId Product
do (productCid, product) <- fetchByKey @Product productKey
archive productCid
create product
test = do
producer <- getParty "producer"
processor <- getParty "processor"
(orangesCid) <- submit producer do
create Product with
owner = producer
productId = "abc"
handler = producer
requestObserver = producer
(orangesCid, handoverRequestCid) <- submit producer do
exercise orangesCid MakeHandoverRequest with
newHandler = processor
_ <- submit processor do
exercise handoverRequestCid AcceptHandoverRequest
pure ()
Sure, I’ll send all the code (with completely irrelevant parts redacted):
template Product
with
productId : Text
owner : Party
handler : Party
productType : Text
recordKeys : [RecordKey]
requestObserver : Party
where
signatory owner
observer requestObserver
key (owner, productId) : ProductKey
maintainer key._1
controller owner can
MakeHandoverRequest : (ContractId Product, ContractId HandoverRequest)
with
newHandler : Party
do
productCid <- create this with
requestObserver = newHandler
handoverRequestCid <- create HandoverRequest with
productKey = (owner, productId)
newHandler
oldHandler = handler
productOwner = owner
productId
return (productCid, handoverRequestCid)
...
template HandoverRequest
with
productId : Text
productKey : ProductKey
productOwner : Party
oldHandler : Party
newHandler : Party
where
signatory productOwner, oldHandler
controller newHandler can
AcceptHandoverRequest : (ContractId Product, ContractId Record, [ContractId Record])
with
recordId : Text
recordTime : Time
do
(productCid, product) <- fetchByKey @Product productKey
assert (product.handler == oldHandler)
assert (recordTime > product.lastUpdated)
let recordKey = (oldHandler, recordId)
recordCid <- create Record with
recordType = HANDOVER
productId
recordId
completionTime = recordTime
actor = oldHandler
productOwner
details = HandoverRecord HandoverRecordDetails with
newHandler = newHandler
time = recordTime
productCurrentHandler = newHandler
archive productCid
productCid <- create product with
recordKeys = product.recordKeys ++ [recordKey]
handler = newHandler
oldRecordCids <- forA product.recordKeys $ \k -> do
exerciseByKey @Record k ListNewHandlerOnRecord with newHandler = newHandler
return (productCid, recordCid, oldRecordCids)
I think I have just realised my problem: that second to last line (with the loop on exerciseByKey)… The oldHandler is an observer on the Records but not the newHandler
Let’s keep it, I think it’s useful for others that might run into similar problems and it shows that we probably should have better error messages that allow you to track this down more quickly.
@cocreature the code I last shared does raise an important point. If the HandoverRequest is accepted, there are some things that need to be done (like that final looped choice on the Records) that would be better done as soon as the request is accepted. I could do this by making another contract like HandoverAcceptedRecord that has a choice where the oldHandler can call the ListNewHandlerOnRecord choice, but this is not the workflow I want ([Request, Accept, Extra step] as opposed to [Request, Accept]). Is it possible to do this without the extra workflow step?