Some thoughts on controllers, promises, and fault isolation

I think the frame of promises may be helpful to analyze what kinds of properties we want distributed controller operation and state synchronization to have. Specifically, when I talk about promises in this context, I mean promises by a controller to accept inclusion of certain future histories under certain conditions. Controllers always have control over what histories they accept - what transactions they sign over - so these are promises which they can make, and promises on which users will at least implicitly rely in order to make decisions about how to move their resources around the system - and I think it may help to make them explicit.

I think it’s also helpful to keep in mind that resources in the Anoma system will always be tied to resources in the real world by a single originating external identity, who is - at least implicitly - promising to honor the digital representation (for example, allowing redemption for a physical good). When users are using resources “downstream” of that originating external identity (possibly also a controller), they ultimately care whether or not the resources which they’re using can be redeemed back “upstream”, since that upstream path is what connects them to the real world. Accordingly, users of downstream resources care about the existence of a promise chain which they could rely on to redeem the resource if desired. Typically, this redemption should not actually have to be enacted - but it must be clearly possible.

Let’s assume that this originating identity enforces, at minimum, local linearity: no more promises can be redeemed than were originally created. The first question, then, is - considering that there may be potentially conflicting histories, which one is accepted?

As we have only local time, if we want to eventually accept a history, all of our options look like “FIFO + x”:

  • FIFO only - as soon as a valid history is provided, accept it, and reject all future conflicting histories (this is what IBC does, with the specific case of fungible tokens).
  • FIFO + delay - as soon as a valid history is provided, start a timer. When the delay period has passed, if no other valid histories were provided, accept the history. If another valid history is submitted during the delay period, run a resolution protocol, which may rely on other controllers, further delay periods, etc.
  • FIFO + delay + endorsements - same as the delay period, but endorsements from various controllers can shorten the delay period (as they provide additional evidence that a particular history is canonical in other parts of the network).

I think these are all the possible options.

No more time for writing today, to be continued…

A few thoughts:

Our promises get a bit more complex here with the addition of “multiple parents.” For example, suppose Alice issues 1 AliceCoin, and Bob issues 1 BobCoin. At some point these are both on chain C, so the AliceCoin controller DAG looks like “Alice → C”, and the BobCoin controller DAG looks like “Bob → C”. If there is some kind of transaction that uses both (perhaps someone trades the AliceCoin in exchange for the BobCoin), the results (the new AliceCoin and the new BobCoin) would have a controller DAG in which Alice and Bob are sources with edges to C.

The reason is that the new traded coins are contingent on both Alice and Bob: The premise of the trade was that Alice promised this was the “true” ownership of the AliceCoin, and Bob promised this was the “true” ownership of the BobCoin. Either Alice or Bob could render the results of that trade invalid by lying, and forking the state of AliceCoin or BobCoin. The trade results are only as good as the promises from Alice and Bob to maintain a consistent state (corresponding to real things). You can, in some sense, have some kind of endorsement where chain C decides “I’m not going to accept any contradictory histories from Bob, so we can remove Bob from the DAG of this AliceCoin.” Unfortunately, figuring out exactly when you can do this / what invariant you have to maintain is application-specific.

  • this does, however, make a good argument that not all “origin” nodes (nodes which cannot be removed) in the DAGs of the inputs of a transaction have to be “origin” nodes in the DAGs of the outputs. Perhaps that can be application-specific?
1 Like

I’d default to (and I think my ART report draft implies) pure first-come-first-serve. First-come-first-serve preserves maximum inter-chain speed, and the only time any of these choices matter would be if a chain forks, and we’re trying to figure out what to do with resources transferred from that chain. The important thing is that we can tell the difference between resources from different sides of the fork, so that users can decide which side they want to care about.

In general, you can only move resources from A to B if B has “endorsed” A. This is a problem if B has endorsed one fork of A, but chain C has endorsed another fork of A. However, you can still transfer resources from A to C to B. This gives the resources a different type from resources transferred from A directly to B, representing that they are different in an important way. Most notably, we want our controller DAG reduction moves to keep A in at least one of these resource’s DAGs, so we maintain Controller Label Integrity.

In fact, we could imagine integrating a “fork detected” flag directly into controller labels, which might be useful for something. Maybe we could explicitly not remove flagged nodes, so we’d forever be able to see that this came from one side of a forked chain?

1 Like

Hmm. My basic mental model - admittedly inherited from IBC, and perhaps wrong here - is that if I swap “Alice → C Alicecoin” and “Bob → C Bobcoin” on C - suppose that user 1 owned the Alicecoin and user 2 owned the Bobcoin - wouldn’t user 1 just end up with the “Bob → C Bobcoin” and user 2 with the “Alice → C Alicecoin”? That transaction should balance just fine - we don’t need to add the other controllers to the DAG to preserve any security properties, as far as I can see. Now, if we wanted to make a combined resource called “Alicebobcoin” which is redeemable for one Alicecoin and one Bobcoin, then it seems like we’d need to either track both controllers as ancestors of the new resource, or make it explicit that it can be redeemed only for 1 “Alice → C Alicecoin” and 1 “Bob → C Bobcoin” - but I don’t think we necessarily have to add controllers to the controller DAG just because resources were involved in a transaction together. I guess this would depend on the application-specific invariant which we want to maintain - if Alicecoin and Bobcoin are some arbitrary messages instead of “coins”, maybe we do need to track these dependencies - but then I think this interaction is something other than a “trade”.

I think that might make sense - it seems concordant with my reasoning above?

FIFO does preserve maximum speed, but I think we (or controllers) will want the option to add (perhaps conditionally variable) delays - perhaps applied only to resources of high subjective value - because it can provide far greater security in practice (where the network is usually capable of delivering a message within some t), and thereby reduce actual security costs - and because users are more willing to wait longer for transactions of high subjective value, which already happens in the real world. This is similar to a generalization of some of the ideas found in Plasma and IBC rate limits.