Parallelizing EVM With Sei | Jayendra (Jay) Jog - Sei
Ethereum Denver·Wed, Oct 9, 2024, 12:00 AM
APPLICATIONS FOR ETHDenver 2024: #YearOfTheSpork ARE OPEN APPLY NOW https://ethdenver.com/apply
Transcript
Very good, GM GM to all. Let's begin. Well, my name is Jay. I am a co-founder of SE Labs and we are building the first stalled EVM. And today I wanted to take some time to talk about both what we're building and some of our more controversial opinions, especially at Et Denver.
So let's move on . In the last two years we have begun to develop a thesis: the EVM is here to stay. 87% of crypto developers currently work in the EVM. Basically, everyone who is crypto native is an EVM developer, and if you ask them about moving to a new runtime environment, say Cosm or any other, there is usually a lot of hesitation, and this is due to two main components. The first is that it represents a fairly large security risk.
If you are comfortable with Solidity and know the inner workings of the EVM, it is quite risky to move to a new runtime environment. Let's say, writing a smart contract in Rust. A small mistake can ruin your entire project. That's why switching to a new runtime environment is so intimidating . Furthermore, there is also this idea of alignment with Ethereum, and many developers feel some resistance to it.
They feel that it goes against their values if they move away from the EVM. And this is fundamentally because EVM is not just a technology stack. At its core, the EVM is an ecosystem. It's not just the technology, but also all the builders, all the tools created, all the shared knowledge, and all the research surrounding it. And because of that, we fundamentally believe that EVM is not going anywhere.
Definitely not in the near future. Probably not in the medium or long term either. The question then is, what is the current limitation of EVM? What is the biggest problem with EVM? I will argue that the biggest problem with the EVM right now is performance, or more specifically, the lack of performance.
Throughput is the general idea of how much you are able to process, how many transactions, or how much work you can process per unit of time. And if you look at Ethereum L1 or the roll-ups built on Ethereum, they really can't process more than 50 transactions per second. So transactions per second. So what does this mean for the user experience and the developer experience? Well, for the user, it means you have to pay ridiculous gas rates .
For example, I'm sure many of you have been doing things on Ethereum recently. If you try to do anything on L1, and if it's a 100 GB tariff, you're going to spend a fortune on gas. And come on, nobody wants to pay those kinds of fees. This ends up causing most of the population, honestly, to be excluded from participating in the chain. And that is definitely not the world we hope to see happen in the chain in the future.
The other problem is that it also becomes much more restrictive for developers. As a developer, if you're limited to a 50 TPS environment, it becomes much more difficult to build the kind of applications you'd see in Web 2 or traditional finance. And it forces you to use anti-patterns just to get things working in the EVM. An example of this would be automated market makers. Automated market makers do not exist in traditional finance.
And the reason they've become so popular, the main reason they 're popular in the chain, is because they fit within the limitations of what the EVM actually allows you to do. So there are a lot of limitations that come with this low performance. So the question is, how do we actually solve this? And that's where parallelization comes in. If you look at the current landscape of EVM-based chains, they are all essentially single-threaded.
If you receive 100 transactions, you will have to process each one individually. This is very simple to implement from an engineering point of view, but it is not efficient. It is extremely inefficient and does not take advantage of modern hardware. Half of you are using your phones right now; Those phones have multiple cores. They are capable of processing multiple workflows simultaneously.
And if you have single-threaded runtime environments , you're really not taking full advantage of the hardware you have available. So, by parallelizing the EVM, greater performance is unlocked, which helps reduce gas fees and opens up a much larger design space for developers. And that's basically the inspiration behind SEV2. There are so many different things we 've ended up doing as part of SEV2. We support EVM.
We have added optimistic parallelization. We have created a new database layer and we also have a "twin turbo" consensus which I will briefly explain. If we look at how parallelization is currently supported in the chain . Basically, there are two ways the teams have done it. The first approach involves having the developers define the dependencies.
This is the approach seen with Solana. When a transaction is submitted, all dependencies must be provided. The second approach is for the chain itself to determine what the dependencies are. This results in a much better experience for the developer, but it adds complexity to the chain because the developer has to figure out how to do it. But if you can get the chain to determine dependencies, it's a better end state to reach, because it drastically improves the developer experience.
And that's exactly what we decided to do with Sei: we used optimistic parallelization. The general idea here is to try to run everything in parallel initially. Then, there will be conflicts in transactions that affect the same state, and those are re- executed. The good thing is that, when re-executing the transactions a second time, much of the state they require has already been loaded into memory. Therefore, it is much more efficient because there is no need to worry about reading from the disk.
With this approach, we have been able to begin parallelizing the VM. However, there is a downside to parallelization: state inflation. State refers to the data that must persist to process new transactions and generate state roots. At a very general level, the state consists of two main ideas. One would be the account balances.
Like saying, I have 10, you have 20; It also includes the status of smart contracts. Let's say there is a Uniswap smart contract. Uniswap smart contract. What are the actual liquidity pools ? What tokens have been deposited?
All of that must persist in the full nodes and must be information stored in the chain. Higher throughput, meaning more transactions processed, creates more state, and this is something you should keep in mind. And when you have more state, the result is that, first of all, you need to manage more state storage. If there are 10 terabytes of state, most consumer hardware will not be able to support it automatically. You need to start upgrading your hardware.
This leads to centralization forces, because people are forced to use custom hardware to run a full node, and that creates problems. So you need to be aware of how much state is being created. And the second thing is state synchronization. So, when you start running a new node, let's say you want to be a network validator, you need to import all the states to process the new transactions. There are 10 terabytes of data that you need to import.
That takes a long time. It could take hours, days, or potentially even longer. And that also has significant impacts on the network. You definitely need to be aware of how to manage state storage and synchronization. To help with that, we introduced the idea of SADB.
SADB essentially has two main components. The first is a memory-mapped IVL tree and the second is asynchronous writes to disk. The way the memory-mapped IVL tree works is that it has three files on disk; You take the entire tree, divide it, and place it in those three files. This is different from the approach AV1 had, where the entire tree was stored directly on disk. When storing the entire tree, there is a lot of external metadata that needs to be saved.
An example of this would be if you have an intermediate node that does not have a key-value pair. Guess what? There is data that must be allocated to store those key-value pairs. If you have a child node at the base of the tree that doesn't have a left child or a right hash, a left child or a right hash, guess what? You still need to store something for the left hash and the right hash.
And that ends up generating a ton of unnecessary metadata that you're just tracking. When you have these three files stored on disk as part of this memory-mapped file system, you can avoid all that unnecessary metadata, which helps reduce the state size by 60%, which is huge. It's a huge improvement in the amount of data you actually need to persist. And when you have less data, that also improves state synchronization times because, suddenly, if you only have 40% of the amount of data that needs to be imported, it's easier to import that information. Additionally, if you have three separate files that you are writing to, then you can parallelize the writes to these three distinct files.
So that also allows you to get approximately a 12x improvement in state synchronization times . That's huge in helping to prevent the expansion or "bloat" of the state. The other benefit of sadb comes from asynchronous writes to the disk. What happens with SDB is that you have all this data stored in this memory-mapped IVL tree , which are these three files on disk. Then, you extract as much data as possible into memory.
Ideally, the entire IVL tree can reside in memory. If not, there are subsets of data that you can extract to memory. And once you extract this data into memory, you can generate the state root within memory itself. Normally within memory itself. Normally , to end the execution of a block, you need to import this state root or have some kind of state root that is used as part of the block header.
And in the case, for example, that you can get the generation of the state root to happen in memory, that makes it much, much easier to simply terminate the processing of the block at that point. And then you can write asynchronously to a write-ahead register, and also write the data to disk asynchronously. This approach helps achieve a 287-fold increase or improvement in confirmation times, because you no longer need to wait for the confirmation to occur on disk. You can simply keep it in memory and then, once it's written to the write-ahead record, you have disaster recovery. So it makes it much more efficient from that side, and that really helps improve the overall performance of the entire chain.
Another thing we continue to use, which was already present since CV1, is the " present since CV1, is the " twin turbo" consensus. The "twin turbo" consensus is Sei's novel consensus mechanism . This allows us to achieve a completion time of 390 milliseconds completion time of 390 milliseconds , making it the fastest chain currently on the main network. And this will continue to be the case for SEV2. In addition, it has a single-slot purpose with an internationally distributed set of validators.
The two major breakthroughs we had here to achieve this are optimistic block processing and smart block propagation. The way optimistic block processing works is highlighted in this diagram, where there is typically a block proposer proposing a block. Other people in the network receive the block and then have two rounds of voting, a pre-voting step and a pre-confirmation step, and then they start processing the block. However, that is very inefficient because, as a validator, you receive the block and then do nothing with it during these two voting steps, which is totally unnecessary. The idea behind optimistic block processing is that, as soon as you receive the block, you can start voting on it simultaneously while also starting to process it.
And if you process the block, you can update a candidate state instead of updating what should be the canonical state. If the block ends up being approved by the network, then you can confirm that candidate status. If the block is rejected by the network, then you can simply discard the candidate state and no harm is actually done. Thus, with this approach, you can achieve a significant improvement in completion time. The other idea we introduced was intelligent block propagation.
The way things normally worked before was that there was a block producer who needed to send a block proposal message along with the entire block, say, of a thousand different transactions across the network. For a validator to start voting, it needs to receive all one thousand transactions, even if they are all already in its local mempool, because there is already a P2P layer where these transactions are propagated . So, the current innovation with smart block propagation is: what if you allowed validators to attempt to build a block locally before waiting to receive the block over the network? What ends up happening is that the block producer will only send a block proposal message, which will be an ordered list of transaction hashes. Then, the validators will receive this ordered list of transaction hashes.
After that, they can start checking their own mempool and try to build the block locally. If they have everything, which is the case most of the time, then they can process the block immediately. Otherwise, if they don't have it, they can wait to receive all the content through the network. So, from these two approaches, we have managed to observe a finality of 390 milliseconds, which, I mean, is the fastest you can have, or that I assume any network has, in terms of finality at this time. Brilliant.
So , I would say that's where we are right now . I also wanted to review some of the more controversial ideas we have, based simply on the debates we've been having or ideas that people have been sharing today. The first idea we wanted to review is that we don't think it's good to focus blindly on the number of validators blindly on the number of validators . And I think that, especially at ETH Denver, this might go against what many people believe. But if you think about validators, how do you actually incentivize people to run validators?
Well, there must be some kind of mechanism in place so that all the validator costs, i.e., the cost of the hardware, the operating costs, and the opportunity cost, are offset in some way. This could be through foundation grants, as seen in some ecosystems. It could come from transaction fees come from transaction fees , plus the possible redistribution of MEV, plus network inflation.
But running validators is expensive. And if you have 100,000 validators, then the network is basically paying the cost to keep those 100,000 validators operational. Otherwise, people will stop carrying them out. So it ends up being very expensive. And most of the time, it's like you're repeating the same job.
For example, if you have 100,000 validators and there are a thousand transactions in a block. The 100,000 validators are literally re-executing those same transactions. And I mean, it works, but it's not necessarily the most efficient. So, if what matters to you is decentralization, trustlessness, and verifiability, I would argue that it's better to have a smaller, sufficiently decentralized set of validators, where people can consume zero-knowledge proofs of any state transition that occurs to verify the state of the chain they want using some kind of lightweight client. So, what would this look like?
One approach would be to have block producers or other network participants create zero-knowledge proofs asynchronously, and then have them used by thin clients, such as one you might be running locally. This could simply be part of MetaMask. Well, you receive a zero-knowledge proof (zk proof) and from there you can verify the network status without needing to run a validator or a full node. This allows for a smaller set of validators, making the network more sustainable. It allows these validators to have potentially more efficient machines and also provides users with verifiability and decentralization in the network.
I believe it's a model that many chains will move towards in the future. It's interesting because many of the recent L1 releases in the last few years have had smaller validator sets, and I think many more will follow this direction. The second idea I want to address is that validiums are not complete L2s. I think many people see validiums as the best of all, since they get external data availability (DA) while still remaining aligned with Ethereum. But if you use a validium, you get the minimum security between the settlement layer and the DA layer the settlement layer and the DA layer .
So, if your settlement layer is Ethereum and your DA layer is Celestia is Ethereum and your DA layer is Celestia , you basically get the security of the DA layer and not the full security of Ethereum. Therefore, at that point you're not really a full L2 Ethereum. You're closer to being a sovereign rollup or an L1 than a true rollup. We created this diagram to explain it, but rollups are the only ones that get liquidation and Ethereum DA. If you don't get Ethereum's DA, you won't be fully secured by Ethereum, and in that case, you're not a true L2.
Another thing we've started to see is a strong focus on trying to improve disk reads, perhaps through things like asynchronous I/O. I would argue that, instead of trying to improve disk reads, greater gains will be made by encouraging memory access as much as possible, because in the end it is impossible to avoid disk reads completely and there will always be overhead when reading from disk. It is best to configure your mechanisms in a way that encourages memory access as much as possible. So, LRU caches do this implicitly; For example do this implicitly; For example , if you have data that a transaction is using and this data is cached, then future transactions that access that same state can make use of it. So that's an example of a very simple way to encourage memorization.
Another example that I think will become more common with multidimensional tariff markets in the future is having different gas prices depending on whether you perform memory reads or disk reads. If you perform memory reads, ideally you would pay a lower gas rate , and you would receive a penalty if you perform disk reads; I believe there are also very deterministic ways to implement this. For example, you could have a priority status where people participate in some kind of auction to bid for it. And from there, that priority state is the one that will most likely be cached. Then, from there, you can ensure that you are getting the maximum possible access to memory .
I definitely think this is something we'll start to see in the next year or two from different chains that are considering multidimensional pricing. The last idea I'm going to mention is that asynchronous writing is currently underexplored. We observed a 287-fold increase in confirmation times, and the reason is that the generation of the root state occurs in memory, allowing the process to continue and the rest of the writes to occur asynchronously. I think it's an idea that, in general, has been little explored and that can definitely result in much, much better performance. And there are ways to ensure, for example there are ways to ensure, for example , that if you use a write-ahead log and things are written to disk later, there are disaster recovery mechanisms in place.
So it's very easy for the network to be generally reliable. So yes, I think there are a lot of interesting things that will be explored from our side, and that's all from me. Thank you all.
Automatic transcript — names and jargon may be misspelled.