---
# System prepended metadata

title: OPStack在EIP-4844中的升级

---

# OPStack在EIP-4844中的升级
Ethereum的EIP-4844对layer2来说是一次巨大的变革，它显著降低了layer2在使用L1作为DA（数据可用性）的费用。本文不详细解析EIP-4844的具体内容，只简要介绍，作为我们了解OP-Stack更新的背景。

## EIP-4844
TL;DR: 
EIP-4844引入了一种称为“blob”的数据格式，这种格式的数据不参与EVM执行，而是存储在共识层，每个数据块的生命周期为4096个epoch（约18天）。blob存在于l1主网上，由新的type3 transaction携带，每个区块最多能容纳6个blob，每个transaction最多可以携带6个blob。

欲了解更多详情，请参考以下文章：
[Ethereum Evolved: Dencun Upgrade Part 5, EIP-4844](https://consensys.io/blog/ethereum-evolved-dencun-upgrade-part-5-eip-4844)
[EIP-4844, Blobs, and Blob Gas: What you need to know](https://www.blocknative.com/blog/eip-4844-blobs-and-blob-gas-what-you-need-to-know)
[Proto-Danksharding](https://notes.ethereum.org/@vbuterin/proto_danksharding_faq#Proto-Danksharding-FAQ)

## OP-Stack的应用
OP-Stack在采用BLOB替换之前的CALLDATA作为rollup的数据存储方式后，费率直线下降
![image](https://hackmd.io/_uploads/BJCSEyngA.png)

**在OP-Stack的此次更新中，主要的业务逻辑变更涉及将原先通过calldata发送的数据转换为blob格式，并通过blob类型的交易发送到L1。此外，还涉及到从L1获取发送到rollup的数据时对blob的解析，以下是参与此次升级的主要组件：**

1. **submitter** —— 负责将rollup数据发送到L1的组件
2. **fetcher**  —— 将L1的数据（旧rollup数据/deposit交易等）同步到L2中
3. **blob相关定义与实现** —— 如何获取和结构blob数据等内容等
4. **其他相关设计部分** —— 如客户端支持blob类型交易的签名、与fault proof相关的设计等




---

⚠️⚠️⚠️请注意，本文中所有涉及的代码均基于最初的PR设计，可能与实际生产环境中运行的代码存在差异。


---

### Blob相关定义与编解码实现

[Pull Request(8131) blob 定义](https://github.com/ethereum-optimism/optimism/pull/8131/files#diff-30107b16d72d6e958093d83b5d736522a7994cab064187562605c82174400cd5)
[Pull Request(8767) encoding & decoding](https://github.com/ethereum-optimism/optimism/commit/78ecdf523026d0afa45c519524a15b83cbe162c8#diff-30107b16d72d6e958093d83b5d736522a7994cab064187562605c82174400cd5R86)

#### 定义blob
	BlobSize        = 4096 * 32
    
    type Blob [BlobSize]byte

#### blob encoding
[**official specs about blob encoding**](https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/derivation.md#blob-encoding)
需要注意的是，此specs对应的是最新版本的代码，而下方PR截取的代码则为最初的简化版本。主要区别在于：Blob类型被分为4096个字段元素，每个字段元素的最大大小受限于特定模的大小，即math.log2(BLS_MODULUS) = 254.8570894...，这意味着每个字段元素的大小不会超过254位，即31.75字节。最初的演示代码只使用了31字节，放弃了0.75字节的空间。而在最新版本的代码中，通过四个字段元素的联合作用，充分利用了每个字段元素的0.75字节空间，从而提高了数据的使用效率。
以下为Pull Request(8767)的部分截取代码
通过4096次循环，它读取总共31*4096字节的数据，这些数据随后被加入到blob中。
```
func (b *Blob) FromData(data Data) error {
	if len(data) > MaxBlobDataSize {
		return fmt.Errorf("data is too large for blob. len=%v", len(data))
	}
	b.Clear()
	// encode 4-byte little-endian length value into topmost 4 bytes (out of 31) of first field
	// element
	binary.LittleEndian.PutUint32(b[1:5], uint32(len(data)))
	// encode first 27 bytes of input data into remaining bytes of first field element
	offset := copy(b[5:32], data)
	// encode (up to) 31 bytes of remaining input data at a time into the subsequent field element
	for i := 1; i < 4096; i++ {
		offset += copy(b[i*32+1:i*32+32], data[offset:])
		if offset == len(data) {
			break
		}
	}
	if offset < len(data) {
		return fmt.Errorf("failed to fit all data into blob. bytes remaining: %v", len(data)-offset)
	}
	return nil
}
```
#### blob decoding
blob数据的解码，原理同上述的数据编码
```
func (b *Blob) ToData() (Data, error) {
	data := make(Data, 4096*32)
	for i := 0; i < 4096; i++ {
		if b[i*32] != 0 {
			return nil, fmt.Errorf("invalid blob, found non-zero high order byte %x of field element %d", b[i*32], i)
		}
		copy(data[i*31:i*31+31], b[i*32+1:i*32+32])
	}
	// extract the length prefix & trim the output accordingly
	dataLen := binary.LittleEndian.Uint32(data[:4])
	data = data[4:]
	if dataLen > uint32(len(data)) {
		return nil, fmt.Errorf("invalid blob, length prefix out of range: %d", dataLen)
	}
	data = data[:dataLen]
	return data, nil
}
```


### Submiter
[Pull Request(8769)](https://github.com/ethereum-optimism/optimism/pull/8769)

#### flag配置
```
switch c.DataAvailabilityType {
case flags.CalldataType:
case flags.BlobsType:
default:
    return fmt.Errorf("unknown data availability type: %v", c.DataAvailabilityType)
}
```
#### BatchSubmitter
BatchSubmitter的功能从之前仅发送calldata数据扩展为根据情况发送calldata或blob类型的数据。Blob类型的数据通过之前提到的FromData（blob-encode）函数在blobTxCandidate内部进行编码
```
func (l *BatchSubmitter) sendTransaction(txdata txData, queue *txmgr.Queue[txData], receiptsCh chan txmgr.TxReceipt[txData]) error {
	// Do the gas estimation offline. A value of 0 will cause the [txmgr] to estimate the gas limit.
	data := txdata.Bytes()

	var candidate *txmgr.TxCandidate
	if l.Config.UseBlobs {
		var err error
		if candidate, err = l.blobTxCandidate(data); err != nil {
			// We could potentially fall through and try a calldata tx instead, but this would
			// likely result in the chain spending more in gas fees than it is tuned for, so best
			// to just fail. We do not expect this error to trigger unless there is a serious bug
			// or configuration issue.
			return fmt.Errorf("could not create blob tx candidate: %w", err)
		}
	} else {
		candidate = l.calldataTxCandidate(data)
	}

	intrinsicGas, err := core.IntrinsicGas(candidate.TxData, nil, false, true, true, false)
	if err != nil {
		// we log instead of return an error here because txmgr can do its own gas estimation
		l.Log.Error("Failed to calculate intrinsic gas", "err", err)
	} else {
		candidate.GasLimit = intrinsicGas
	}

	queue.Send(txdata, *candidate, receiptsCh)
	return nil
}

func (l *BatchSubmitter) blobTxCandidate(data []byte) (*txmgr.TxCandidate, error) {
	var b eth.Blob
	if err := b.FromData(data); err != nil {
		return nil, fmt.Errorf("data could not be converted to blob: %w", err)
	}
	return &txmgr.TxCandidate{
		To:    &l.RollupConfig.BatchInboxAddress,
		Blobs: []*eth.Blob{&b},
	}, nil
}
```

### Fetcher
[Pull Request(9098)](https://github.com/ethereum-optimism/optimism/pull/9098/files#diff-1fd8727490dbd2214b6d0c247eb222f2ac4098d259b4a45e9c0caea7fb2d3e08)

#### GetBlob
GetBlob负责获取blob数据，其主要逻辑包括利用4096个字段元素构建完整的blob，并通过commitment验证构建的blob的正确性。
同时，GetBlob也参与了上层[L1Retrieval中的逻辑流程](https://github.com/joohhnnn/Understanding-Optimism-Codebase-CN/blob/main/sequencer/04-how-derivation-works.md)。
```
func (p *PreimageOracle) GetBlob(ref eth.L1BlockRef, blobHash eth.IndexedBlobHash) *eth.Blob {
	// Send a hint for the blob commitment & blob field elements.
	blobReqMeta := make([]byte, 16)
	binary.BigEndian.PutUint64(blobReqMeta[0:8], blobHash.Index)
	binary.BigEndian.PutUint64(blobReqMeta[8:16], ref.Time)
	p.hint.Hint(BlobHint(append(blobHash.Hash[:], blobReqMeta...)))

	commitment := p.oracle.Get(preimage.Sha256Key(blobHash.Hash))

	// Reconstruct the full blob from the 4096 field elements.
	blob := eth.Blob{}
	fieldElemKey := make([]byte, 80)
	copy(fieldElemKey[:48], commitment)
	for i := 0; i < params.BlobTxFieldElementsPerBlob; i++ {
		binary.BigEndian.PutUint64(fieldElemKey[72:], uint64(i))
		fieldElement := p.oracle.Get(preimage.BlobKey(crypto.Keccak256(fieldElemKey)))

		copy(blob[i<<5:(i+1)<<5], fieldElement[:])
	}

	blobCommitment, err := blob.ComputeKZGCommitment()
	if err != nil || !bytes.Equal(blobCommitment[:], commitment[:]) {
		panic(fmt.Errorf("invalid blob commitment: %w", err))
	}

	return &blob
}
```
### 其他杂项

除了以上几个主要模块外，还包含例如负责签署type3类型transaction的client sign模块，和fault proof相关涉及的模块，fault proof会在下一章节进行详细描述，这里就不过多赘述了

[Pull Request(5452), fault proof相关](https://github.com/ethereum-optimism/optimism/commit/4739b0f8bfe2f3848af3f1a5661a038c5d602b2f#diff-790daa91002e5c07497fdc2d7c2149b551d77ccec1b1906cc70f575b7c7bad65)

[Pull Request(9182), client sign相关](https://github.com/ethereum-optimism/optimism/pull/9185/files#diff-8046655b02fcced5322724e2cd61ece649a9d79ba09405093f9ed70b2087e47d)
