Skip to content

Latest commit

 

History

History
215 lines (162 loc) · 8.96 KB

06-Upgrade-of-OPStack-in-EIP-4844.md

File metadata and controls

215 lines (162 loc) · 8.96 KB

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

EIP-4844, Blobs, and Blob Gas: What you need to know

Proto-Danksharding

OP-Stack的应用

OP-Stack在采用BLOB替换之前的CALLDATA作为rollup的数据存储方式后,费率直线下降 image

在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 定义

Pull Request(8767) encoding & decoding

定义blob

BlobSize        = 4096 * 32

type Blob [BlobSize]byte

blob encoding

official specs about 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)

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)

GetBlob

GetBlob负责获取blob数据,其主要逻辑包括利用4096个字段元素构建完整的blob,并通过commitment验证构建的blob的正确性。 同时,GetBlob也参与了上层L1Retrieval中的逻辑流程

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相关

Pull Request(9182), client sign相关