KIP 279: BlobTx for Kaia
| Author | Ollie, Nasu, Shogo |
|---|---|
| Discussions-To | https://devforum.kaia.io/t/discussion-on-kip-279/8897 |
| Status | Draft |
| Type | Core |
| Created | 2025-11-24 |
Abstract
Introduce the Blob transaction type that represents the existence of data blobs, corresponding KZG commitments and proofs. This KIP follows the latest Ethereum Fusaka specs and will be included in Kaia’s Osaka Hardfork. Therefore, EIP-4844 is the baseline, but also includes the EIP-7516 BLOBBASEFEE opcode, EIP-7594 cell proof format, and EIP-7918 blob base fee calculation. The blob per block limit was lowered to fit in Kaia’s short 1-second block time. Since the limit is low, the target gas mechanism was removed for simplicity.
Motivation
Kaia wants to accommodate rollups for scalability and specialized features in L2 chains. Rollups typically need to upload their summary data onto L1 which incurs a high data load. For instance, if an L2 submits a 128 KB blob every 10 seconds, the chain receives 33 GB a month and pays 33,000 KAIA. With this KIP, the L2 can post its data through EIP-4844 compatible blob transactions to provide cost-effective and scalable method. As the blockchain can be free from the burden of persisting the calldata, the chain can offer a lower pricing.
Specification
Parameter
Type parameters
| Parameter | Value | Note |
|---|---|---|
BLOB_TX_TYPE |
0x03 |
For Ethereum compatible RLP |
TxTypeEthereumBlob |
0x7803 |
For Kaia consensus RLP |
VERSIONED_HASH_VERSION_KZG |
0x01 |
Prefixed to BlobVersionedHash |
BlobSidecarVersionV1 |
0x01 |
The sidecar_version of BlobTxWithBlobsV1 |
Blob size parameters
| Parameter | Value | Note |
|---|---|---|
BYTES_PER_FIELD_ELEMENT |
32 | (byte/elem) |
FIELD_ELEMENTS_PER_BLOB |
4096 | (elem/blob) |
BYTES_PER_BLOB |
BYTES_PER_FIELD_ELEMENT * FIELD_ELEMENTS_PER_BLOB = 131072 |
128 KB |
GAS_PER_BLOB |
131072 | (blobgas/blob). 1 blobgas/byte |
MAX_BLOB_GAS_PER_BLOCK |
131072 | max 1 blob/block |
BLOB_BASE_FEE_MULTIPLIER |
8 | baseFeePerBlobGas = 8*baseFeePerGas |
BLOB_SIDECARS_RETENTION |
1814400 | 21 days in blocks |
Cell proof parameters
| Parameter | Value | Note |
|---|---|---|
FIELD_ELEMENTS_PER_EXT_BLOB |
2 * FIELD_ELEMENTS_PER_BLOB = 8192 |
(elem/extblob) |
FIELD_ELEMENTS_PER_CELL |
64 | (elem/cell) |
BYTES_PER_CELL |
FIELD_ELEMENTS_PER_CELL * BYTES_PER_FIELD_ELEMENT = 2048 |
(byte/cell) |
CELLS_PER_EXT_BLOB |
FIELD_ELEMENTS_PER_EXT_BLOB // FIELD_ELEMENTS_PER_CELL = 128 |
(cell/extblob) |
Data types
| Type | Definition |
|---|---|
kzg4844.Blob |
[BYTES_PER_BLOB]byte = [131072]byte |
kzg4844.Commitment |
[48]byte |
kzg4844.Proof |
[48]byte |
BlobVersionedHash |
[32]byte = 0x01 + hash(commitment)[1:] |

Blob transaction
The transaction fields are identical to the EIP-4844 definition.
TransactionPayloadBody = [chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data, access_list, max_fee_per_blob_gas, blob_versioned_hashes, y_parity, r, s]
All fields follow the same semantics as EIP-4844. Note that the to field cannot be nil and always be an address.
Block header
The block header is extended with two new fields blobGasUsed and excessBlobGas.
class Header:
parentHash: hash
rewardbase: address
root: hash
txHash: hash
receiptHash: hash
bloom: bloom
blockScore: bigint
number: bigint
gasUsed: uint64
time: bigint
timeFoS: uint8
extra: bytes
governance: bytes
vote: bytes
baseFee: int # since Magma, as per KIP-71
randomReveal: bytes # since Randao, as per KIP-114
mixHash: bytes # since Randao, as per KIP-114
blobGasUsed: uint64 # since Osaka, as per KIP-279
excessBlobGas: uint64 # since Osaka, as per KIP-279
Networking
There are multiple network representations. BlobTx and BlobSidecar travel through the network of nodes in various forms.
BlobTxRLP = 0x7803 || rlp(TransactionPayloadBody)
EthBlobTxRLP = 0x03 || rlp(TransactionPayloadBody)
BlobTxSidecar = rlp([sidecar_version, blobs, commitments, cellProofs])
BlobTxWithBlobs = rlp([TransactionPayloadBody, sidecar_version, blobs, commitments, proofs]
The Kaia chain internally processes the RLP encoding prefixed with TxTypeEthereumBlob (0x7803). But for Ethereum compatibility, a Kaia node MAY return an RLP encoding prefixed with BLOB_TX_TYPE (0x03).
Sidecar format
- Both BlobTxSidecar and BlobTxWithBlobs MUST include a
sidecar_versionfield before the blobs field. The version field is also referred to aswrapper_version. - The sidecar version MUST be
BlobSidecarVersionV1 (0x01)as per EIP-7594. - There MUST be
CELLS_PER_EXT_BLOB (128)proofs (i.e. cell proofs) for each blob as per EIP-7594.
Propagate BlobTxWithBlobs
- An RPC endpoint node, MAY accept a
BlobTxWithBlobsvia itseth_sendRawTransactionandkaia_sendRawTransactionJSON-RPC API. - A node SHOULD accept a
BlobTxWithBlobsvia its p2pTxMsg. - When a node receive a
BlobTxWithBlobsvia RPC or p2p, it MUST be validated, then propagated to its peers like any other transactions in the txpool.
Consensus
- When a block proposer decides to include a
BlobTx, the proposer MUST assure that the originatingBlobTxWithBlobsis verified. - Block proposals SHOULD include the
BlobTxWithBlobsso validators can verify it. - Validators MUST assure that the
BlobTxWithBlobsis verified before committing to the block. Note that verifying aBlobTxWithBlobsdoes not require block execution. - As a result, whenever you see a
BlobTxin a finalized block, you are sure that there exists a correspondingBlobTxSidecarbecause you trust the CNs’ consensus.
Persist BlobTxSidecar
- When a node receive a finalized block with
BlobTxinside, the node SHOULD persist the correspondingBlobTxSidecar.- The
BlobTxSidecarcan come from theBlobTxWithBlobsin the node’s txpool memory. - The
BlobTxSidecarcan come from the peer over p2p.
- The
- Nodes MAY request peers for
BlobTxSidecarsinBlobSidecarsRequestMsg = 0x15p2p message. In turn, nodes SHOULD respond peers withBlobTxSidecarsinBlobSidecarsMsg = 0x16p2p message, if available. - Nodes SHOULD NOT request peers for
BlobTxSidecarswhose block number is older thanBLOB_SIDECARS_RETENTIONbefore the known head block. - Nodes MAY delete the
BlobTxSidecarwhose block number is older thanBLOB_SIDECARS_RETENTIONbefore the head block.

BlobTxWithBlobs validation
Upon receiving a BlobTxWithBlobs via RPC or p2p, it is verified as follows.
- The
BlobTxWithBlobsformat complies with EIP-7594.tx, sidecar_version, blobs, commitments, proofs = blobTxWithBlobs assert sidecar_version == 1 # BlobSidecarVersionV1 assert len(tx.blobVersionedHashes) == len(blobs) == len(commitments) # 1 commitment per blob assert len(blobs) * CELLS_PER_EXT_BLOB == len(proofs) # 128 proofs per blob - The
BlobTxWithBlobscontains the correct proofsfor i in range(len(tx.blobVersionedHashes)): assert CalcBlobHashV1(commitments[i]) == tx.blobVersionedHashes[i] assert VerifyCellProofs(blobs, commitments, cellProofs)
A BlobTxWithBlobs verification happens in these situations:
- Receive via
eth_sendRawTransactionRPC - Receive via
TxMsgp2p message - Receive via
IstanbulMsgp2p message’s Proposal payload. Note that thetype Proposal interfaceis instantiated as*types.Blockwhich includes the transactions inside. Since aBlobTxWithBlobsis treated as a transaction, the Proposal should naturally include Sidecars.
Block validation
A valid block must satisfy the following conditions. Note that the authenticity of the tx.blobVersionedHashes is not verified here because we assume that consensus nodes validated them against BlobTxSidecar.
Rules below are similar to the EIP-4844 ‘Execution layer validation’ specification, except the baseFeePerBlobGas calculation.
header.excessBlobGasequals the value calculated from the parent header.def calcExcessBlobGas(parent: Header) -> int: return max(0, parent.excessBlobGas + parent.blobGasUsed - TARGET_BLOB_GAS_PER_BLOCK) assert block.header.excessBlobGas == calcExcessBlobGas(block.parent.header)- For each BlobTx, the sender has enough balance to fund both execution gas and blob gas.
for tx in block.transactions: ... max_execution_fee = tx.gas * tx.maxFeePerGas max_blob_fee = len(tx.blobVersionedHashes) * GAS_PER_BLOB * tx.maxFeePerBlobGas assert signer(tx).balance >= max_execution_fee + max_blob_fee ... - For each BlobTx, there is at least one blob versioned hash with the correct version.
for tx in block.transactions: ... if tx.type == BLOB_TX_TYPE: assert len(tx.blobVersionedHashes) > 0 for h in tx.blobVersionedHashes: assert h[0] == VERSIONED_HASH_VERSION_KZG ... - For each BlobTx,
tx.maxFeePerBlobGasis at least the calculatedbaseFeePerBlobGas.baseFeePerBlobGas = BLOB_BASE_FEE_MULTIPLIER * block.header.baseFeePerGas for tx in block.transactions: if tx.type == BLOB_TX_TYPE: assert tx.maxFeePerBlobGas >= baseFeePerBlobGas header.blobGasUsedis correctly calculated from the transactions and is below the limit.blobGasUsed = 0 for tx in block.transactions: if tx.type == BLOB_TX_TYPE: blobGasUsed += len(tx.blobVersionedHashes) * GAS_PER_BLOB assert block.header.blobGasUsed == blobGasUsed assert block.header.blobGasUsed <= MAX_BLOB_GAS_PER_BLOCK
Opcodes
The BLOBHASH (0x49) opcode was introduced to the Kaia chain with the Cancun hardfork, but it has been returning a zero hash. Its behavior MUST change to take in one integer argument index and return tx.blob_versioned_hashes[index]. Its gas cost 3 stay the same.
The BLOBBASEFEE (0x4a) opcode was introduced to the Kaia chain with the Cancun hardfork, but it has been returning 0. Its behavior MUST change to return the blob base fee of the current block it is executing in. Its gas cost 2 MUST stay the same.
Precompile
The POINT_EVALUATION_PRECOMPILE (0x0a) was introduced to the Kaia chain with the Cancun hardfork, and it does not change.
API
eth_sendRawTransaction, kaia_sendRawTransaction
eth_sendRawTransaction and kaia_sendRawTransaction accepts a BlobTxWithBlobs. It MUST NOT accept BlobTx without the sidecar.
eth_getRawTransaction, kaia_getRawTransaction
eth_getRawTransaction returns the raw transaction RLP prefixed with BLOB_TX_TYPE (0x03). kaia_getRawTransaction returns the raw transaction RLP prefixed with TxTypeEthereumBlob (0x7803). Both APIs never return BlobTxWithBlobs.
eth_getBlock, eth_getTransaction, eth_getTransactionReceipt
These eth namespace APIs that return transaction fields MUST show the maxFeePerBlobGas and blobVersionedHashes fields, and its type: "0x03".
The eth_getTransactionReceipt MUST also show blobGasUsed and blobGasPrice fileds.
kaia_getBlock, kaia_getTransaction, kaia_getTransactionReceipt
These kaia namespace APIs that return transaction fields MUST show the maxFeePerBlobGas and blobVersionedHashes fields, and its typeInt: 30723, and type: "TxTypeEthereumBlob".
The eth_getTransactionReceipt MUST also show blobGasUsed and blobGasPrice fileds.
eth_getBlobSidecars
A new JSON-RPC API eth_getBlobSidecars returns the stored BlobTxSidecars associated with the BlobTxs in the specified block. An empty array is returned if the block does not contain any BlobTx. An error is returned if the block has some BlobTx but any of the associated BlobTxSidecars are unavailable in the RPC node, e.g. deleted by expiration or didn’t receive from peers.
- Parameters:
number- integer or hexadecimal block number, or the string tags such as “pending”, “latest”.fullBlob- If true, returns the full blob data. Otherwise, return up to the first 32 bytes of each blob. False by default.
- Returns:
- An array of:
blobSidecar- A sidecar objectversion- Thesidecar_versionin integerblobs- An array of blobs in hex stringscommitments- An array of commitments in hex stringsproofs- An array of proofs in hex strings
blockHash- The hash of the block the blob is includedblockNumber- The number of the block the blob is includedtxHash- The hash of the transaction the blob is includedtxIndex- The index of the transaction the blob is included
- An array of:
- Example
curl http://localhost:8551 -X POST -H "Content-Type: application/json" \ --data '{"jsonrpc":"2.0", "id":1, "method":"eth_getBlobSidecars", "params":["0x1234",false]}' \{ "jsonrpc": "2.0", "id": 1, "result": [ { "blobSidecar": { "version": 1, "blobs": [ "0x61c47a49eb50be125fa6c05e1bc9f3eb1c7c555bddd93d8fc1be4d0bff6cae32" ], "commitments": [ "0x90b049b9255bf13b96c226a1ec1a3dac0deac6533b55378c846e91839000ddc31a662afdfc489694add406846cfeefbf" ], "proofs": [ "0xb4104a0b89b4a302c44c753560fae156107746edc98ce81c91d7045490eebee984bd9d96648bad6ff1454643ad00d2e1" ] }, "blockHash": "0x1d67a5039edec9296a3f5935111da5b712df541905ff6ce9f3581d3bc7a1afbd", "blockNumber": "0xc0f6b6b", "txHash": "0xbb7376baf28c7a1698729ce91266ac82652281704fe217e0b5a5ef968e62b169", "txIndex": "0x1", } ] }
eth_getBlobSidecarByTxHash
A new JSON-RPC API eth_getBlobSidecarByTxHash returns the stored BlobTxSidecars associated with the specified BlobTx. An error is returned if the transaction does not exist or is not a BlobTx type. An error is returned if the transaction is a BlobTx but the associated BlobTxSidecars are unavailable in the RPC node, e.g. deleted by expiration or didn’t receive from peers.
- Parameters:
txHash- The hash of the blob transaction.fullBlob- If true, returns the full blob data. Otherwise, return up to the first 32 bytes of each blob. False by default.
- Returns:
blobSidecar- A sidecar objectversion- Thesidecar_versionin integerblobs- An array of blobs in hex stringscommitments- An array of commitments in hex stringsproofs- An array of proofs in hex strings
blockHash- The hash of the block the blob is includedblockNumber- The number of the block the blob is includedtxHash- The hash of the transaction the blob is includedtxIndex- The index of the transaction the blob is included- Example
curl http://localhost:8551 -X POST -H "Content-Type: application/json" \ --data '{"jsonrpc":"2.0", "id":1, "method":"eth_getBlobSidecarByTxHash", "params":["0xbb7376baf28c7a1698729ce91266ac82652281704fe217e0b5a5ef968e62b169",false]}' \{ "jsonrpc": "2.0", "id": 1, "result": { "blobSidecar": { "version": 1, "blobs": [ "0x61c47a49eb50be125fa6c05e1bc9f3eb1c7c555bddd93d8fc1be4d0bff6cae32" ], "commitments": [ "0x90b049b9255bf13b96c226a1ec1a3dac0deac6533b55378c846e91839000ddc31a662afdfc489694add406846cfeefbf" ], "proofs": [ "0xb4104a0b89b4a302c44c753560fae156107746edc98ce81c91d7045490eebee984bd9d96648bad6ff1454643ad00d2e1" ] }, "blockHash": "0x1d67a5039edec9296a3f5935111da5b712df541905ff6ce9f3581d3bc7a1afbd", "blockNumber": "0xc0f6b6b", "txHash": "0xbb7376baf28c7a1698729ce91266ac82652281704fe217e0b5a5ef968e62b169", "txIndex": "0x1", } }
eth_blobBaseFee
A new JSON-RPC API eth_blobBaseFee returns the expected next block’s baseFeePerBlobGas. Clients may use this value to choose an appropriate tx.maxFeePerBlobGas. The expected value is BLOB_BASE_FEE_MULTIPLIER * next baseFeePerGas.
- Parameters: none
- Returns:
result: The expected blob base fee in kei in hex string.
- Example
curl http://localhost:8551 -X POST -H "Content-Type: application/json" \ --data '{"jsonrpc":"2.0", "id":1, "method":"eth_blobBaseFee", "params":[]}' \{ "jsonrpc": "2.0", "id": 1, "result": "0x2e90edd000" }
eth_feeHistory, kaia_feeHistory
The eth_feeHistory and kaia_feeHistory APIs MAY return an additional array field baseFeePerBlobGas that contains the historic blob base fee over the requested block range.
Rationale
Blob capacity
Ethereum blobspace supply is 0.5 blobs/sec since Pectra hardfork’s EIP-7691 (target 6 blobs per block, 12 second block time). Ethereum’s blobspace utilization is currently at around 60%. Ethereum is planning to reach 1.2 blobs/sec in the future after EIP-7892 BPO2 hardfork.
This KIP proposes higher or similar capacity of 1 blobs/sec. This will be sufficient enough to accommodate several L2s.
The network should easily handle 1 blob per block because one blob (134 KB including cell proofs) is much smaller than the theoretical block size limit of 10 MB.
Blob base fee as a multiple of base fee
This KIP proposes the max 1 blobs per block (MAX_BLOB_GAS_PER_BLOCK = TARGET_BLOB_GAS_PER_BLOCK = 1 * GAS_PER_BLOB) parameter. Under this configuration, excessBlobGas is always zero.
EIP-4844 adjusts the blob base fee based on previous block’s excessBlobGas. This mechanism cannot work in this KIP because MAX equals the TARGET.
EIP-7918 imposes an implicit lower bound (“reserve price”) to the blob base fee. It is implicit in a way that excessBlobGas does not drop when the blob base fee is lower than the reserve price. Again, this mechanism cannot be applied as-is in this KIP because excessBlobGas does not rise at the first place. The reserve price here is a certain multiple of base fee.
This KIP directly defines the blob base fee as a multiple of base fee, skipping the excessBlobGas logic. It inherits the the rationale of EIP-7918 to make BlobTxs pay for the computing cost it incurs.
Blob base fee multiplier
The blob fee is priced relative to a large-calldata transaction price.
- Storing in calldata: Send a regular transaction with a 128 KB calldata.
- Sending BlobTx: Send a BlobTxWithBlobs. Its size is 134 KB including the 128 cell proofs.
Computing cost
Broadcasting a BlobTx incurs KZG proof verification to every node in the network. According to the estimation in EIP-7918, batch-verifying CELLS_PER_EXT_BLOB (128) proofs costs roughly 15 times the POINT_EVALUATION_PRECOMPILE (50000 gas). Therefore, we can estimate that verifying a blob costs about 750,000 gas.
Storage cost
A significant storage burden is saved with BlobTx because sidecars are only persisted for a short period of time. In contrast, transaction calldata are stored indefinitely. For comparion, let us assume that EIP-4444 is activated in the future so that transaction calldata are stored for a year.
Storing a 128 KB calldata indefinitely (but 1 year in this calculation) is priced at 5,242,880 gas. With KIP-223 and EIP-7623, nonzero byte costs 40 gas per byte. Usually L2 rollup data are compressed blobs, so they are mostly nonzero random bytes. Therefore, 40 * 128 * 1024 = 5,242,880 gas.
Proportionally, storing a 134 KB blob for 21 days should be priced at (21day * 134KB) / (1year * 128KB) * 5,242,880 = 315,785 gas
Blob base fee
Based on the computing and storage costs, a blob fee should be equivalent to paying an execution gas fee of 750,000 + 315,785 = 1,065,785 gas. This is around 5 times cheaper than sending a large calldata. Now translate the gas cost to the unit of blob base fee.
(1,065,785 gas) * (baseFee kei/gas) ~ (1 blob) * (131072 blobgas/blob) * (blobBaseFee kei/blobgas)
blobBaseFee ~= (8.13 gas/blobgas) * (baseFee kei/gas)
Therefore, baseFeePerBlobGas = BLOB_BASE_FEE_MULTIPLIER (8) * baseFeePerGas. For instance:
- When baseFeePerGas = 25 gkei,
- Calldata gas fee =
5,242,880 * 25gkei = 0.131 KAIA - BlobTx blob fee =
131072 * 8 * 25gkei = 0.026 KAIAis 5x cheaper.
Reject sidecar V0
The EIP-4844 defined the blobTxWithBlobs without the version field (V0) that had been used since Dencun Hardfork. Since Fusaka Hardfork, only the new format - with the version field (V1) - will be accepted (EIP-7607, EIP-7594, go-ethereum blobpool). Since we haven’t supported V0, we start with V1 at the beginning of BlobTx in Kaia.
BlobTxWithBlobsV0 = rlp([TransactionPayloadBody, blobs, commitments, proofs]
BlobTxWithBlobsV1 = rlp([TransactionPayloadBody, sidecar_version, blobs, commitments, proofs]
Custom RPC for sidecar retrieval
Ethereum provides the sidecars via its Beacon API /eth/v1/beacon/blobs/{block_id}. Since Kaia does not have Beacon API framework, new JSON-RPC APIs eth_getBlobSidecars and eth_getBlobSidecarByTxHash were added.
References
- EIP-4844: Shard Blob Transactions
- EIP-7516: BLOBBASEFEE instruction
- EIP-7594: PeerDAS - Peer Data Availability Sampling
- EIP-7918: Blob base fee bounded by execution cost
- Ethereum Deneb consensus specs
- Ethereum Fulu consensus specs
- Kaia BLOBHASH, BLOBBASEFEE, KZG precompile implementation
- Kaia RLP block size limit implemenation
- AWS pricing calculator