Total ordering of domain messages

This tweet and related forum topic made me think about the total ordering assumption of Canton domains, which “… ensures that participants see all confirmation requests and responses in the same order”. What concrete possible problems / examples are addressed by total ordering in Canton?

In traditional blockchains and esp. in those relying on a UTXO model it’s clear why atomic ordering (which is essentially equivalent to consensus) is required. However in Canton/DAML, the individual participants are still required to validate transactions as they arrive and somehow need to reach “consensus” among all confirmers, so this is less obvious.

My (possibly wrong) assumption is that contract archival (or lack thereof) is the main contention source of possible “double spending”. However, given that contract signatories are always confirmers (of archive actions), if they behave according to the protocol they can detect double-spends regardless of the ordering of transactions. Say a participant P1 issues a transaction that archives C and a participant P2 exercises a choice on C in another transaction. We want P1 and P2 to consistently see either TX(archive C)->TX(exercise C) OR TX(exercise C)->TX(archive C) - therefore the ordering mediated by the domain. However if they saw the transactions in different orders (i.e. no consensus on the “order”) they would still need to validate the transactions locally (and reach consensus on the “validity” of the transactions) - which they won’t be able to achieve in this case, so most likely none of the transactions will be accepted. So the problem here seems to be a liveness issue - which one may naively argue will hopefully not happen frequently and can be addressed by acknowledging the conflict and retry later (or use some other tie-break policy)?

2 Likes

One of the core guarantees that Canton is designed to give is that any set of honest participants can reconcile their views into a valid Daml Ledger, even in the presence of malicious third participants.

Now imagine Eve submits two transactions

TX1: Create(C_A), Archive (C_B)
TX2: Create(C_B), Archive(C_A)

where C_A is visible to Alice and C_B is visible to Bob. If Eve could make sure that Alice sees the order [TX1, TX2] and Bob sees order [TX2, TX1], then they respectively have these views:

Alice:
TX1: Create(C_A)
TX2: Archive(C_A)

Bob:
TX2: Create(C_B)
TX1: Archive(C_B)

Both perfectly valid so Alice and Bob will happily confirm and accept these transactions. But if they ever try to put their views together, there is no way to reconcile TX1 and TX2 into a valid Daml ledger.

Thank you. I think I see your point. However, I would argue that malicious Eve should not be allowed to deliver TX1 and TX2 as described above. If any “archive” operation had to reference the merkle root / hash of the transaction that created the contract in the first place (similarly to “known since…” in sandbox) there would be no way to build such a sequence. Eve could only use a bogus “back-pointer” for the transaction that created C_B in the Archive(C_B) statement in TX1, which would be detected by Bob, who would refuse to confirm TX1. Since TX1 fails, so will TX2, because Alice would see Archive(C_A) of a contract C_A that was never created.

Transactions

TX1: Create(C_A), Archive (C_B)
TX2: Create(C_B)

would be however feasible because Eve could now create a valid TX1 pointing back to TX2. The only way this sequence will succeed if if Bob sees TX2 before TX1 - so the only “acceptable order” would have to be [TX2, TX1] for Bob. Alice sees only TX1 anyway, which would be confirmed IFF Bob receives TX2 before TX1

How can anyone validate whether such a pointer is “bogus”? Ie how would Bob detect it?

If you assumption is that all transactions are part of the same merkle tree, you have a total consistent order between transactions again. But where is that ordering coming from?

Imagine Alice does a Create(C_A) and Bob does a Create(C_B) . With Daml’s privacy, they don’t find out about each others’ transactions. There cannot be a pointer from one to the other.

C_B is “visible” to Bob, and to Eve too, in that at some point C_B had to be created by a transaction that was confirmed by both Bob and Eve. Then at some other point they both see the archival of C_B. Therefore it seems possible for the Archive(C_B) action to reference the transaction that created C_B - unless it is admissible for a party to see an archival of a contract it didn’t know was created (if we leave divulgence aside for the sake of this example). By “pointer” I mean a hash commitment to the TX message that created the contract, which means a TX archiving a contract can be built only after the transaction creating it has been built (unless it’s in the same tx of course). By using a commitment (hash, merkle root…) instead of an arbitrary id for the back-link we are simply preventing the case you described, because TX1 and TX2 in that shape could not be constructed.

Imagine Alice does a Create(C_A) and Bob does a Create(C_B) . With Daml’s privacy, they don’t find out about each others’ transactions. There cannot be a pointer from one to the other.

Say Alice does a Create(C_A) in which Alice is obviously a signatory and Charlie is an observer. Bob learns nothing about C_A, The resulting TX (which may contain other things not disclosed to Charlie) has hash 0x0a1b2c… Now when Alice (or Charlie, if allowed) want to archive C_A in another transaction they must disclose the reference to 0x0a1b2c… which must be known to all stakeholders of C_A, because that’s the transaction that created it. The idea is that everybody who has the right to see an archival of a contract they are stakeholders of, must be able to back-link to the transaction that created the contract (divulgence might make this less feasible, though)

While thinking more about (total) ordering one good reason to do the sequencing seems definitely to prevent front-running, i.e. a participant receives an interesting confirmation request coming through and issues a new transaction while holding back the confirmation response in the hope of gaining an advantage based on the information it received. With total (and/or causal) ordering this should not have unintended / malicious consequences - but of course a malicious receiver could arbitrarily reject the original confirmation request and potentially still gain something out of their front-running attempt, depending on the context.

Ok, you are saying Eve can’t create a circular dependency cryptographically secure hashes of TX1 and TX2. You are probably right, but let’s replace the creates by fetches then:

TX1: Create(C_A), Archive (C_B)
TX2: Fetch(C_B), Archive(C_A)
TX3: Fetch(C_A), Archive(C_B)

Alice sees [TX1, TX3, TX2], Bob sees [TX1, TX2, TX3], thus getting views

Alice:
TX1: Create(C_A)
TX3: Fetch(C_A)
TX2: Archive(C_A)

Bob:
TX1: Create(C_B)
TX2: Fetch(C_B)
TX3: Archive(C_B)

I don’t think you can expect a backlink from a consuming action like an archive to a non-consuming one like a fetch, for in the absence of a total order, who decides what the “latest” non-consuming choice to backlink to would be?

The main point here is:

With sequencers providing a total order to all confirmation requests, we know how to get this property:

any set of honest participants can reconcile their views into a valid Daml Ledger, even in the presence of malicious third participants.

Without it I don’t know how to get the above property. But I don’t know that we have a formal proof that it’s impossible, so maybe there’s a solution out there. Not needing consistent ordering on messages would open a lot of possibilities for Canton sequencer implementations so I’d love to shed that requirement. So please keep thinking about it :slight_smile:

Thank you @bernhard for playing along :star_struck:

Alice:
TX1 @123…: Create(C_A)
TX3 @789…: Fetch(C_A)
TX2 @456…: Archive(C_A) created @123

Bob:
TX1 @123…: Create(C_B)
TX2 @456…: Fetch(C_B)
TX3 @789…: Archive(C_B) created @123

I don’t see much evil that Eve is doing here.
It just so happens that transactions are not totally ordered, BUT both Alice and Bob see archivals after creations, which is, at first glance, all that is needed here. From a ledger point of view, those are indeed the only writes, and so fetch actions are not all that important (caveat: if we forgo disclosures altogether).

Both Alice and Bob are informees of the creation of the respective contracts C_A and C_B, so they both know that the “Archive” actions on their contracts are legit (e.g. by “cryptographic correlation” between archive and create transactions). Their respective ledgers based on write actions do not conflict at all.

  1. TX1 Alice:Create(C_A), Bob:Create(C_B)
  2. TX2 Alice:Archive(C_A)
  3. TX3 Bob:Archive(C_B)

Things get shaky only if you consider read actions as well, because Alice would end up reading C_A in TX3 after it’s been archived in TX2. I emphasized that after because:

  1. if there is no order, there is no after, i.e. before->after relationships do not universally hold
  2. a fetch is a read operation, so it does not alter the state of the ledger, so why should we log it
  3. any fetch must succeed as long as it happens within a window between a “create” and an “archive”

I guess my totally-not-formally-verified, intuition-only assertion is that we need total order only for “create” and “archive” actions, i.e. an “archive” must always follow its “create” (the cryptographically-secure back-link idea could be a way to achieve that) - or transactions are aborted by honest players.

Perhaps we are looking at a too simplistic cases (2 parties, disjointed contracts), so I’m not really sure this scales to more complex cases at all :man_shrugging:.

However, at this point I am tempted to rephrase the initial question as: “if you could guarantee transactions that archive contracts always follow transactions that create those contracts (and fail otherwise), would you still need total ordering?”.

Short answer: I don’t know.

But more importantly to me, that hypothetical is a major change to Daml’s causality model and with that the Daml ledger model. I have no immediate intuition just what exploits this would open up for Eve, but my gut says that writing secure Daml applications would get much harder.

You are most likely right. I am still a bit confused by the causality model, where it says a Daml Ledger need not totally order all transaction (sic), unlike ledgers in the Daml Ledger Model” and " Daml ledgers do not totally order all transactions. So different parties may observe two transactions on different Participant Nodes in different orders via the Ledger API. Moreover, different Participant Nodes may output two transactions for the same party in different orders". On the other hand the canton total order assumption “…ensures that participants see all confirmation requests and responses in the same order”, which would seem to imply participants see transactions in the same order indeed. I guess there are multiple conceptual layers involved in the description of what essentially feels like should be the same thing, especially when trying to reason about what the “global shared ledger” really is.

The sentence “…ensures that participants see all confirmation requests and responses in the same order” needs the extra term “on a single domain” or similar:

“…ensures that participants see all confirmation requests and responses on a single domain in the same order”.

Once you go into multi-domain setups you lose the property of having a single total order so as contracts move around between domains, the order can become partial. This is currently early access stuff, but the ledger model extensions are described here. The local ledgers/causality piece is already prepped for multi-domain.