This guide demonstrates how to generate, send, fetch, and verify a Blob Transaction when integrating rollup data into the IoTeX blockchain using EIP-4844 Blob Transactions. These steps ensure scalability and data integrity for rollup-based systems.
Overview
EIP-4844 introduces a new transaction type called Blob Transactions to improve scalability by enabling the temporary storage of large amounts of data outside of a blockchain's execution layer.
Here are key points about the Blob transaction:
Blob transactions contain large amounts of data that are not directly stored in the Blockchain's execution layer (i.e., they are not part of the state or permanent history).
This data is ephemeral and is retained only for a short period (e.g., 20 days) to facilitate rollup proofs.
It is designed to significantly reduce the cost of rollup solutions (Layer 2 scaling technologies) by allowing them to postoff-chain data (proofs, commitments) to a Blockchain more efficiently.
This off-chain data is essential for rollups to verify transactions but doesn't need to persist in the Blockchain's state.
Gas-efficient: Lower costs for rollups to post data compared to traditional calldata.
EIP-4844 reduces costs for Layer 2 solutions, setting the stage for Ethereum’s future scalability improvements, such as danksharding.
Blob Data Structure
The blob data structure in EIP-4844 is as follows:
// BlobTxSidecar contains the blobs of a blob transaction.
type BlobTxSidecar struct {
Blobs []kzg4844.Blob // Blob is 128kB bytes array
Commitments []kzg4844.Commitment // Commitment is 48-bytes
Proofs []kzg4844.Proof // Proof is 48-bytes
}
A Blob represents 128kB of raw data, while a Commitment is a 48-byte cryptographic hash generated by applying a cryptographic commitment scheme (for example, KZG commitment) to the blob data. This provides a compact and efficient method for verifying the integrity of the blob data. The Proof, which is also 48 bytes, is generated by the commitment scheme and allows validators to verify that the blob data corresponds to the on-chain commitment without needing access to the full blob.
How to Generate a Blob Transaction
Blob data contains large amounts of off-chain data, such as rollup proofs from an L2 chain, which are required for rollup verification. In the context of IoTeX, blob data can also include device data generated by IoTeX’s W3bstream devices. This capability enables a wide range of IoT use cases, where data such as sensor readings or event logs can be efficiently bundled and posted to the blockchain.
To create blob data and associated transactions, use the Ethereum SDK or a similar toolkit. See the code example below:
package main
import (
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"crypto/ecdsa"
"log"
)
func createBlobTx() *types.BlobTx {
// Example private key (replace with your own key securely)
privateKey := "YOUR_PRIVATE_KEY"
key, err := crypto.HexToECDSA(privateKey)
if err != nil {
log.Fatalf("Failed to load private key: %v", err)
}
// Prepare blob data
blobData := []byte("Rollup proof or other off-chain data")
// or it could be W3bstream device data
deviceData := []byte("IoT device data from W3bstream")
// generate commitment and proof
commitment := kzg4844.BlobToCommitment(blobData)
proof := kzg4844.ComputeBlobProof(blobData, commitment)
// Create blob data
blob := BlobTxSidecar {
Blobs []kzg4844.Blob{blobData}
Commitments []kzg4844.Commitment{commitment}
Proofs []kzg4844.Proof{proof}
}
return &types.BlobTx {
ChainID: 4689, // chain ID of the IoTeX blockchain
Nonce: nonce,
GasTipCap: tipcap,
GasFeeCap: maxFee,
Gas: gas,
To: to,
Data: calldata,
BlobFeeCap: blobFeeCap,
BlobHashes: blob.BlobHashes(),
Sidecar: &blob,
}
}
How to Send the Blob Transaction to IoTeX Blockchain
The IoTeX blockchain is fully compatible with Ethereum, so it can accept Blob Transactions using its RPC interface. See the code example below to send a Blob Transaction to the IoTeX blockchain.
package main
import (
"log"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
// Connect to IoTeX PRC endpoint
client, err := ethclient.Dial("https://babel-mainnet.api.iotex.io")
if err != nil {
log.Fatalf("Failed to connect to IoTeX: %v", err)
}
// Example private key (replace with your own key securely)
privateKey := "YOUR_PRIVATE_KEY"
key, err := crypto.HexToECDSA(privateKey)
if err != nil {
log.Fatalf("Failed to load private key: %v", err)
}
// Sign the blob transaction
tx := types.NewTx(createBlobTx())
signedTx, err := types.SignTx(tx, types.NewCancunSigner(4689), key)
if err != nil {
log.Fatalf("Failed to sign blob transaction: %v", err)
}
// Send the transaction
err := cli.SendTransaction(context.Background(), signedTx)
if err != nil {
log.Fatalf("Failed to send transaction: %v", err)
}
log.Printf("Transaction Sent: %s", signedTx.Hash().String())
}
Once the Blob Transaction has been successfully sent to the IoTeX blockchain, it will be mined into a block and retained in ephemeral storage for a short period (approximately 20 days).
How to Fetch a Given Blob Transaction from IoTeX Blockchain
You can access the IoTeX RPC endpoint to retrieve any transaction (including a Blob Transaction) by its hash. See the code example below:
package main
import (
"context"
"github.com/ethereum/go-ethereum/ethclient"
"log"
)
func main() {
// Connect to IoTeX PRC endpoint
client, err := ethclient.Dial("https://babel-mainnet.api.iotex.io")
if err != nil {
log.Fatalf("Failed to connect to IoTeX: %v", err)
}
// Query the transaction by hash
transactionHash := "TRANSACTION_HASH"
tx, pending, err := cli.TransactionByHash(transactionHash)
if err != nil {
log.Fatalf("Failed to fetch transaction: %v", err)
}
log.Printf("Fetched Transaction: %v", tx)
}
How to Verify the Received Blob Transaction
Once the Blob Transaction has been retrieved, you can verify it by checking the commitment against the raw blob data. The verification process consists of two steps:
Verify that the lengths of hashes, blobs, commitments, and proofs match, and that the blob hashes match the hashes in the transaction.
Verify the blob data against the commitments and proofs.
See the code example below:
package main
import (
"github.com/ethereum/go-ethereum/core/types"
"log"
)
func ValidateBlobTransaction() {
// tx is received Blob transaction
sidecar := tx.BlobTxSidecar()
if sidecar == nil {
return fmt.Errorf("missing sidecar in blob transaction")
}
// Ensure the number of items in the blob transaction and various side
// data match up before doing any expensive validations
hashes := tx.BlobHashes()
if len(hashes) == 0 {
return fmt.Errorf("blobless blob transaction")
}
if len(hashes) > params.MaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob {
return fmt.Errorf("too many blobs in transaction: have %d, permitted %d", len(hashes), params.MaxBlobGasPerBlock/params.BlobTxBlobGasPerBlob)
}
if len(sidecar.Blobs) != len(hashes) {
return fmt.Errorf("invalid number of %d blobs compared to %d blob hashes", len(sidecar.Blobs), len(hashes))
}
if len(sidecar.Commitments) != len(hashes) {
return fmt.Errorf("invalid number of %d blob commitments compared to %d blob hashes", len(sidecar.Commitments), len(hashes))
}
if len(sidecar.Proofs) != len(hashes) {
return fmt.Errorf("invalid number of %d blob proofs compared to %d blob hashes", len(sidecar.Proofs), len(hashes))
}
// Blob quantities match up, validate that the provers match with the
// transaction hash before getting to the cryptography
hasher := sha256.New()
for i, vhash := range hashes {
computed := kzg4844.CalcBlobHashV1(hasher, &sidecar.Commitments[i])
if vhash != computed {
return fmt.Errorf("blob %d: computed hash %#x mismatches transaction one %#x", i, computed, vhash)
}
}
// Blob commitments match with the hashes in the transaction, verify the
// blobs themselves via KZG
for i := range sidecar.Blobs {
if err := kzg4844.VerifyBlobProof(sidecar.Blobs[i], sidecar.Commitments[i], sidecar.Proofs[i]); err != nil {
return fmt.Errorf("invalid blob %d: %v", i, err)
}
}
return nil
}