Canton transaction issue
Hello,
I’m facing an issue when performing a transaction with a multi domain canton setup of 5 participants (P(A-E)) and 2 domains (D(1-2)).
The architecture is as follows:
P[A,B,C,E] -> D1
P[A,B,D,E] -> D2
Additional setup:
- All participants except C and D has multi domain support configured
- A dar with a template Foo was uploaded to all participants
- A dar with a template Bar was uploaded to C, D and E
- Template Bar has a choice Choice that takes as parameter a contract for template Foo coming from PA (fooPAcid).
- Neither Foo contract’s stakeholders have visibility on Bar with the exception of the Bar’s choice controller, which is an observer on both Foos
Now the problem:
Executing this choice on PE with only a fetch for fooPAcid fails the transaction with the following error.
FAILED_PRECONDITION: AUTOMATIC_TRANSFER_FOR_TRANSACTION_FAILED(9,160a9949): Automatically transferring contracts to a common domain failed.
I tried to discover the meaning of this error in the daml docs, but it didn’t give me much info. What is the reason for this error?
Thanks in advance
Hi @David_Martins !
Could you share the two templates and indicate:
- Which are the contract instances that you have (including the signatories and observers parties)?
- Who is the party exercising the choice on participant PE?
- Are all the parties hosted on all the domains ?
Thanks!
Raf
Hello @Rafael_Guglielmetti, thanks for the reply.
For a bit more context I’m working on a DvP project that includes a multi node workflow, that also leverages the Daml Finance library.
I can share snippets of the templates that i have as well as the transaction tree that was logged.
Interface module for Settlement
data View = View
with
operator : Party
provider : Party
holder : Party
deriving (Eq, Show)
interface Settlement requires Services.Service where
viewtype View
instructTrade : InstructTrade -> Update ()
nonconsuming choice InstructTrade : ()
with
tradeRequestCid : ContractId TradeRequest.TradeRequest
counterRequestView : TradeRequest.View
controller (view this).holder
do instructTrade this arg
Interface module for trade request
data View = View
with
operator : Party
requestorAccount : AccountKey
counterpartyAccount : AccountKey
sending : (ContractId Holding.I, InstrumentKey)
receiving : InstrumentQuantity
tradePath : [(Party, AccountKey)]
deriving (Eq, Show)
interface TradeRequest requires Disclosure.I where
viewtype View
Implementation module for Settlement contract (a service type contract)
This module is present in E and choice’s controller for the is the holder party (Clearinghouse). The “tradeRequestCid” is is an interface cid created in either A or B and “counterRequestView” a View record for a trade request interface. The transaction fails on executing the fetch for “tradeRequestCid”.
template Settlement
with
operator : Party
holder : Party
where
signatory operator, holder
key (operator, holder) : (Party, Party)
maintainer key._1
interface instance Service.Service for Settlement where
view = Service.View with operator, provider = operator, holder
interface instance Settlement.Settlement for Settlement where
view = Settlement.View with operator, provider = operator, holder
instructTrade Settlement.InstructTrade{tradeRequestCid, counterRequestView} = do
tradeRequestView <- view <$> fetch tradeRequestCid
Implementation module for trade request
template TradeRequest
with
operator : Party
requestorAccount : AccountKey
counterpartyAccount : AccountKey
sending : (ContractId Holding.I, InstrumentKey)
receiving : InstrumentQuantity
tradePath : [(Party, AccountKey)]
observers : PartiesMap
where
let
custodian = requestorAccount.custodian
requestor = requestorAccount.owner
signatory operator, custodian, requestor
Logged transaction tree
The transaction is accepted be results in failure latter on
Commands(
participant5,
"",
daml-script,
4550a631-f415-4b8b-9f4c-08a7f0edb15d,
Clearinghouse::122097235f1c23d018711fb65dfe766d3ee66f3e1d5d6559249b5273dc86a0a566c4,
Command(
Exercise(
ExerciseCommand(
Identifier(99b310bc7e2432bd0c706d7b283b041d4ab3b3e3ec1d1e8d71f2661f58f02036, Dvp.Services.Settlement, Settlement),
00d0feeb1681a436fb8adb384ca71d2a0f439f1ed979cc23ce60a341f5e6b99e65ca0212203e6320ca6466fe7b7ce3cef2c15e57cc0cdeba08ea655e24419873f1a645cfc9,
InstructTrade,
Value(
Record(
Record(
Identifier(99b310bc7e2432bd0c706d7b283b041d4ab3b3e3ec1d1e8d71f2661f58f02036, Dvp.Services.Settlement, InstructTrade),
Vector(
RecordField(
tradeRequestCid,
Value(ContractId(00213a109e8d0f748f469f8a4ee14f659b2703a4565481b56d4f5542f8bf65ed50ca0212203cbc0b51f737064cdaf205eb8dc275a5f2c12dc583430b9d6959d40decd5575c))
),
RecordField(
counterRequestView,
Value(
Record(
Record(
Identifier(99b310bc7e2432bd0c706d7b283b041d4ab3b3e3ec1d1e8d71f2661f58f02036, Dvp.Models.Trade.Request, View),
Vector(
RecordField(operator, Value(Party(Participant1 Operator::12205ac75896fe8f3d3f5af37d464a8a2e6402ffb40b8586052824ca71b131439c7d))),
RecordField(
requestorAccount,
Value(
Record(
Record(
Identifier(b1646d11a251b65507ac12de89633d323dc5237ec11127ad66e983d8d6e3e623, Daml.Finance.Interface.Types.Common.Types, AccountKey),
Vector(
RecordField(custodian, Value(Party(Participant1 Custodian::12205ac75896fe8f3d3f5af37d464a8a2e6402ffb40b8586052824ca71b131439c7d))),
RecordField(owner, Value(Party(Alice::12205ac75896fe8f3d3f5af37d464a8a2e6402ffb40b8586052824ca71b131439c7d))),
RecordField(
id,
Value(
Record(
Record(
Identifier(b1646d11a251b65507ac12de89633d323dc5237ec11127ad66e983d8d6e3e623, Daml.Finance.Interface.Types.Common.Types, Id),
RecordField(
unpack,
Value(
Text(
Alice::12205ac75896fe8f3d3f5af37d464a8a2e6402ffb40b8586052824ca71b131439c7d@Participant1 Custodian::12205ac75896fe8f3d3f5af37d464a8a2e6402ffb40b8586052824ca71b131439c7d
)
)
)
)
)
)
)
)
)
)
)
),
RecordField(
counterpartyAccount,
Value(
Record(
Record(
Identifier(b1646d11a251b65507ac12de89633d323dc5237ec11127ad66e983d8d6e3e623, Daml.Finance.Interface.Types.Common.Types, AccountKey),
Vector(
RecordField(custodian, Value(Party(Participant2 Custodian::12206bf2e70b08a87ec8a96e345fd298c89846c9dd3a88cbe153325b7ba0b43ea729))),
RecordField(owner, Value(Party(Bob::12206bf2e70b08a87ec8a96e345fd298c89846c9dd3a88cbe153325b7ba0b43ea729))),
RecordField(
id,
Value(
Record(
Record(
Identifier(b1646d11a251b65507ac12de89633d323dc5237ec11127ad66e983d8d6e3e623, Daml.Finance.Interface.Types.Common.Types, Id),
RecordField(
unpack,
Value(
Text(
Bob::12206bf2e70b08a87ec8a96e345fd298c89846c9dd3a88cbe153325b7ba0b43ea729@Participant2 Custodian::12206bf2e70b08a87ec8a96e345fd298c89846c9dd3a88cbe153325b7ba0b43ea729
)
)
)
)
)
)
)
)
)
)
)
),
RecordField(
sending,
Value(
Record(
Record(
Identifier(40f452260bef3f29dede136108fc08a88d5a5250310281067087da6f0baddff7, DA.Types, Tuple2),
Vector(
RecordField(
_1,
Value(ContractId(000648fde7f1699912f91250be253e2cecfd1020448bb04de9f9beea0e8161dcf4ca0212202150cfbdd249e7d95b67a6809386af54cc44c19c9f41501dfa3570e449880cc7))
),
RecordField(
_2,
Value(
Record(
Record(
Identifier(b1646d11a251b65507ac12de89633d323dc5237ec11127ad66e983d8d6e3e623, Daml.Finance.Interface.Types.Common.Types, InstrumentKey),
Vector(
RecordField(depository, Value(Party(Central Bank::122004a5609cbbc56b9e8b0642539c59bb4d81d59e26780c4a8d65b93f582025c885))),
RecordField(issuer, Value(Party(Central Bank::122004a5609cbbc56b9e8b0642539c59bb4d81d59e26780c4a8d65b93f582025c885))),
RecordField(
id,
Value(
Record(
Record(
Identifier(b1646d11a251b65507ac12de89633d323dc5237ec11127ad66e983d8d6e3e623, Daml.Finance.Interface.Types.Common.Types, Id),
RecordField(unpack, Value(Text(USD)))
)
)
)
),
RecordField(version, Value(Text(1)))
)
)
)
)
)
)
)
)
)
),
RecordField(
receiving,
Value(
Record(
Record(
Identifier(b1646d11a251b65507ac12de89633d323dc5237ec11127ad66e983d8d6e3e623, Daml.Finance.Interface.Types.Common.Types, Quantity),
Vector(
RecordField(
unit,
Value(
Record(
Record(
Identifier(b1646d11a251b65507ac12de89633d323dc5237ec11127ad66e983d8d6e3e623, Daml.Finance.Interface.Types.Common.Types, InstrumentKey),
Vector(
RecordField(depository, Value(Party(Central Securities Depository::12202b19c274f7356d9676c367ad1aa19cb3a08ffbcde8f85547b2b430e1ade3377f))),
RecordField(issuer, Value(Party(Central Securities Depository::12202b19c274f7356d9676c367ad1aa19cb3a08ffbcde8f85547b2b430e1ade3377f))),
RecordField(
id,
Value(
Record(
Record(
Identifier(b1646d11a251b65507ac12de89633d323dc5237ec11127ad66e983d8d6e3e623, Daml.Finance.Interface.Types.Common.Types, Id),
RecordField(unpack, Value(Text(TSLA)))
)
)
)
),
RecordField(version, Value(Text(1)))
)
)
)
)
),
RecordField(amount, Value(Numeric(1000.0000000000)))
)
)
)
)
),
RecordField(
tradePath,
Value(
List(
List(
Vector(
Value(
Record(
Record(
Identifier(40f452260bef3f29dede136108fc08a88d5a5250310281067087da6f0baddff7, DA.Types, Tuple2),
Vector(
RecordField(_1, Value(Party(Alice::12205ac75896fe8f3d3f5af37d464a8a2e6402ffb40b8586052824ca71b131439c7d))),
RecordField(
_2,
Value(
Record(
Record(
Identifier(b1646d11a251b65507ac12de89633d323dc5237ec11127ad66e983d8d6e3e623, Daml.Finance.Interface.Types.Common.Types, AccountKey),
Vector(
RecordField(custodian, Value(Party(Participant1 Custodian::12205ac75896fe8f3d3f5af37d464a8a2e6402ffb40b8586052824ca71b131439c7d))),
RecordField(owner, Value(Party(Alice::12205ac75896fe8f3d3f5af37d464a8a2e6402ffb40b8586052824ca71b131439c7d))),
RecordField(
id,
Value(
Record(
Record(
Identifier(b1646d11a251b65507ac12de89633d323dc5237ec11127ad66e983d8d6e3e623, Daml.Finance.Interface.Types.Common.Types, Id),
RecordField(
unpack,
Value(
Text(
Alice::12205ac75896fe8f3d3f5af37d464a8a2e6402ffb40b8586052824ca71b131439c7d@Participant1 Custodian::12205ac75896fe8f3d3f5af37d464a8a2e6402ffb40b8586052824ca71b131439c7d
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
),
Value(
Record(
Record(
Identifier(40f452260bef3f29dede136108fc08a88d5a5250310281067087da6f0baddff7, DA.Types, Tuple2),
Vector(
RecordField(_1, Value(Party(Participant1 Custodian::12205ac75896fe8f3d3f5af37d464a8a2e6402ffb40b8586052824ca71b131439c7d))),
RecordField(
_2,
Value(
Record(
Record(
Identifier(b1646d11a251b65507ac12de89633d323dc5237ec11127ad66e983d8d6e3e623, Daml.Finance.Interface.Types.Common.Types, AccountKey),
Vector(
RecordField(custodian, Value(Party(Central Bank::122004a5609cbbc56b9e8b0642539c59bb4d81d59e26780c4a8d65b93f582025c885))),
RecordField(owner, Value(Party(Participant1 Custodian::12205ac75896fe8f3d3f5af37d464a8a2e6402ffb40b8586052824ca71b131439c7d))),
RecordField(
id,
Value(
Record(
Record(
Identifier(b1646d11a251b65507ac12de89633d323dc5237ec11127ad66e983d8d6e3e623, Daml.Finance.Interface.Types.Common.Types, Id),
RecordField(
unpack,
Value(
Text(
Participant1 Custodian::12205ac75896fe8f3d3f5af37d464a8a2e6402ffb40b8586052824ca71b131439c7d@Central Bank::122004a5609cbbc56b9e8b0642539c59bb4d81d59e26780c4a8d65b93f582025c885
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
),
Value(
Record(
Record(
Identifier(40f452260bef3f29dede136108fc08a88d5a5250310281067087da6f0baddff7, DA.Types, Tuple2),
Vector(
RecordField(_1, Value(Party(Central Bank::122004a5609cbbc56b9e8b0642539c59bb4d81d59e26780c4a8d65b93f582025c885))),
RecordField(
_2,
Value(
Record(
Record(
Identifier(b1646d11a251b65507ac12de89633d323dc5237ec11127ad66e983d8d6e3e623, Daml.Finance.Interface.Types.Common.Types, AccountKey),
Vector(
RecordField(custodian, Value(Party(Clearinghouse::122097235f1c23d018711fb65dfe766d3ee66f3e1d5d6559249b5273dc86a0a566c4))),
RecordField(owner, Value(Party(Central Bank::122004a5609cbbc56b9e8b0642539c59bb4d81d59e26780c4a8d65b93f582025c885))),
RecordField(
id,
Value(
Record(
Record(
Identifier(b1646d11a251b65507ac12de89633d323dc5237ec11127ad66e983d8d6e3e623, Daml.Finance.Interface.Types.Common.Types, Id),
RecordField(
unpack,
Value(
Text(
Central Bank::122004a5609cbbc56b9e8b0642539c59bb4d81d59e26780c4a8d65b93f582025c885@Clearinghouse::122097235f1c23d018711fb65dfe766d3ee66f3e1d5d6559249b5273dc86a0a566c4
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
)
),
Empty,
None,
None,
Clearinghouse::122097235f1c23d018711fb65dfe766d3ee66f3e1d5d6559249b5273dc86a0a566c4,
Vector(),
"",
Vector()
)
)
As for party hosting, it’s as follows:
- Alice and Custodian 1 are hosted on PA
- Bob and Custodian 2 are hosted on PB
- Central Bank is hosted on PC
- Central Securities Depository is hosted on PD
- Clearinghouse is hosted on PE
Hope these clarifications help, if needed I can provide further context.
Best regards
Hi @David_Martins ,
Sorry for the delay in answering!
I am coming back more precisely to ask question related to your first message:
- You mentioned templates Foo and Bar. Could you share them ?
- What are the contract instances for these templates ?
- Who is the party exercising the choice on PE? (is it the clearinghouse?)
- Are all the parties hosted on all the domains?
Best,
Rafael
Hello @Rafael_Guglielmetti,
In my reply I have the snippets for the actual templates that Foo and Bar were extrapolated from, where Foo is the TradeRequest template and Bar is the Settlement template, where the Settlement contract was created on PE and the TradeRequest contract was created on PA.
As party hosting is concerned, both D1 and. D2 would know about Alice, Bob, Custodian 1, Custodian 2 and Clearinghouse, D1 would be privy to Central Bank and D2 to Central Securities Depository.
The Clearinghouse party is indeed the controller for the mentioned choice.
Unfortunately, I’m not quite sure, what you mean in the second question, so if you could provide further clarification there, it would be appreciated.
Kind regards
Hey!
Sorry, I got a bit confused by the extrapolation from Foo/Bar to your example.
Some context first,
In Daml, any transaction is performed/coordinated over a single domain. In particular, for a transaction to be executed, all the input contracts (contracts needed for the transaction) have to be on a single domain. By default, Canton will try to automatically transfer the contracts to a suited domain before submitting the transaction.
Roughly, the find candidates for this “shared domain”, Canton will
- list all the input contracts
- for all the contracts in 1., list all the stakeholders (signatories and observers)
- for all the contracts in 1., list all the packages containing the corresponding templates
- find a domain on which all the stakeholders (2.) are hosted and all the packages are vetted.
In your case, the transaction involve the CSD and the CentralBank but they don’t share a common domain. What you can do is to connect P3 to domain D2 or connect P4 to domain D1: this should allow the transaction to go through.
Hope this helps.
Raf
Hello,
Actually this design came about because of the need to synchronise the transaction in a single domain.
Even though the csd and central bank parties don’t share a domain, they don’t share the transaction either, so, in principle, it should be able to go only through D1.
Yes, but if you look at your transaction tree (snippet you posted above) we see CSD and CentralBank being involved in the transaction, which means they must be connected to a common domain.
They shoiuldn’t be involved in the transaction though, since they only exist as fields on the arguments that the choice takes. Given they are present as fields, a domain must exist that knows about both?
You are right that it is not necessarily an input contract to the transaction.
In order for me to investigate further, could you:
- Tell me which Canton version you are using
- Provide the Canton logs
- Ideally, a full example that reproduces the issue
Thanks.
Best,
Raf
Hello,
To answer the first question, I’m using version 2.7.1, and I’ve sent you a private message for the rest.
Thank you
Hi @David_Martins can you file through the support portal, please?