Design: EIP-4844 On BSC

1. Introduction

Ethereum EIP-4844 is used to reduce the L2 rollup cost, it mainly introduces a new transaction type: Blob-Carrying Transaction. The blob-carrying transaction(BlobTx) will contain 6 blobs at most, each blob’s size is fixed 128 KB and is represented by a KZG polynomial. The blob will eventually be saved by the Ethereum consensus layer for a fixed period of time (4906 Epochs, which is ~18.2 days for Ethereum).

BSC will implement EIP-4844 with same purpose and most part of the design will be similar to Ethereum, but with 2 major differences:

  • Propagation/Persistence Of Blobs: as there is no consensus layer on BSC, blobs will only be able to be saved and propagated by the execution layer.
  • Blob Base Fee will be burnt on Ethereum, but not on BSC, since BSC has different tokenomics mechanisms.

As 4844 is a big feature to BSC, there are some guidelines on how to integrate it on BSC:

  • L1 Chain’s liveness must be guaranteed.

  • Blob’s availability must be guaranteed, as users should always be able to achieve the unexpired blobs.

  • Better to keep compatible with ethereum, could be more friendly to users and also easier for future maintenance.

2.About Ethereum EIP-4844

2.1.Overview

EIP-4844 mainly consists of 3 parts:

  • Blob-Carrying TX: it is the main part of 4844, users can submit BlobTx which contains 1 or 2 blobs, each blob’s size is fixed 128 KB, and will be verified before adding to the TxPool. Once the BlobTx is included in a block, its blobs will be saved by the CL(Consensus Layer) for a period of time(~18.2 days). The EL(Execution Layer) can only access the commitment of the blob, but EL can not access the blob content directly.
  • BlobFee Market: blob will have its own blob fee market, there will be a dynamic base blob gas price calculation mechanism similar to EIP-1559.
  • Precompile Contract: it is a point evaluation precompile to verify a KZG proof which claims that a blob (represented by a commitment) evaluates to a given value at a given point.

The general workflow of BlobTx on Ethereum can be described as below:

2.2 EIP Details

  • blob-carrying transaction:
    • BlobTX will be propagated to EL’s BlobTxPool and verified by EL first. But EL actually can not access the blobs directly, the data will be forwarded and stored solely by CL;
    • Rollup sequencers (and potentially others) will use this new transaction type to post data to Ethereum mainnet with a much cheaper gas price;
    • Two new fields max_fee_per_blob_gas, blob_versioned_hashes;
  • New Precomplies
    • The point evaluation precompile;
    • The fraud proof use the function to verify the data against the versioned hash that was submitted before, and then perform the fraud proof verification on that data as is done today;
    • ZK Rollup will uses it to prove that the KZG (which the protocol ensures points to available data) and the ZK rollup’s own commitment refer to the same data;
  • BlobFee Market
    • 3 blobs will be targeted per block, it will increase up to 12.5% when blobs size hit the maximum 6 and will decrease to 12.5% when there is no blob at all.
  • Blob Expiry
    • Available for exactly 4096 epochs, which translates to roughly 18.2 days;
  • KZG Commitments
    • It associated with each blob, need Trusted Setup;
    • It can prove any position data on blob;
  • Block Header
    • Add 2 fields blob_gas_used, excess_blob_gas for blob market;
  • Related EIPs
    • EIP-7516 will ship to create the opcode BLOBBASEFEE;
    • EIP-5793 announce tx type for decrease bandwidth requirements;
    • EIP-7569 is Dencun hardfork meta information;

2.3 Key Parameters

  • Blob Size
    • Each blob attached to a block may hold 128 KB data, it contains 4096 field elements of 32 bytes;
  • Blob Amount Per Block
    • The number of blobs attached to a block is dynamic (ranging from 0-6), 3 blobs per block will be targeted;
    • Target add 384 KB per block, max at ~0.75MB;
  • Blob Gas Accounting
    • GAS_PER_BLOB = 131,072
    • MIN_BLOB_GASPRICE = 1 (1Wei)
    • In order to keep the number of blobs at 3, if it exceeds or decreases, the gas price will increase or decrease by up to 12.5%;
    • All blobs will be 128 KB in size regardless if they are completely filled or not;
  • KZG Trusted Setup
    • The Ethereum’s community conducted a KZG Trusted Setup Ceremony to create the necessary cryptographic parameters, see here;
  • Blob Expiry Period
    • A blob remains available for exactly 4096 epochs, which translates to roughly 18.2 days;
    • It must larger than optimistic rollups 7 day fault proof window;
    • 384KB x 32 blocks per epoch x 4096 epochs: 48GB Increase in Storage;

3. About BSC 4844

3.1.Overview

The general workflow of 4844 on BSC will be similar to Ethereum, mainly merging the consensus layer part into the execution layer.

3.2.Key Parameters

  • Blob Size: 128 KB, same as Ethereum
  • Blob Amount Per Block: same as Ethereum
  • Blob Gas Accounting:
    • GAS_PER_BLOB = 131072
    • MIN_BLOB_GASPRICE = 1 (1wei)
    • In order to keep the number of blobs at 3, if it exceeds or decreases, the dynamic blob gas price will increase or decrease by up to ~12.5%;
    • All blobs will be 128 KB in size regardless if they are completely filled or not;
  • KZG Trusted Setup: same as Ethereum
  • Blob Expiry Period
    • A blob remains available for exactly 524,288 (40963212/3sec) blocks, which is ~18.2 days, same as Ethereum
    • Target blob storage: 3*128KB*524288: 192GB;

3.3.Data Structure

a.Blob Tx: Same As Ethereum

New BlobTxType(0x3) with two new elements:

  • BlobFeeCap *uint256.Int
  • BlobHashes common.Hash
// BlobTx represents an EIP-4844 transaction.
type BlobTx struct {
  ChainID *uint256.Int
  Nonce uint64
  GasTipCap *uint256.Int // a.k.a. maxPriorityFeePerGas
  GasFeeCap *uint256.Int // a.k.a. maxFeePerGas
  Gas uint64
  To common.Address
  Value *uint256.Int
  Data []byte
  AccessList AccessList
  BlobFeeCap *uint256.Int // a.k.a. maxFeePerBlobGas
  BlobHashes []common.Hash
  // A blob transaction can optionally contain blobs. This field must be set when BlobTx
  // is used to create a transaction for sigining.
  Sidecar *BlobTxSidecar `rlp:"-"`
  // Signature values
  V *uint256.Int `json:"v" gencodec:"required"`
  R *uint256.Int `json:"r" gencodec:"required"`
  S *uint256.Int `json:"s" gencodec:"required"`
}

// BlobTxSidecar contains the blobs of a blob transaction.
type BlobTxSidecar struct {
  Blobs []kzg4844.Blob // Blobs needed by the blob pool
  Commitments []kzg4844.Commitment // Commitments needed by the blob pool
  Proofs []kzg4844.Proof // Proofs needed by the blob pool
}

User needs to provide the corresponding BlobTxSidecar along with the transaction content when submitting the transaction, BlobTx without blob is invalid.

But the sidecar will be pruned when the transaction is included in the block.

b.Block Header: Same As Ethereum

There will have 2 new elements:

  • BlobGasUsed: the total amount of blob gas consumed by the transactions within the block.
  • ExcessBlobGas: total of blob gas consumed in excess of the target, prior to the block. Blocks with above-target blob gas consumption increase this value, blocks with below-target blob gas consumption decrease it (bounded at 0).
// Header represents a block header in the Ethereum blockchain.

type Header struct {
  ...
  // BlobGasUsed was added by EIP-4844 and is ignored in legacy headers.
  BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"`
  // ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers.
  ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"`
  ...
}

Pseudocode to calculate ExcessBlobGas:

def calc_excess_blob_gas(parent: Header) -> int:
  if parent.excess_blob_gas + parent.blob_gas_used < TARGET_BLOB_GAS_PER_BLOCK:
    return 0
  else:
  return parent.excess_blob_gas + parent.blob_gas_used - TARGET_BLOB_GAS_PER_BLOCK

c.BlockWithExtraData: Only BSC

Unlike Ethereum, Blobs will be broadcasted with block as a unit for BSC, a new structure BlockWithExtraData will be introduced

// BlobTxSidecar contains the blobs of a blob transaction.
type BlobTxSidecar struct {
  Blobs []kzg4844.Blob // Blobs needed by the blob pool
  Commitments []kzg4844.Commitment // Commitments needed by the blob pool
  Proofs []kzg4844.Proof // Proofs needed by the blob pool
}

type BlockWithExtraData struct {
  Transactions []*Transaction
  Uncles []*Header
  Withdrawals []*Withdrawal `rlp:"optional"`
  Sidecars []*BlobTxSidecar `rlp:"optional"` // 1 sidecar for 1 BlobTx
}

3.4.Blob Pool: Same As Ethereum

A new dedicated BlobPool will be designed to propagate Blob Transactions. Since each blob will be 128 KB, which is significantly larger than normal transactions, the blob pool will be designed carefully to reduce the network traffic cost. As shown in the above diagram, the blob transactions will be cached in the database once it arrives in the blob pool, it will be helpful to avoid redownloading the blob transaction.

a.Blob Tx Propagation

Only broadcast BlobTxHash, if the remote peer wants to get the content, reply BlobTx with BlobTxSidecar, for each BlobTx its sidecar is always available.

b.BlobTx GasPrice Order

It will be the same as the current GasPrice order mechanism. Transactions with higher gas prices are likely to be included first, but it is not part of consensus, validators may not follow this rule.

3.5.Blob Storage

  • Available: the most recent 524,288 blocks’ blobs(~18.2 days) must be available, the period will be the same as Ethereum. It is used in sync protocol, during sync phase, blobs within this period must be available, otherwise sync will get stuck.
  • Expired::ExtraReserve: nodes will also keep additional 28800 blocks’ blobs(1 day) at least, in order to improve the sync UX. There will be a flag(--blob.extra-reserve $num) to configure this period of extra reserved blobs:

if $num == 0: will keep all historical blobs

if $num > 0 & $num < 28800: will keep additional 28800

others: will keep additional $num blobs

  • Expired::Pruned: blobs in this period will be pruned automatically.

Blobs will be kept the same way as blocks, i.e. the most recent 90K blocks and their blobs will be kept in the main LevelDB database, while older blocks and blobs will be kept in the ancient database, which is basically a large file based database.

a.Rational Of ExtraReserve Period

When a node has lagged for several weeks or starts to sync based on a recent snapshot, it may take hours to download/import the lagging blocks. While the node is trying to catch up to the latest height, the chain keeps moving forward, these extra blobs could be helpful to prevent the node from getting stuck due to missing blobs. It is not part of the protocol, so the reserve size could be changed in the future without a hard fork.

b.Blob Prune

Blob prune will be conducted by the freezer module, similar to block prune, expired blobs will be removed from the node. As mentioned above, with --blob.extra-reserve 0 , the blob will not be pruned.

3.6.Blob Sync

For Ethereum, Blob sync is implemented on the consensus layer with a module called: sidecar. Unlike Ethereum, BSC does not have the consensus layer, it could be quite complicated to implement it on BSC, as it will involve mechanisms like: backfilling, sidecar sync protocol, sidecar persistent… And it is not easy to port the code, as sidecar is in another repo: Prysm. Porting the code into BSC would cost lots of maintenance effort and could have potential risks(unknown bugs, blob availability issues…).

So we decided to implement Blob Sync in a simpler way, we don’t port Prysm’s sidecar logic, but to sync blobs with blocks directly. It would be much more reliable for blob availability, since we make it mandatory that blocks and blobs are propagated as a unit; when there is a block, its unexpired blobs must exist as well.

a.Mine Phase

When a validator mines a new block, it needs to encode Block and its Sidecar into one message and broadcast it to its connected peers. The encoded message could be large, like 1MB, but it could be acceptable on the BSC network.

In the future, there are some optimization opportunities to reduce the network traffic, like:

  • a.broadcast block and sidecars to a subset of the connected peers, sqrt root? cube root?
  • b.broadcast block only to another subnet, sqrt root of the remaining peers? And the peers query the sidecar if it does not have it.
  • c.broadcast block hash to the remaining peers and the peers query the block and sidecar that it does not have.

b.Download Phase

The blob sync operation will be combined with block sync, the sync operation will get stuck if it can not get the desired blob, i.e. the blob is still alive(within 18 days), but the remote peer can not provide the desired blobs.

The node needs to reselect a peer to sync blocks and blobs.

c.P2P Protocol

As blob transactions will be based on eth68 protocol, for blob propagation, there will be some updates to the original eth68 protocol.

These messages will be redefined based on eth68:

  • NewBlockMsg: encodes corresponding blobs into the message.
  • BlockBodiesMsg: encodes corresponding blobs into the message.

The receiver of these messages will also need to detect and get the encoded blobs.

3.7.Blob GasPrice

At the beginning, the dynamic BlobGasPrice calculation formula will be the same as Ethereum in order to provide competitive blob fees. It will be increased or decreased depending on the usage of blobs.

Once the BlobGasPrice hit 50 Gwei, the blob cost will be similar to the cost of current calldata. Here is the pseudo code about how to calculate the blob gas price:

MIN_BLOB_GASPRICE = 1
BLOB_GASPRICE_UPDATE_FRACTION = 3338477

def fake_exponential(factor: int, numerator: int, denominator: int) -> int:
  i = 1
  output = 0
  numerator_accum = factor * denominator
  while numerator_accum > 0:
    output += numerator_accum
    numerator_accum = (numerator_accum * numerator) // (denominator * i)
    i += 1
  return output // denominator

def calc_data_fee(header: Header, tx: Transaction) -> int:
  return get_total_blob_gas(tx) * get_blob_gasprice(header)

def get_total_blob_gas(tx: Transaction) -> int:
  return GAS_PER_BLOB * len(tx.blob_versioned_hashes)

def get_blob_gasprice(header: Header) -> int:
  return fake_exponential(
    MIN_BLOB_GASPRICE,
    header.excess_blob_gas,
    BLOB_GASPRICE_UPDATE_FRACTION
  )

And unlike Ethereum the base blob data fee will not be burnt, as BSC already has its own burn mechanism.

3.8.Target Blob Size

Take opBNB’s recent(2024-02-27) traffic for example, its average GasUsed per block is around 3.2M, it rollups ~117KB data to L1 every 15 seconds, equal to ~23.4KB for each block. For large traffic on opBNB, like 100M gas per block, it will be ~731KB(23.4*100/3.2), that is ~5.85 blobs(731KB/125KB).

So 6 blobs per block will be just enough to handle the maximum traffic of opBNB, if there are other L2s, then 6 blobs could be not enough. But at the first stage, we would prefer a conservative blob size, which will be the same as Ethereum, i.e. target 3 blobs and maximum 6 blobs for each block.

The blob size could be updated in the future.

3.9.New Opcode and Precompile Contract

Same as Ethereum.

# 2 new opcode
BLOBHASH OpCode = 0x49
BLOBBASEFEE OpCode = 0x4a

# 1 precompile contract
point_evaluation_precompile(...)

3.10.RPC APIs

Ethereum Beacon Chain provides 1 Restful API:
https://ethereum.github.io/beacon-APIs/#/Beacon/getBlobSidecars

BSC could provide 2 APIs:

type BlobTxSidecar struct {
	Blobs       []kzg4844.Blob       `json:"blobs"`       // Blobs needed by the blob pool
	Commitments []kzg4844.Commitment `json:"commitments"` // Commitments needed by the blob pool
	Proofs      []kzg4844.Proof      `json:"proofs"`      // Proofs needed by the blob pool
}

eth_getBlobSidecars(blockNrOrHash BlockNumberOrHash)
return: [(BlobTxSidecar, blockNum, blockHash, txIndex, txHash)...]

eth_getBlobSidecarByTxHash(hash common.Hash)
return: (BlobTxSidecar, blockNum, blockHash, txIndex, txHash)

4.FAQ

Refer: FAQ: About EIP-4844 on BSC

1 Like