KIP 279: BlobTx for Kaia Source

AuthorOllie, Nasu, Shogo
Discussions-Tohttps://devforum.kaia.io/t/discussion-on-kip-279/8897
StatusDraft
TypeCore
Created2025-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:]

blobtx_structure

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_version field before the blobs field. The version field is also referred to as wrapper_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 BlobTxWithBlobs via its eth_sendRawTransaction and kaia_sendRawTransaction JSON-RPC API.
  • A node SHOULD accept a BlobTxWithBlobs via its p2p TxMsg.
  • When a node receive a BlobTxWithBlobs via 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 originating BlobTxWithBlobs is verified.
  • Block proposals SHOULD include the BlobTxWithBlobs so validators can verify it.
  • Validators MUST assure that the BlobTxWithBlobs is verified before committing to the block. Note that verifying a BlobTxWithBlobs does not require block execution.
  • As a result, whenever you see a BlobTx in a finalized block, you are sure that there exists a corresponding BlobTxSidecar because you trust the CNs’ consensus.

Persist BlobTxSidecar

  • When a node receive a finalized block with BlobTx inside, the node SHOULD persist the corresponding BlobTxSidecar.
    • The BlobTxSidecar can come from the BlobTxWithBlobs in the node’s txpool memory.
    • The BlobTxSidecar can come from the peer over p2p.
  • Nodes MAY request peers for BlobTxSidecars in BlobSidecarsRequestMsg = 0x15 p2p message. In turn, nodes SHOULD respond peers with BlobTxSidecars in BlobSidecarsMsg = 0x16 p2p message, if available.
  • Nodes SHOULD NOT request peers for BlobTxSidecars whose block number is older than BLOB_SIDECARS_RETENTION before the known head block.
  • Nodes MAY delete the BlobTxSidecar whose block number is older than BLOB_SIDECARS_RETENTION before the head block.

blobtx_networking

BlobTxWithBlobs validation

Upon receiving a BlobTxWithBlobs via RPC or p2p, it is verified as follows.

  1. The BlobTxWithBlobs format 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
    
  2. The BlobTxWithBlobs contains the correct proofs
      for 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_sendRawTransaction RPC
  • Receive via TxMsg p2p message
  • Receive via IstanbulMsg p2p message’s Proposal payload. Note that the type Proposal interface is instantiated as *types.Block which includes the transactions inside. Since a BlobTxWithBlobs is 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.

  1. header.excessBlobGas equals 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)
    
  2. 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
     ...
    
  3. 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
     ...
    
  4. For each BlobTx, tx.maxFeePerBlobGas is at least the calculated baseFeePerBlobGas.
      baseFeePerBlobGas = BLOB_BASE_FEE_MULTIPLIER * block.header.baseFeePerGas
      for tx in block.transactions:
     if tx.type == BLOB_TX_TYPE:
       assert tx.maxFeePerBlobGas >= baseFeePerBlobGas
    
  5. header.blobGasUsed is 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 object
        • version - The sidecar_version in integer
        • blobs - An array of blobs in hex strings
        • commitments - An array of commitments in hex strings
        • proofs - An array of proofs in hex strings
      • blockHash - The hash of the block the blob is included
      • blockNumber - The number of the block the blob is included
      • txHash - The hash of the transaction the blob is included
      • txIndex - 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_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 object
      • version - The sidecar_version in integer
      • blobs - An array of blobs in hex strings
      • commitments - An array of commitments in hex strings
      • proofs - An array of proofs in hex strings
    • blockHash - The hash of the block the blob is included
    • blockNumber - The number of the block the blob is included
    • txHash - The hash of the transaction the blob is included
    • txIndex - 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 KAIA is 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