Layer 2 Scaling: Rollups, Sidechains and State Channels
In this tutorial, you'll learn Ethereum Layer 2 scaling solutions including optimistic rollups, zk-rollups, sidechains, and state channels that enable faster and cheaper transactions. Why it matters: Ethereum mainnet processes only 15-30 transactions per second, but L2 solutions scale to thousands of TPS while inheriting Ethereum's security, making decentralized applications viable for mass adoption. By the end, you'll understand how each L2 solution works.
Layer 2 (L2) refers to protocols built on top of Ethereum (Layer 1) that process transactions off-chain while using L1 for security and final settlement, dramatically increasing throughput and reducing costs.
Understanding the Scaling Problem
Ethereum's base layer is intentionally slow to maintain decentralization and security. L2 solutions trade different properties for throughput.
| Solution | TPS | Finality | Security Model | Trust Assumption |
|---|---|---|---|---|
| Ethereum L1 | 15-30 | ~15 min | Full consensus | None |
| Optimistic Rollup | 2,000-4,000 | 7 days | Fraud proofs | Honest validator |
| zk-Rollup | 5,000-20,000 | Minutes | Validity proofs | Cryptography |
| Sidechain | 1,000-10,000 | Seconds | Separate consensus | Validator set |
| State Channels | 1,000,000+ | Instant | Multi-sig | Counterparty |
flowchart TD
subgraph "Layer 1 - Ethereum"
L1[Ethereum Mainnet
15 TPS, 12s blocks]
end
subgraph "Layer 2 - Scaling Solutions"
OR[Optimistic Rollup
Arbitrum, Optimism]
ZR[zk-Rollup
zkSync, StarkNet]
SC[Sidechain
Polygon, Gnosis]
CH[State Channels
Raiden, Connext]
end
subgraph "Execution"
OR -->|Batch txs, post to L1| L1
ZR -->|Post validity proofs| L1
SC -->|Periodic checkpoints| L1
CH -->|Final settlement| L1
end
subgraph "Benefits"
OR --> D1[Low fees, EVM compatible]
ZR --> D2[Lowest fees, fast finality]
SC --> D3[Fast, standalone security]
CH --> D4[Instant, unlimited TPS]
end
Optimistic Rollups
Optimistic rollups assume transactions are valid by default and only run computation when challenged.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
// Simplified optimistic rollup bridge contract
contract OptimisticBridge {
struct Transaction {
address sender;
address recipient;
uint256 amount;
uint256 nonce;
}
struct Batch {
bytes32 root;
uint256 timestamp;
bool finalized;
}
mapping(uint256 => Batch) public batches;
uint256 public challengePeriod = 7 days;
uint256 public batchCount;
event BatchSubmitted(uint256 indexed batchId, bytes32 root);
event Challenge(uint256 indexed batchId, uint256 indexed txIndex);
function submitBatch(bytes32 _root) external {
batchCount++;
batches[batchCount] = Batch(_root, block.timestamp, false);
emit BatchSubmitted(batchCount, _root);
}
function finalizeBatch(uint256 _batchId) external {
Batch storage batch = batches[_batchId];
require(block.timestamp >= batch.timestamp + challengePeriod, "Challenge period active");
require(!batch.finalized, "Already finalized");
batch.finalized = true;
}
function challengeTransaction(uint256 _batchId, Transaction calldata _tx, bytes[] calldata _proof) external {
// Verify Merkle proof and check if transaction is invalid
// If valid challenge, reward challenger and revert batch
}
}
Expected behavior: Operators submit transaction batches to L1. Anyone can challenge fraudulent transactions during a 7-day window. After the challenge period, the batch is finalized.
zk-Rollups
Zero-knowledge rollups generate cryptographic proofs that verify all transactions in a batch are valid.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
// Simplified zk-rollup verifier interface
interface IVerifier {
function verifyProof(
uint256[2] memory a,
uint256[2][2] memory b,
uint256[2] memory c,
uint256[] memory input
) external view returns (bool);
}
contract ZKRollup {
IVerifier public verifier;
mapping(uint256 => bytes32) public stateRoots;
uint256 public stateId;
event StateUpdated(uint256 indexed stateId, bytes32 newRoot);
constructor(address _verifier) {
verifier = IVerifier(_verifier);
}
function updateState(
bytes32 _newRoot,
uint256[2] memory a,
uint256[2][2] memory b,
uint256[2] memory c
) external {
uint256[] memory input = new uint256[](1);
input[0] = uint256(_newRoot);
require(
verifier.verifyProof(a, b, c, input),
"Invalid proof"
);
stateId++;
stateRoots[stateId] = _newRoot;
emit StateUpdated(stateId, _newRoot);
}
}
Expected behavior: Operators generate a zk-SNARK proof that the new state root is valid given the previous state and pending transactions. The proof is verified on-chain in constant time, regardless of batch size.
flowchart LR A[Users send
transactions] --> B[Sequencer] B --> C[Off-chain execution
& state update] C --> D[Generate
validity proof] D --> E[Submit proof +
state root to L1] E --> F[L1 verifies proof
in milliseconds] F --> G[State finalized
with L1 security] B --> H[Transaction data
posted to L1] H --> I[Data availability]
Sidechains
Sidechains are independent blockchains with their own consensus, bridged to Ethereum.
const { ethers } = require("ethers");
// Simulating a Polygon sidechain bridge interaction
async function bridgeToSidechain() {
const mainnetProvider = new ethers.JsonRpcProvider("https://mainnet.infura.io/v3/YOUR_KEY");
const polygonProvider = new ethers.JsonRpcProvider("https://polygon-rpc.com");
// Lock tokens on mainnet
const tokenAddress = "0x...";
const bridgeAddress = "0x...";
const token = new ethers.Contract(tokenAddress, [
"function approve(address spender, uint256 amount) returns (bool)",
"function balanceOf(address) view returns (uint256)]
], mainnetProvider);
const bridge = new ethers.Contract(bridgeAddress, [
"function deposit(uint256 amount) external",
"function withdraw(uint256 amount) external",
"function getBalance(address) view returns (uint256)]
], mainnetProvider);
// Check balance
const balance = await token.balanceOf(process.env.USER_ADDRESS);
console.log("Mainnet balance:", ethers.formatEther(balance));
// Bridge takes ~20-30 minutes for Polygon
console.log("Bridging to Polygon...");
console.log("Expected time: 20-30 minutes");
console.log("Cost: ~0.01 ETH on mainnet + minimal on Polygon");
// Once bridged, interact on Polygon
const maticBalance = await polygonProvider.getBalance(process.env.USER_ADDRESS);
console.log("Polygon MATIC balance:", ethers.formatEther(maticBalance));
}
// Expected output:
// Mainnet balance: 10.0
// Bridging to Polygon...
// Expected time: 20-30 minutes
// Cost: ~0.01 ETH on mainnet + minimal on Polygon
// Polygon MATIC balance: 100.0
State Channels
State channels allow participants to transact off-chain instantly, settling only the final state on-chain.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract SimpleStateChannel {
struct Channel {
address alice;
address bob;
uint256 aliceBalance;
uint256 bobBalance;
uint256 nonce;
uint256 timeout;
bool closed;
}
mapping(bytes32 => Channel) public channels;
event ChannelOpened(bytes32 indexed channelId, address alice, address bob);
event ChannelClosed(bytes32 indexed channelId, uint256 aliceBalance, uint256 bobBalance);
function openChannel(address _counterparty) external payable returns (bytes32) {
require(msg.value > 0, "Must deposit ETH");
bytes32 channelId = keccak256(abi.encodePacked(msg.sender, _counterparty, block.timestamp));
channels[channelId] = Channel({
alice: msg.sender,
bob: _counterparty,
aliceBalance: msg.value,
bobBalance: 0,
nonce: 0,
timeout: block.timestamp + 1 days,
closed: false
});
emit ChannelOpened(channelId, msg.sender, _counterparty);
return channelId;
}
function closeChannel(bytes32 _channelId, uint256 _aliceBalance, uint256 _bobBalance, bytes memory _signature) external {
Channel storage channel = channels[_channelId];
require(!channel.closed, "Already closed");
bytes32 message = keccak256(abi.encodePacked(_channelId, _aliceBalance, _bobBalance, channel.nonce));
// Verify both parties signed
address signer = recoverSigner(message, _signature);
require(signer == channel.alice || signer == channel.bob, "Invalid signature");
channel.closed = true;
payable(channel.alice).transfer(_aliceBalance);
payable(channel.bob).transfer(_bobBalance);
emit ChannelClosed(_channelId, _aliceBalance, _bobBalance);
}
}
Expected behavior: Two parties deposit ETH into a channel. They exchange signed state updates off-chain instantly with no gas. When done, the final state is submitted to L1, distributing funds.
Comparing L2 Solutions
| Factor | Optimistic Rollup | zk-Rollup | Sidechain | State Channel |
|---|---|---|---|---|
| Withdrawal delay | 7 days | Minutes | Minutes | Instant |
| EVM compatible | Yes | Limited | Yes | No |
| Gas cost | $0.01-0.10 | $0.001-0.01 | $0.001-0.01 | ~$0 |
| Security | L1 (fraud proofs) | L1 (math proofs) | Separate | Multi-sig |
| Best for | General dApps | Payments, DEX | Gaming | Micro-payments |
Common Errors and Misunderstandings
1. Confusing Sidechains with L2 Rollups
Sidechains have their own security model independent of Ethereum. Rollups inherit Ethereum's security. A sidechain validator collusion can steal funds. Rollups cannot.
2. Forgetting 7-Day Withdrawal Delay
Optimistic rollups require a 7-day challenge period for withdrawals. Users bridging back to L1 must wait. Liquidity providers on L2 offer faster exits for a fee.
3. Assuming All L2s Are EVM Compatible
zk-Rollups like zkSync Era and StarkNet have limited EVM compatibility. Optimistic rollups like Arbitrum and Optimism have near-perfect EVM compatibility.
4. Underestimating Data Availability Costs
Rollups must post transaction data to L1. Calldata is expensive. EIP-4844 (proto-danksharding) will significantly reduce these costs.
5. Security Trade-offs in Bridges
Bridges between L2s or between L1 and sidechains are the most attacked components. Bridge hacks account for over $2 billion in losses.
Practice Questions
What is the main difference between optimistic and zk-rollups? Optimistic rollups assume validity (with fraud proofs) and require a challenge period. zk-rollups provide cryptographic validity proofs, enabling instant finality without waiting periods.
Why do optimistic rollups have a 7-day withdrawal delay? To allow time for validators to challenge potentially fraudulent transactions. If a fraud proof is submitted within 7 days, the batch can be reverted.
How does a sidechain differ from a rollup? A sidechain is an independent blockchain with its own validators and consensus mechanism. A rollup posts data to L1 and inherits L1 security. Sidechains have weaker security guarantees.
What is the maximum TPS of state channels? Theoretically unlimited since transactions happen off-chain without global consensus. Practical limits are network latency and channel participant bandwidth.
What EIP improves rollup cost efficiency? EIP-4844 (proto-danksharding) introduces blob-carrying transactions, providing cheaper data availability for rollups by separating L1 execution from data storage.
Challenge
Build a simple optimistic rollup implementation in Solidity with batch submission, Merkle tree state management, fraud proof challenge mechanism, and a 7-day withdrawal delay. Write tests that prove fraudulent transactions are caught within the challenge period.
Real-World Task
Create a dApp that deploys the same ERC-20 token to Ethereum Sepolia, Arbitrum Sepolia, and Polygon Mumbai, then implements a bridge UI using React and ethers.js that allows users to deposit tokens on L1 and receive wrapped tokens on L2 using official bridge contracts from each platform.
Frequently Asked Questions
Next Steps
After understanding L2 scaling, explore IPFS for decentralized storage, then dive into oracle solutions for connecting your L2 dApp to real-world data.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro