If an observer
’s participant node in a submitted command is down, would Canton still process and accept that command, will it pass validation?
It depends on your confirmation policy. By default, you get the signatory confirmation policy which requires that signatories and actors confirm. So unless the observer is also an actor, they do not have to confirm and the command will go through even if the respective node is down.
Thanks for confirming ( ) my intuition.
How does one set confirmation policies in the domain, I could not find anything with a search for “confirmation policy”?
The confirmation policy is derived from the “TrustLevel” given to a participant via the participant domain trust certificates: Console Commands — Canton 2.6.0-SNAPSHOT documentation
However, this is hidden behind a feature flag and not supported in production right now and we haven’t yet decided whether we really need this.
I think of signatory
as having a strict consistency model and observer
as eventually consistent.
Or in CAP theorem terms, we always want Availability, so signatory
gives you Consistency but isn’t Partition-tolerant, and observer
is Partition-tolerant but not Consistent.
Even though there are no strict consistency guarantees on observer
you will get confirmation from their node eventually via the ACS commitments (see my forum question on this topic)
That raises an interesting question; should the issuer of a currency be a signatory
or an observer
?
From a language and rights perspective they have to be a signatory
as their authorization of the creation is what gives it value. But from a distributed operations perspective it would be nicer if they were an observer
so that they are not a bottleneck on transactions.
The availability requirement on the issuer in Daml’s default way of modelling assets is a tradeoff we made consciously.
Let’s say we have this chain of events where the #s are UTXO/Contract ids.
- Bank issues asset #0 to Alice
- Alice transfers #0 to Eve who now holds #1
- Eve transfers #1 to Bob who now holds #2
- Eve transfers #1 to Carol
- Eve makes up a #3 and transfers #3 to Carol
- Eve transfers #2 to Carol.
4, 5, and 6 should not be possible so Carol needs a mechanism each to detect and prevent these.
- Proof of Authenticity: Ascertain whether an asset #N was indeed at some point created.
- Double Spend Detection: Ascertain whether a UTXO #N that’s being used in a transaction was already spent.
Different systems solve these two in different ways:
Blockchains typically solve the proof of authenticity by making everything public and delegating the verification to the miners. Anyone can verify that a given UTXO/contract does actually exist. Miners and full nodes do so as part of validating blocks.
Corda is an exception in that is solves this through backchains. Ie if you receive an asset, you receive its entire history back to issuance and can verify that. So in our 5. above, Carol would not be able to verify authenticity because there’s no backchain leading back to an issuer signature.
Another approach that’s “up and coming” is to use Zero Knowledge Proofs to prove authenticity. There are some systems that do this for simple token use-cases. But ZKPs for general purpose smart contracts are some way away.
Blockchains typically solve the double spend protection by making everything public and delegating the verification to the miners. Anyone can verify that a given UTXO/contract was not already spent by going through the entire history or using a state snapshot. Miners and full nodes do so as part of validating blocks.
Corda is an exception in that it solves this through its Notary. You send the UTXO Ids of all “States” you want to consume to the notary, which checks that they did not already get spent in a previous transaction, and then gives you its signature which conveys “no double spend here”. If the notary doesn’t get to see the transaction itself, it does this double spend checking blindly, which means it will also “spend” UTXO Ids that get spent in an invalid transaction. So in our example 6 above, Carol wouldn’t receive #2, but Bob would lose it. This is called a “Denial of State” attack in Corda. If the Notary does see the whole transaction, you have given up a fair amount of privacy.
The tradeoff should be clear from the above. There’s a triangle made out of Privacy, Security and Availability here. Most public blockchain has gone without Privacy. Corda has chosen limited privacy, but depending on setup lost security and introduced a global single point of failure with the Notary. We made this tradeoff available per-contract: You choose the signatories. They are the set that sees the contract, but you have to trust at least one signatory, and they all have to be available. By default, it means that the issuer is a single point of failure for an asset.
But this per-contract trade-off allows us to do better. On our roadmap we have a feature for “shared” parties, where a party might be co-owned between 7 Participants, with a 5-out-of-7 signature policy. If you used that kind of party as an issuer, you still get high privacy and importantly privacy per contract, but you also get fault tolerance for the issuer participants.
Thanks for the reply.
I think we should have a behavior specified at the language level so that the abstraction is captured there and one would not have different executions based upon where one is creating a template.
Can’t we already have something along those lines via
wellKnownIssuers : [Party]
wellKnownIssuers = mapOptional partyFromText ["UST", "SNB", "Bernhard"]
template TheMoney with
issuer : Party
owner : Party
amount : Decimal
where
signatory issuer
observer owner
ensure issuer `elem` wellKnownIssuers
Similarly, isn’t the intent for Party
’s to be hosted on more than one participant node? That would allow an issuer of lots of assets, (ex. a government treasury/central-bank/money-printer), have a Party
that is shared and distributed amongst many small domains so that they are able to authorize transactions?
Another thing to consider is that there might be assets (ex. art) or instances (Party
s want to transact without access to the issuer because they are stranded on a desert island with great wifi) where we do want to have something like backchains. And you can almost do that with Daml
template Asset with
issuer : Party
owner : Party
name : Text
where
signatory issuer
observer owner
choice Give : ContractId Asset
with
newOwner : Party
controller owner
do create this with
owner = newOwner
choice Transfer : ContractId TransferedAsset
with
newOwner : Party
controller owner, newOwner -- to avoid propose & accept.
do
create TransferedAsset with
owner = newOwner
previousOwner = owner
assetId = self
history = []
..
template TransferedAsset with
owner : Party
previousOwner : Party
name : Text
assetId : ContractId Asset
history : [ContractId TransferedAsset]
where
signatory owner, previousOwner
choice TransferAgain : ContractId TransferedAsset
with
newOwner : Party
controller owner, newOwner
do
create TransferedAsset with
owner = newOwner
previousOwner = owner
history = self :: history
..
choice Convert : ContractId Asset
with
issuer : Party
controller owner
do
-- need issuer rights
-- create Asset with ..
undefined
setup : Script ()
setup = do
alice <- allocatePartyWithHint "Alice" (PartyIdHint "Alice")
bob <- allocatePartyWithHint "Bob" (PartyIdHint "Bob")
aliceTV <- submit alice do
createCmd Asset with
issuer = alice
owner = alice
name = "TV"
bobTV <- submitMulti [alice, bob] [] do
exerciseCmd aliceTV Transfer with newOwner = bob
-- alice goes away
carl <- allocatePartyWithHint "Carl" (PartyIdHint "Carl")
carlTv <- submitMulti [bob, carl] [] do
exerciseCmd bobTV TransferAgain with
newOwner = carl
doug <- allocatePartyWithHint "Doug" (PartyIdHint "Doug")
dougTv <- submitMulti [carl, doug] [] do
exerciseCmd carlTv TransferAgain with
newOwner = doug
the question though is how can Alice
, the original issuer verify the transaction history.
As soon as I wrote this I realized what feature would solve this, we need something like a FutureParty
; a Party
to be specified later.
data FutureParty = FutureParty Text -- Should be Opaque
deriving (Eq, Show)
instance IsParties FutureParty where
toParties _ = undefined
assigned : FutureParty -> Optional Party
assigned = undefined
template Asset with
issuer : Party
owner : Party
name : Text
where
signatory issuer
observer owner
choice Give : ContractId Asset
with
newOwner : Party
controller owner
do create this with
owner = newOwner
choice Transfer : ContractId TransferedAsset
with
newOwner : Party
validator : FutureParty
controller owner, newOwner
do
create TransferedAsset with
owner = newOwner
previousOwner = owner
assetId = self
history = []
..
template TransferedAsset with
owner : Party
previousOwner : Party
name : Text
assetId : ContractId Asset
history : [ContractId TransferedAsset]
validator : FutureParty
where
signatory owner, previousOwner
observer validator
choice TransferAgain : ContractId TransferedAsset
with
newOwner : Party
controller owner, newOwner
do
create TransferedAsset with
owner = newOwner
previousOwner = owner
history = self :: history
..
choice Convert : ContractId Asset
with
issuer : Party
controller issuer
do
-- Not that this is superfluous; once the Party API assigns to the validator
-- FutureParty, that real party gets to see all the previous transactions and
-- is now an observer here.
assertMsg "Not validated by the right party" $
assigned validator == Some issuer
create Asset with ..
For a given contract you’re fixing the issuer here so if that issuer is down you haven’t actually gained anything. The feature Bernhard is alluding to is closer to your FutureParty
. You have one fixed party at the Daml level but the confirmers of that can be multiple parties and those can change over time. So you can have rules like 2/3 of a consortium must confirm for the issuer party.
It isn’t necessarily the instance of the contract that matters but the ability to execute the choice?
template TheMoney with
issuer : Party
owner : Party
amount : Decimal
where
signatory issuer
observer owner
ensure issuer `elem` wellKnownIssuers
choice Give : ContractId TheMoney
with
issuerToUseToValidateTransaction : Party
newOwner : Party
controller owner
do
create this with
owner = newOwner
issuer = issuerToUseToValidateTransaction
One could try issuers to Give
who are available until the transaction works.
Signatories have to validate the execution of choices. So in your example, issuer
still has to validate the transaction.