State-of-the-art blockchains are yet to resolve the Blockchain Trilemma
Bitcoin and Ethereum de-facto lost decentralization due to their consensus algorithm, they are also very slow.
The majority of modern blockchains today use a variation of Proof-of-Stake consensus, meaning they are permissioned and
inherently not scalable to unbounded number of participants. Those that are based on Proof-of-Work are arguably not
secure to begin with, regardless of what algorithm they are using due to how cheap and easy it is to purchase compute in
large quantities today to attach the blockchain.
There are many blockchains that claim to be scalable, but all of them have a glass ceiling and are unable to scale
bandwidth, storage and compute sub-linearly with unbounded number of participants. Those that do not support sharding
are not scalable by definition, those that are sharded have other inherent limitations that prevent unbounded growth.
Only security is the property arguably achieved by many blockchains, but it is also debatable how real or useful it is
without two other properties.
The goal here is to find a way to remove existing bottlenecks and unleash the full power of the blockchain.
We need ABUNDANCE:
Abundance of consensus participation
Without practical limits
Abundance of bandwidth, storage and compute
With increased consensus participation, it must be possible to process more transactions, persist larger history
and do more computation without practical limits
Abundance of developers
Standard programming languages with familiar tooling, standard and efficient execution environment, infrastructure
that allows for a single app to scale to the capacity of the blockchain itself
While Proof-of-Work and Proof-of-Stake have solidified as primary ways to achieve consensus in blockchains, they
both inevitably lead to centralization and potentially to reduction of security.
With Proof-of-Work, this is primarily driven by the need to access to exotic hardware and cheap electricity, which
limits the number of people who can participate. With the growth of the network and difficulty increase, it also becomes
impractical for small miners to participate without mining pools due to infrequent and unpredictable rewards. Another
complication is the existence of services like Nicehash that allow to buy large amounts of compute for limited amounts
of time on an open market, which makes it very practical to own the network and in case of anything that is smaller than
Bitcoin arguably fairly inexpensive.
warning
As the result majority of Proof-of-Work blockchains are neither decentralized nor secure in practice.
With Proof-of-Stake currently staked owners of tokens get richer every day, which arguably makes it a permissioned
system. Due to being permissioned and requiring on-chain registration before participation, most Proof-of-Stake
implementations have to substantially limit the number of consensus participants by imposing things like minimum stake
amount as well as only selecting a subset of validators to be active at any time. Due to the nature of consensus
implementation, it is also important for consensus nodes to stay online, so those unable to are typically being punished
for it on top of simply having their tokens locked and not receiving rewards. This also leads to pooling and
centralization. Blockchains like Polkadot support nominated staking that improves scalability of consensus
participation
to some degree, but it is not a full replacement for being able to participate in consensus individually.
warning
As the result majority of Proof-of-Stake networks arguably are not really that decentralized in a sense of supporting
millions or even billions of consensus participants.
The alternative to above that is not talked about quite as much is Proof-of-Space. There are different variations of
it, but a shared trait between them all is permissionless participation with low energy usage requirements, while also
using the resource that is generic, widely distributed and abundant: disk storage.
The most prominent example of Proof-of-Space consensus is probably Chia. Chia is essentially an energy efficient
version of Bitcoin that wastes disk space to store random data just like Bitcoin wastes compute to calculate hashes. It
also happens to suffer, just like almost every other Proof-of-Space blockchain from Farmer’s dilemma, which makes
incentive compatibility a challenge.
While this is an improvement over Proof-of-Work, it turns out it could be even better.
Proof-of-Archival-Storage is a flavor of Proof-of-Space, more specifically Proof-of-Storage, that instead of filling
disks with random data stores the history of the blockchain itself. This not only resolves Farmer’s dilemma, but allows
for a few interesting side effects:
On-chain storage cost no longer needs to be hardcoded, it can be driven by on-chain Automated Market Maker thanks to
ability to measure not only demand for storage (size of blockchain history), but also supply (space pledged to the
network by farmers), resulting in approximation of real-world price of the hardware, regardless of the token price
The history of the blockchain can’t be lost regardless of how large it becomes, in an incentive-compatible and
sustainable way, as long as the blockchain is operational
Blockchain effectively becomes a Distributed Storage Network since any piece of data uploaded to the network can later
be retrieved from the network of farmers
To make pools even more interesting to farmers Autonomys Network was deployed with a voting mechanism built-in that
increases the frequency of rewards 10x comparing to just having block rewards, making it possible to receive weekly
rewards for even relatively small farmers.
important
As a result, Proof-of-Archival-Storage seems to be the closest ideal consensus mechanism that is both permissionless,
distributed and secure.
In contrast to many blockchains, the address is just a monotonically increasing number, decoupled from a public key (in
case of end user wallet) or code (in case of what is typically understood as “smart contract” in other blockchains).
The address is allocated on contract creation and doesn’t change regardless of how the contract evolves in the future.
This means that externally, all contracts essentially look the same regardless of what they represent. This enables a
wallet contract to change its logic from verifying a single signature to multisig to 2FA to use a completely different
cryptography in the future, all while retaining its address/identity.
This not only includes contracts created/deployed by users/developers, but also some fundamental blockchain features.
For example, in most blockchains code of the contract is stored in a special location by the node retrieves before
processing a transaction. Here code is managed by a system code contract instead of the node as such, and deployment
of a new contract is a call to system code contract instead of special host function provided by the node and code
contract will store the code in the corresponding slot of the newly created contract (see Storage model below for more
details).
A few examples of contracts:
a wallet (can be something simple that only checks signature or a complex smart wallet with multisig/2FA)
utility functions that offer some shared logic like exotic signature verification
various kinds of tokens, including native token of the blockchain itself
even fundamental pieces of logic that allocate addresses and deploy other contracts are contracts themselves
It’ll be clear later how far this concept can be stretched, but so far the potential is quite high to make as many
things as possible “just a contract.”
This helps to reduce the number of special cases for built-in functions vs. something that blockchain user can deploy.
All storage owned by a contract is organized into a container that has slots inside. It forms a tree with the root
being the root of contract’s storage, which can be used to generation inclusion/exclusion proofs when processing
transactions (see Transaction processing). Having a per-contract tree with storage proofs allows consensus nodes to
not be required to store the state of all contracts, just their storage roots. This is unlike many other blockchains
where contract may have access to a form of key-value database.
Each slot is managed by exactly one of the existing contracts and can only be read or modified by that contract.
Contract’s code and state are also slots managed by contracts (system contracts), even though developer-facing API might
abstract it in a more friendly way. It is possible for a contract to manage one of its slots too, like when a token
contract owns some number of its own tokens.
In contract to most other blockchains by “state” we refer to the inherent state of the contract itself, rather than
things that might belong to end-users. The right mental model is to think of it as a global state of a contract.
Let’s take a generic fungible token as an example. System state contract will manage its state, stored in
corresponding slot owned by the token contract. State will contain things like total supply and potentially useful
metadata like number of decimal places and ticker, but not balances of individual users.
In contrast to most blockchains, the state of the contract is typically bounded in size and defined by contract
developer upfront. Bounded size allows execution environment to allocate the necessary amount of memory and to limit the
amount of data that potentially needs to be sent with the transaction over the network (see Transaction processing).
This implies there can’t be more traditional unbounded hashmap there. Instead, balances are stored in slots of contracts
that own the balance (like smart wallet owned by end user), but managed by the token contract. This is similar to how
contract’s state and code are managed by corresponding system contracts.
Visually, it looks something like this:
Contracts do not have access to underlying storage implementation in the form of key-value database, instead they modify
slots as the only way of persisting data between transactions.
A transaction submitted to the network includes not only inputs to the contract call, but also storage proofs of the
storage items (code, state, other slots) required for the transaction to be processed alongside corresponding storage
proofs. This allows nodes to not store state of contracts beyond a small root, yet being able to process incoming
transactions (it is of course possible to have a cache to remove the need to download frequently used storage items).
Each method call of the contract includes metadata about what slots it will read or modify alongside any inputs or
outputs it expects and their type information. With this information, contract execution engine can run non-conflicting
transactions in parallel.
Not only that, it can follow the chain of calls ensuring a Rust-like ownership model where contract can’t recursively
call its own method that mutates slots because it’ll violate safety invariants. Recursive calls of stateless or
read-only methods are fine though.
The right mental model is that storage is contained within RwLock and each slot read/write results in
RwLock::try_read()/RwLock::try_write(). As a result, multiple methods can read the same data concurrently, but only
if nothing tries to write there at the same time. This rule applies through recursive methods calls into other
contracts, and any violation aborts the corresponding method, which caller can observe and either handle or propagate
further up the stack.
This makes traditional reentrancy attacks impossible in such execution environment.
Conceptually in pseudocode it looks something like this:
#![allow(unused)]
fn main() {
fn entrypoint(data: &RwLock<Data>) -> Result<(), Error> {
// This is the first lock acquisition, it succeeds
let data_write_guard = data.try_write()?;
// This will fail because we still have write access to the data
if call_into_other_contract(data).is_err() {
// This is okay, the data was given as an explicit argument
modify_data(data_write_guard);
}
Ok(())
}
fn call_into_other_contract(data: &RwLock<Data>) -> Result<(), Error> {
// Only succeeds if there isn't already write lock acquired
data.try_read()?;
Ok(())
}
fn modify_data(data: &mut Data) {}
}
Here is a visual example:
No state (Contract 1)Mutates own state (Contract 2)Reads state (Contract 3)fn compute(...)fn update(&mut self, ...)fn read(&self, ...)✅✅❌✅✅
Such a loop will be caught and the transaction will be aborted:
Mutates own state (Contract 1)Reads state (Contract 2)fn update(&mut self, ...)fn read(&self, ...)✅Start❌
Not only contract methods do not have access to general purpose key-value store (even if private to the contract), they
don’t have access to any other data except such that was explicitly provided as method input. They also can’t return
data in any other way except through return arguments. Execution environment will pre-allocate memory for all
slots/outputs and provide it to the method to work with, removing a need for heap allocation in many cases.
One can think about contract logic as a pure function: it takes inputs and slots, potentially modifies slots and returns
outputs.
Conceptually, all methods look something like this:
Environment handle allows calling other contracts and request ephemeral state, contract slots can be read and written
to, inputs are read-only and outputs are write-only. & or &mut in Rust limits what can be done with these types,
there is no other implicit “global” way to read or update ephemeral or permanent state of the blockchain.
Handling everything through explicit inputs and outputs results in straightforward implementation, analysis and testing
approach without side effects. In many cases, even heap allocations can be avoided completely, leading to fast and
compact smart contract implementation.