Written by Brecht. Thanks to Cecilia and Dani for review.
In this article, we aim to explain in more detail how Gwyneth works, or at least will likely work as Gwyneth is still under heavy development. For a less technical introduction, please refer to the Gwyneth introduction article. For a more in-depth explanation, please refer to the design doc (though this document is often outdated!).
Let’s start with listing the core properties/functionality again and take it from there.
Basic setup
Synchronous composability
L2 <> L2
L2 <> L1
Booster rollup
Real-time multi-prover
Based preconfirmations
We also give some more details about some of the practicalities:
A. Block proposing and proving
B. Value capture
C. Infrastructure
D. Interoperability with other (based) rollups
E. Tentative EIP idea for improving L1 sync composability
Gwyneth on the most basic level is a horizontal scaling solution for Ethereum. Gwyneth is a network of multiple identical Ethereum equivalent L2s with some additional functionality to make it easy to do cross-chain calls. In this way, developers can still easily make things look like a single chain. The function of each L2 is to add extra parallel processing and storage capacity to the Gwyneth network. This keeps hardware requirements for a single L2 low, while the throughput of the whole network theoretically does not have any limitations.
In the rest of the article, when we talk about an L2, we refer to one of these L2s in the Gwyneth network.
All Gwyneth L2s can directly access each other synchronously. They can also access L1 synchronously if the L1 proposer supports this. With “access”, we mean to read and write all states as if they are part of the same chain. From the user’s perspective, it looks like the transactions run on a single chain. The user creates a single transaction and pays the fee as usual. The gas price changes with the specific L2 on which the current part of the transaction is running.
We introduce L2 synchronous composability with a new precompile called XCALLOPTIONS
. This precompile allows a smart contract to define the target chain of the first following (DELEGATE)CALL
. The precompile switches the execution environment to the state of the specific chain, including the basefee.
For a stateless prover to prove this behavior, the required input is all the state commitments of all the accessed chains. We verify the different chain states with Merkle Proofs, and update the states upon verification. The output is then the updated state commitments.
For a builder to execute these cross-chain transactions, the latest state for each chain needs to be known. Users specify the accessed chains as part of the transaction, which informs builders of the required chain states. If a non-specified chain is used, the transaction reverts with the user still paying for the executed part. This prevents a DOS attack on builders, because they would have to simulate each transaction to know the required chain states.
Scalability
If Gwyneth looks like a single chain made up of multiple chains for a builder, how does it improve scaling? Although Gwyneth contains multiple L2s, nodes can sync an arbitrary list of L2s next to the L1, instead of syncing the entirety of Gwyneth states.
Because each L2 can be synced independently, builders can easily pick and match which chains they want to sync, and thus which chains they can build blocks for. We assume builders are sophisticated actors who have the necessary hardware to sequence their selected chains.
A node can sync each L2 independently, since data availability of each L2 is guaranteed on L1. When a node syncs, cross-chain execution is verified by proofs, so it can trust the output of cross-chain calls and progress the EVM execution. Specifically, we post the CALLDATA
and the corresponding RETURNDATA
of the call. When the call results in state changes, a new transaction for the targeted L2 is generated from the CALLDATA
and inserted in the block.
We can post state deltas just for node, or publish the full body of cross-chain transactions. The downside of the former approach is less transparency, although it could save on data availability costs. Currently, we are planning on only using the first approach.
The L2 builder needs to be the same entity as the current slot L1 builder to synchronously compose both chains. If that’s not the case, we need execution preconfirmation to guarantee the expected L1 state. To verify the L2 cross-chain reads and writes to L1, we need L2 block validity proofs being included with the L2 proposal transaction at the expected location in the L1 block.
Given the current infrastructure being built for preconfirmations, this type of composability should be possible shortly. However, the opt-in rate and economic incentives of L1 validators matter for this system’s usability. It remains unclear how the ecosystem will progress.
For implementation, we use the same XCALLOPTIONS
precompile to call into L1 from L2.
Booster rollups are rollups that execute transactions as if they are executed on L1, having access to all the L1 state, but they also have their own storage. This way, both execution and storage are scaled on L2, with the L1 environment as a shared base. Put another way, each L2 is a reflection of the L1, where the L2 directly extends the blockspace of the L1 for all applications deployed on L1 by sharding the execution of transactions and the storage.
Part of Gwyneth originates from booster rollups. Booster functionality allows easier horizontal scalability with less fragmentation. The L1 state can be accessed on any L2 at the same cost as the native L2 state.
To prevent fragmentation, it is efficient to use L1 as the global state to store most Dapps, since L1 is by design always available for cross-chain reading. If a new L2 is deployed, existing contracts on L1 can be directly accessed and used, which means L2 users can, for example, use Uniswap’s L1 deployment to transact on L2 without additional setup.
As activity grows on different L2s, congestion might trigger the basefee to increase because of EIP-1559, which results in higher fees for users. In general, activities are incentivized to happen on L2s with lower basefee, thus when additional capacity is required, additional L2s can be deployed. Dapps can choose their strategy depending on their use case and limitations.
An application can support full scaling by parallelizing its state and execution across all L2s. The smart contract can be deployed once to L1, and then the Dapp’s functionality could happen on each chain in parallel by doing DELEGATECALL
s to the L1 instance. Synchronization points are only introduced when doing cross-chain calls. A parallelizable Dapp spanning across different Gwyneth L2s can benefit significantly from this setup. For example, Uniswap pools deployed on L1 can have users transacting on L2s simultaneously with DELEGATECALL
s, with multiple pools’ state on different L2. Since pools are independent, Uniswap benefits from parallel execution. Users can also use cross-chain calls to interact with the pools on different chains.
For use cases that are intrinsically not parallelizable, for example, a single AMM pool which is deployed on L1, storing its state on one Gwyneth L2 still allows sister L2s to cross-chain call for swapping. However, this might create congestion on this L2, thereby increasing fees. Therefore, Dapps are incentivized to follow a parallelizable design pattern for fee-saving. It’s also possible to deploy a smart contract to a specific L2, avoiding costly L1 deployment.
Booster functionality can make synchronous composability with L1 more efficient. Because the L1 can be accessed directly from L2, there is no need to execute smart contracts on L1. Instead, L2 blockspace is used, making L1 execution and state reads as cheap as on L2. L1 state writes can partially be optimized by only applying the final state deltas on L1, but this requires implementing an additional interface on the smart contract.
However, the update using state deltas introduces additional security risk. The security of the L1 smart contract would now depend on Gwyneth blocks being proven correctly. The original logic which causes state updates in the smart contract on L1 is now skipped.
To achieve synchronous composability with L1, we need real-time (or close to real-time) proving. L2 blocks need to be verified immediately so that the L1 can trust the work done on L2. This puts a limitation on what type of provers we can directly depend as we cannot have any safety delays.
The available prover types are:
TEEs (for example SGX, TDX, nitro enclave, AMD SEV): Proving can be done with close to native performance, hardware to generate these proofs is generally widely available, and these types of proofs are cheap to generate. The downside is that they have additional trust assumptions and have been exploited to different extents.
AVS (or multi-sigs): Also an option that provides native proving performance. It’s possible to go with a big set of decentralized validators or a small set of trusted ones, and staking ensures crypto-economic security. However, this option is not ideal on its own because the L1 is not directly verifying the state transitions.
ZKPs: Theoretically the ideal way to prove because no additional trust assumptions are added (except for the cryptography). However, the latency of the proof generation is currently too high to use for real-time use cases. ZK is still evolving rapidly and it is expected that real-time proving with ZK will be possible in the next year or two.
For now, we will use TEEs and AVS/multi-sig provers. ZK proofs can still be used to harden the security of our protocol indirectly by using them to show that other provers are faulty. More on this below.
Multi-prover
The verifier of each supported prover will be stored in the verifier registry smart contract. Gwyneth enforces a set of proofs that are required for each proposed block. This set of proofs will change on the availability of the provers (e.g. more will be added over time, or some faulty one is removed). Initially, Gwyneth will require the following proofs for each block:
A proof needs to be supplied for all supported TEEs: The security of a single TEE is not sufficient because exploits are sometimes found for a specific TEE. But many different types of TEE proofs offers very strong security because it is very unlikely that an exploit is found for all of them at the same time. TEE proof verification onchain is cheap, so verifying multiple ones does not significantly increase costs.
Some kind of AVS/multi-sig proof to have an additional level with a totally different security profile. Requiring proof types with very different security assumptions makes it much harder for a single entity to break all the provers at the same time.
Prover Cancellation Smart Contract
Additionally to this, we will have an external async path to harden Gwyneth’s security. A smart contract allows anyone to permissionlessly disable proofs in the verifier registry smart contract when it can be shown that a prover is faulty. There are two ways anybody will be able to show that a prover should be disabled:
If the same prover can be used to prove two different things, that prover should be disabled.
If a prover is shown to prove a different output than two other provers (that do agree on a single output), then that prover should be disabled.
This contract works separately from the main Gwyneth smart contracts, it only has the power to disable provers in the verifier registry. Any valid input and output pair can be used to show that a prover is faulty, the system is not limited to just being able to prove blocks that are part of the Gwyneth chain.
This smart contract will function as an onchain bug bounty. A reward will be given automatically to anyone that can show a prover is faulty. An additional reward will be given when the underlying issue that allowed the prover to be exploited is disclosed.
Because of the async nature and the fact that any inputs can be used, no real-time proving is required, and so ZK proofs can still be used in this setup. This allows us to use ZK proofs while they gradually get faster and faster and they can be added to one of the required proof types.
Bridge Quotas
While Gwyneth does not have traditional bridges (cross-chain functionality is built into the protocol), contracts can have quotas that limit how much value can be withdrawn to L1 in some time period. This limits how many funds can be stolen before some safety mechanism kicks in.
Staked Validators
Staked validators can offer economic security. If the validators sign an invalid state transition the can get slashed. The amount slashed can be used to either fully (staked amount ≥ bridge quota for exploited period) or partially help recover lost funds (in case the value is fungible somehow)
Gwyneth will support based preconfirmations. Some additional limitations will apply because of additional functionality added by Gwyneth:
If a transaction touches multiple L2 chains, the preconfer needs to be able to build a block for all chains the transaction requires.
If a transaction requires interaction with L1, then a preconfirmation can only be given if the preconfer is also the L1 proposer (directly or indirectly) for the duration of the current block.
In all other cases, preconfirmations work as they do for a normal based rollup. Users will be able to get a preconfirmation for these more tricky scenarios as more and more L1 validators opt into being a preconfer for Gwyneth.
All blocks will be published to Ethereum. Ethereum will be used for data availability, for sequencing the blocks, and to verify the correctness of the state transitions using validity proofs. The main method in which blocks will be published is by calling a smart contract function which contains the data of one or more super blocks. A super block is a block that contains transactions for any of the L2s in Gwyneth. There are two possible scenarios:
If a block doesn’t require any L1 writes, there is no requirement to immediately prove the blocks because L1 does not need the resulting state at this time. However, syncing nodes that need to trust the additional data posted for cross-chain calls cannot do so without the validity proof.
If a block does require L1 writes, all blocks up until the proposed block need to be proven for the L1 work to be done on the correct known L2 state. It is the proposer’s responsibility to provide these proofs together with the block.
The block data is part of a function call on the Gwyneth smart contract (in the form of calldata or as a blob). The L2 chain will be derived from the L1 blocks, so no other L1 work is required for advancing the chain (except for possibly proving the block at proposal time when needed, including possible L1 calls required for the sync composability with L1), which makes it very efficient in terms of L1 costs.
For proving the blocks, we run over all L1 blocks from the last proven L1 block up until a specified L1 block number. Within this range, we run over the L1 block and detect all L2 block proposals, recursively verifying each L2 block independently. This allows L2 blocks to be proven as they come in, while then being aggregated for the whole L1 block window being proven. (Note that this setup has a limitation as the L1 block in which the block is being proposed is not yet available in the EVM, which in some cases could be worked around. More on that below).
Fee Payments
Fee payments are straightforward:
The proposer collects all the transaction fees/MEV (of course, PBS-style block building may be used in practice).
The proposer has to prove all the blocks it proposed. Assuming proof generation is cheap (which is the case already for some proof types) we can allow the proposer not to prove its blocks. This is efficient because this allows more block verifications to be aggregated. Alternatively, if the offchain work required to prove blocks is still substantial, we could still force the proposer to prove all its own blocks. With preconfirmations, the preconfer is already staked, we can simply slash the proposer if it can be shown this was not done at the end of its block range.
Latest L1 State Available on L2
Up till now, we brushed over how we actually get the latest L1 state on L2. In the EVM, only the blockhash of the previous 256 blocks is available, so we can easily insert those in L2 and know the latest state. However, if we really require the latest L1 state (which we do for sync composability with L1) we need the L1 state exactly at the time the L2 block is proposed. However, the EVM does not expose any information about any changes in the current block. So we have a couple of options:
If we don’t need to write to L1, we don’t need to be able to provide a proof of the L2 blocks immediately. This allows us to re-execute the L1 block in one of the next blocks without any problem, using the L1 block hashes that are available in the EVM.
If we only need to write to L1 and don’t need any reads, there is also no problem because we don’t need access to the latest L1 state and we can use the L1 blockhash from the previous L1 block.
If we do need both L1 reads and writes:
State reads are done on L1, and the state is passed into the L2. This is of course likely to be much more expensive than doing the reads on L2, and getting access to the required state may be tricky (could be guarded by specific addresses only that can access some state, which the Gwyneth contract likely doesn’t have).
Block proposals can only happen in the first transaction of an L1 block, but this greatly limits where sync composability can happen (and top of block blockspace could be more expensive).
The EVM is extended with additional opcodes that expose information about the L1 block up till the current point. See section E below for more information.
Protocol Value Capture
There are a couple of ways the Gwyneth protocol can capture some of the value created by the network. While this may not be very important in the short term, for the long run Gwyneth needs to be sustainable.
Collect the congestion MEV through capturing the basefee: This require some amount of congestion on the different L2s, but if there is significant congestion, then it could mean that either Gwyneth or the Dapp itself is not scaling as well as possible. If there is no congestion, the protocol could also enforce a small basefee to still capture some value (though in this case, it would be a tax).
Selling the proposer rights: We can potentially sell Gwyneth block proposal slots similar to execution tickets, either the rights of sequencing all Gwyneth L2s in a single L1 block, or individual proposing rights per each Gwyneth L2.
Dapp Value Capture
We have to be careful that any value capturing does not impact composability and (re)introduces fragmentation. Ideally, any builder always has the right, or can purchase the right, to build blocks for any of the L2s. We believe that Dapp value capturing should be done on the Dapp level, while sequencing for these Dapps should remain permissionless. Self-sequencing Dapp on Gwyneth will worsen censorship resistance and composability.
There are two desirable properties for the necessary infrastructure (block explorers, RPC node, block builders, etc.) around the core protocol:
The network is eventually self-sustaining, meaning the value created by the network is able to somehow pay for these services.
Users can depend on a uniform experience. The user experience around the chain should also not be fragmented.
It is possible that, eventually, the ecosystem around Gwyneth is able to keep this going organically. But capturing value in the protocol as well to then use to help make this happen still seems important to get things started. If more value is captured than needed it can be redistributed to the users.
TBD
To prove something within an L1 block, we need to do introspection of the current L1 state within a block. Currently, the EVM exposes the last 256 blockhashes where this is possible, however, it does not expose anything about the state in the current L1 block. Of course, we could directly read state on L1 directly, but that is both inefficient and quite impractical. Below we explain one way how the EVM could be extended to enable this in a minimal way.
We can expose the current tx trie to the EVM. While transactions are executed in the block, the tx trie is updated incrementally (instead of created only once at the end for all transactions at once for the block header) and this root is exposed to the EVM. This way we can query all the transactions that came before (including the current tx), so it's possible to calculate the latest L1 state before the current tx in a proof.
Then to make it completely accurate, the EVM could additionally expose the current opcode counter (how many opcodes that have already been executed in the current tx) to know exactly where the stop the execution of the current tx.
This approach requires minimal changes to the EVM, and also no expensive operations in the node, which should make these opcodes very cheap.
There are also other ways this could be achieved as this approach is also not ideal. Any comments/ideas are greatly appreciated!
You can follow Gwyneth for updates on X @gwyneth_taiko and progress on GitHub.