Danksharding is coming to Ethereum Mainnet in less than a month, giving rollup developers more block space at a lower cost. However, even with this progress, all demands won't be fully met. This has led different projects to roll out their own data availability solutions or DA in short. Celestia, Near, and others are building their own solutions but my current favorite is EigenDA.
Why EigenDA?
I believe EigenDA offers the best economic guarantees and the best compatibility with dApps on Ethereum and L2s since it is native to the Ethereum Mainnet and uses Ether as the base economic security. Lately, my focus has been on developing dApps on L3s without economic incentives for attacks, such as social web3 applications or video games. I think EigenDA is a good solution for this even if it doesn't offer the same security as Ethereum. This has led me to research deeper into EigenDA technology.
This article is my current progress on my research on how to use EigenDA, where we'll learn how to interact with blobs. How to store them and how rollups can use them to scale.
Setup your environment
Install grpcurl
EigenDA suggests using grpcurl
to disperse and retrieve blobs.
MacOS
brew install grpcurl
Linux
In order to install it we will need the Go compiler, install it if you haven't.
https://go.dev/dl/go1.22.0.linux-amd64.tar.gz
rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.22.0.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
Now install grpcurl
.
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
grpcurl
is now installed at $HOME/go/bin/grpcurl
.
Install foundry
Install the forge command if you haven't.
curl -L https://foundry.paradigm.xyz | bash
foundryup
Clone the repo
Now clone the EigenDA repo and cd into it.
git clone https://github.com/Layr-Labs/eigenda.git
cd eigenda
1. Disperse a blob
Let's start by putting some data on EigenDA, this process is called "Dispersing a Blob". If you're building a rollup you might want to store your transaction data history to allow users to permissionlessly retrieve any asset, funds or any information from L2 to L1. To achieve this we need to store data in the form of a merkle tree root, one merkle root at the time every now and then.
We're keeping it simple for now, in the following command we store "Hello Eigen DA!", feel free to put whatever data you want.
$HOME/go/bin/grpcurl -import-path ./api/proto -proto ./api/proto/disperser/disperser.proto -d '{"data": "Hello Eigen DA!", "security_params": [{"quorum_id": 0, "adversary_threshold": 25, "quorum_threshold": 50}]}' disperser-goerli.eigenda.xyz:443 disperser.Disperser/DisperseBlob
This will return a json that includes a requestId
value, save it somewhere. We will need it in the following steps.
2. Query the blob status
Wait about a minute and call the following.
$HOME/go/bin/grpcurl -import-path ./api/proto -proto ./api/proto/disperser/disperser.proto -d '{"request_id": "YOUR REQUEST ID"}' disperser-goerli.eigenda.xyz:443 disperser.Disperser/GetBlobStatus
This will return a json with your blob status. Save it somewhere, we'll need it in a moment.
Optional: Query the data you stored directly by running the following command and passing the info.blobVerificationProof.batchHeaderHash
returned on out blob status json.
$HOME/go/bin/grpcurl -import-path ./api/proto -proto ./api/proto/disperser/disperser.proto -d '{"batch_header_hash": "YOUR BLOB HEADER HASH", "blob_index":"0"}' disperser-goerli.eigenda.xyz:443 disperser.Disperser/RetrieveBlob
3. Verify the blob on-chain
Now, create a contracts/scripts/BlobVerification.sol
file and put all your blob status data on the contract as described below. The blob status json uses base64 encoding, so I recommend you using this decoder online, and this hex to dec converter for the X and Y commitment.
contracts/scripts/BlobVerification.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import "forge-std/Script.sol";
import "../src/rollup/MockRollup.sol";
import {EigenDARollupUtils} from "../src/libraries/EigenDARollupUtils.sol";
contract MockRollupDeployer is Script {
function run() external {
vm.startBroadcast();
IEigenDAServiceManager.QuorumBlobParam[]
memory quorumBlobParams = new IEigenDAServiceManager.QuorumBlobParam[](1);
quorumBlobParams[0] = IEigenDAServiceManager.QuorumBlobParam(
/*quorumNumber*/0,
/*adversaryThresholdPercentage*/25,
/*quorumThresholdPercentage*/50,
/*chunkLength*/1);
IEigenDAServiceManager.BlobHeader memory blobHeader = IEigenDAServiceManager.BlobHeader(
BN254.G1Point(/*X*/4317633011943442688312675280968246407649523414935450151451943114581321374794,
/*Y*/19103178923486544823523596462585377039936043999112285141018622651122914686888),
/*dataLength*/ 1,
quorumBlobParams
);
address eigenDAServiceManager = 0xa3b1689Ab85409B15e07d2ED50A6EA9905074Ee5;
//0x1eEa1C6b573f192F33BE6AA56dC9080314a89491;
IEigenDAServiceManager.BatchHeader memory batchHeader
= IEigenDAServiceManager.BatchHeader(
/*bytes32 blobHeadersRoot*/ 0x1d1af6d981553efb8d0a7b6d6022b56d1fbf2a615d1dd5a034e6870e87159a68,
/*bytes quorumNumbers*/ hex"00",
/*bytes quorumThresholdPercentages*/ hex"5f",
/*uint32 referenceBlockNumber*/ 10544012
);
IEigenDAServiceManager.BatchMetadata memory batchMetadata
= IEigenDAServiceManager.BatchMetadata(
batchHeader,
/*bytes32 signatoryRecordHash*/ 0x3bb6b9f58f262b89fb02e339a045438b4765f6f09ca136d045556b3869e07b70,
/*uint96 fee*/0,
/*uint32 confirmationBlockNumber*/10544016
);
EigenDARollupUtils.BlobVerificationProof memory blobVerificationProof
= EigenDARollupUtils.BlobVerificationProof(
/*uint32 batchId*/ 12580,
/*uint8 blobIndex*/ 9,
batchMetadata,
/*bytes inclusionProof*/ hex"082915e241f357b6c2f43fac8767a909286b7f9e392c04a40caf9be28486e561b2127431770921e8f3461e1a4d845e2991e803d1c6591655873bf1cc03702cb5626b47f9b14bb86bbfaf11ad723b5bdaa3a25fc33dda9f768e58c1df463d9e0d279e7a791b0bdcbe8f32610c0ff7cfb99fd15ad0aa9091d2d02ab520a34536793829e2605b7aa684f488b51ffff369f49e23fc075b1d0413fca94f9bb670b92a024b2b6efbf73d9f0522191941d18c15ea5713fb87c679cd7abf2fdec390b5fd",
/*bytes quorumThresholdIndexes*/ hex"00"
);
EigenDARollupUtils.verifyBlob(blobHeader, IEigenDAServiceManager(eigenDAServiceManager), blobVerificationProof);
vm.stopBroadcast();
}
}
4. Test it
Currently, EigenDA is deployed on Goerli. Let's fork it and run an RPC locally.
anvil --fork-url https://goerli.gateway.tenderly.co
#https://rpc.tornadoeth.cash/goerli
If all data is correct, the following command should not revert.
cd contracts
forge script script/BlobVerification.sol --rpc-url http://127.0.0.1:8545
What's next?
Now that we know how to call the verifyBlob()
function from EigenDA, we should be able to verify rollup data stored on a format similar to the following draft.
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.20;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
struct G1Point {
uint256 X;
uint256 Y;
}
contract MyNaiveOptimisticCrentralizedRollupDA is Ownable {
mapping(uint timestamp => bytes32 data) public dataHistory;
mapping(uint timestamp => G1Point commitment) public commitmentsHistory;
function postData(uint timestamp, bytes32 data) public onlyOwner {
dataHistory[timestamp] = data;
}
function postCommitment(uint timestamp, G1Point memory commitment) public onlyOwner {
commitmentsHistory[timestamp] = commitment;
}
}
Notice that we need a trusted actor to post the data on-chain. This is part of the role of what is commonly known as "The Sequencer" who need to do the following:
- Gather all the transaction data from users on L2
- Disperse it to EigenDA
- Post both the commitment and data on L1 and verify it using EigenDA's
verifyBlob
function
Take a look at this official example from EigenDA that shows a very naive implementation of a Rollup that posts blobs optimistically and then challenge them.
Also, take a look at the official documentation.
I think there is a lot more to come about blobs in terms of tools and use cases. I'll be covering it here. Stay tuned!
Thanks for reading this guide!
Follow Filosofía Código on dev.to and in Youtube for everything related to Blockchain development.