In an earlier article, I described how Scrypto is only possible with the Radix Engine’s specialized application environment and its asset-oriented features. But Radix’s roadmap to unlimited scalability is also only possible through a tight integration between Radix Engine and Cerberus, Radix’s unique massively-sharded consensus system. That tight integration is what will make Radix not just scalable in special cases, but also finally allow a world of DeFi dApps to scale without limit.
Our Cerberus whitepaper describes our unique consensus algorithm that enables unlimited scalability (as is planned for integration into the Radix Public Network in the Xi’an release on our roadmap). The key to unlimited scalability is unlimited parallelism. Parallelism means that many things can be processed at the same time without slowing each other down.
What’s a substate? A substate is just a little record of something where some particular rules had to be followed. For example, you might want a “token substate” type that records where some particular tokens are. One bit of token substate might say “these 5 XRD are in Bob’s account”. But the rules of token substates would require that, for that to be true, the transaction also had to include something like “these 5 XRD are no longer in Alice’s account”. That pair of substates would describe a send of 5 XRD from Alice to Bob, and the rules ensure that no XRD can ever be accidentally lost or created. We’ll talk more about why substates are important a little later.
Looking at these three features of Cerberus, you can imagine how Cerberus can allow an unlimited number of token transactions to be processed in parallel: The status of all tokens are assigned to substates (feature 3). Tokens that are held by millions of different accounts can be scattered across a practically infinite set of shards (feature 1). And when anybody wants to transfer some tokens, the specific shards that record the current ownership of those particular tokens can come to consensus (feature 2).
However, to be a Layer-1 network for DeFi with full smart contract functionality, we need much more than just token transfers. We need smart contracts, and this means understanding the relationship between the consensus layer and the application layer.
The application layer is what defines what the network can do. On Bitcoin, the application layer is simply the rules of the BTC token; there is no smart contract functionality. On Ethereum, the application is the Ethereum Virtual Machine (EVM) where smart contracts run, allowing the rules of the network to be programmable. Most smart contract platforms follow something very similar to the EVM application layer.
In order to provide its three features for scalability through parallelism, Cerberus requires two key things from Radix’s application layer:
The first is pretty obvious, and below we’ll see how Radix Engine uniquely defines substates for smart contracts in a very different way from Ethereum’s EVM.
The reason for the second requirement is more subtle. If a key part of parallelism is only conducting consensus across the shards that are necessary (Cerberus feature 2), then the application layer has to tell Cerberus which shards are relevant for each transaction. However, this is only possible with an application layer that knows what things are relevant – which substates matter to do what is desired.
The EVM is designed around the notion of global ordering, which means that absolutely everything that happens in the network is on a single timeline. This is necessary for the EVM because any transaction could make a change absolutely anywhere in the network (any smart contract); it’s impossible to predict. For this reason, the EVM cannot take advantage of Cerberus-style sharding and is fundamentally incompatible – at least if you want to take advantage of Cerberus’ scalability potential.
Radix Engine is instead designed around a notion of partial ordering where each transaction must specify the shards that will be included (although this doesn’t necessarily mean that the developer/app must specify it - as well see in a moment). This is a crucial feature to enable parallelism, but as we’ll see in a moment, it has to be done in the right way.
So the big question is this: If we want Radix to process transactions – ranging from simple tokens sends to complex multi-dApp DeFi interactions – how does Radix Engine define substates and how does it define what substates are relevant in a given transaction?
The answer is three key choices in how Radix Engine is designed:
1. Radix Engine treats tokens as global objects at the platform level. This is important to let us parallelize the movement of assets as much as possible.
As discussed in previous articles, every DeFi application revolves around managing assets, and so Radix Engine makes assets a native feature of the platform as resources. Resources are the basis for tokens, coins, and even things like “badges” used for authorization.
This has enormous benefits for development, but it also means that all assets can be freely scattered around substates and shards as needed. The typical EVM model only implements tokens through individual smart contracts, which causes an immediate bottleneck for sharding since all transactions involving a given type of token have to go through that single smart contract. Global resource-based token assets on Radix give us the flexibility we need.
2. Radix transactions are unique and based on “intent”. This is important to enable high throughput through dApps without conflicts.
You might assume that, in the substate model, transactions would have to specify the substates (and thus shards) needed – as in our example above with the matched pair of substates that created a send of 5XRD from Alice to Bob. And in fact that is essentially how transactions work on a few UTXO-based networks like Bitcoin and Cardano.
However, this approach creates serious problems for smart contracts that process lots of transactions at the same time (ie. most of them). One transaction may be okay: my wallet can read the smart contract and determine, for example, which tokens should move in and out of it as a result and include all of those substates in the transaction. But think about hundreds or thousands of people using a DeFi smart contract like a Uniswap-like exchange dApp. My wallet might identify some tokens held by the smart contract that should come to me as a result of a swap and include those substates in the transaction (I don’t really care which tokens come out, but the transaction must identify specific ones). But by the time the transaction is submitted and processed, it becomes very likely that somebody else’s wallet identified the same tokens. Now suddenly my transaction is invalid and fails.
In fact, this is exactly what happened with the first DEX launched on Cardano. Many blamed this problem on Cardano’s “eUTXO” style state (which has some similarities to Radix’s “substates”), but really the issue wasn’t with eUTXO; it was the fact that Cardano’s transactions specify the eUTXOs directly, creating the problem that many transactions may specify conflicting eUTXOs.
Instead, transactions with Radix Engine specify intent. You can see what this looks like in my previous article about Scrypto. To use the DEX example, rather than the transaction saying…
“these particular tokens (eUTXOs) should be sent and received in the swap”
… a Radix transaction says…
“I want to send this quantity of tokens (I don’t care which ones) into the DEX, and I expect that I will receive a certain quantity of other tokens (I don’t care which ones) as a result.”
The specific substates don’t appear in the transaction submitted by the user. So how does Cerberus know which substates to use? Radix Engine itself determines them at the time of execution on the network.
So if my transaction says “I want to send 5 XRD”, Radix Engine can (deterministically) look at the token substates available and pick substates that represent these particular 5 XRD when the network actually processes the transaction. This means that the substates chosen are always good at that time, and conflicts between transactions are avoided.
(Note: Here I’m talking here about how transactions will operate once smart contract capability is added to the Radix network at the Babylon release and beyond, not the current Olympia release.)
3. Each smart contract (or “component” in Radix terms) – including all of its data and the resources it owns – is assigned to a single shard at any point in time. This is important to ensuring that each component can process as many transactions as possible.
Typically the result of each execution of a smart contract (each time it’s run as part of a transaction) depends on the previous execution in some way. For example, a DEX swap changes the balance of tokens in its pool which in turn changes the exchange rate on the next transaction. This means that there’s not much reason to spread different parts of a smart contract across multiple shards (which means more complex consensus for each transaction).
Instead, the most efficient way is to run each component (a Radix Engine smart contract) – including all of its data and resources it owns – on one shard. Radix Engine does this by capturing the full status of each component within a single substate at any given time. Any resources that move into the component become part of that substate; and when resources move out of the component, they are split out of that substate (because remember, resources are global on Radix!).
As a result, each component on Radix has full use of its own shard that can run unimpeded by anything else going on on the network. On Radix, user accounts are also components and so each account also gets its own shard automatically.
(Note: Radix Engine actually lets us be a little more clever. We don’t have to exclusively assign one component to one shard, but it’s the simplest way to understand how Radix Engine can optimize per-component throughput.)
Going further, a given dApp might actually be made up of multiple logically modular components, each with its own shard. For example, a DEX dApp might include independent components for each “pair” so that trades on each pair can run independently without slowing each other. With Cerberus’ virtually infinite shardspace, this allows a dApps developer to essentially provision as much parallelism as they need for their own application – on top of the parallelism the network offers between separate dApps.
When talking about “scalability”, it’s easy to lose track of what’s important, particularly when it comes to the ability to scale an ecosystem of real DeFi dApps. Today on Ethereum, dApps may be composable, but throughput is extremely low because all transactions, including smart contract calls, go through a single global consensus process that is very slow.
Most sharding concepts (such as Ethereum 2.0, Cosmos, and others) add a limited amount of parallelism through a fixed set of shards or side-chains. This increases throughput somewhat but immediately compromises (or eliminates) composability between shards/side-chains. And still these architectures put a limit on how much throughput can be achieved on a given shard and for all of the dApps and tokens on that shard.
Combining the features of Radix Engine and Cerberus, we can create a platform uniquely designed to scale a full ecosystem of real-world DeFi dApps through massive parallelism:
And all of this without compromising atomic composability because Cerberus can conduct cross-shard transactions, as needed, atomically and efficiently.
This sort of unlimited parallelism – not just for tokens but for DeFi dApps – is crucial to creating a platform that can truly scale DeFi to the level of global finance, and it is only possible through the tightly integrated design of Cerberus and Radix Engine.