feat(rollup-verifier): support codecv1 (#678)

This commit is contained in:
colin 2024-04-04 18:52:23 +08:00 committed by GitHub
parent 06f4573e49
commit 55b03ff405
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 1732 additions and 521 deletions

View file

@ -24,7 +24,7 @@ import (
const ( const (
VersionMajor = 5 // Major version component of the current release VersionMajor = 5 // Major version component of the current release
VersionMinor = 1 // Minor version component of the current release VersionMinor = 1 // Minor version component of the current release
VersionPatch = 30 // Patch version component of the current release VersionPatch = 31 // Patch version component of the current release
VersionMeta = "mainnet" // Version metadata to append to the version string VersionMeta = "mainnet" // Version metadata to append to the version string
) )

View file

@ -1,123 +0,0 @@
package rollup_sync_service
import (
"encoding/binary"
"fmt"
"math/big"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/crypto"
)
const batchHeaderVersion = 0
// BatchHeader contains batch header info to be committed.
type BatchHeader struct {
// Encoded in BatchHeaderV0Codec
version uint8
batchIndex uint64
l1MessagePopped uint64
totalL1MessagePopped uint64
dataHash common.Hash
parentBatchHash common.Hash
skippedL1MessageBitmap []byte
}
// NewBatchHeader creates a new BatchHeader
func NewBatchHeader(version uint8, batchIndex, totalL1MessagePoppedBefore uint64, parentBatchHash common.Hash, chunks []*Chunk) (*BatchHeader, error) {
// buffer for storing chunk hashes in order to compute the batch data hash
var dataBytes []byte
// skipped L1 message bitmap, an array of 256-bit bitmaps
var skippedBitmap []*big.Int
// the first queue index that belongs to this batch
baseIndex := totalL1MessagePoppedBefore
// the next queue index that we need to process
nextIndex := totalL1MessagePoppedBefore
for chunkID, chunk := range chunks {
// build data hash
totalL1MessagePoppedBeforeChunk := nextIndex
chunkHash, err := chunk.Hash(totalL1MessagePoppedBeforeChunk)
if err != nil {
return nil, err
}
dataBytes = append(dataBytes, chunkHash.Bytes()...)
// build skip bitmap
for blockID, block := range chunk.Blocks {
for _, tx := range block.Transactions {
if tx.Type != types.L1MessageTxType {
continue
}
currentIndex := tx.Nonce
if currentIndex < nextIndex {
return nil, fmt.Errorf("unexpected batch payload, expected queue index: %d, got: %d. Batch index: %d, chunk index in batch: %d, block index in chunk: %d, block hash: %v, transaction hash: %v", nextIndex, currentIndex, batchIndex, chunkID, blockID, block.Header.Hash(), tx.TxHash)
}
// mark skipped messages
for skippedIndex := nextIndex; skippedIndex < currentIndex; skippedIndex++ {
quo := int((skippedIndex - baseIndex) / 256)
rem := int((skippedIndex - baseIndex) % 256)
for len(skippedBitmap) <= quo {
bitmap := big.NewInt(0)
skippedBitmap = append(skippedBitmap, bitmap)
}
skippedBitmap[quo].SetBit(skippedBitmap[quo], rem, 1)
}
// process included message
quo := int((currentIndex - baseIndex) / 256)
for len(skippedBitmap) <= quo {
bitmap := big.NewInt(0)
skippedBitmap = append(skippedBitmap, bitmap)
}
nextIndex = currentIndex + 1
}
}
}
// compute data hash
dataHash := crypto.Keccak256Hash(dataBytes)
// compute skipped bitmap
bitmapBytes := make([]byte, len(skippedBitmap)*32)
for ii, num := range skippedBitmap {
bytes := num.Bytes()
padding := 32 - len(bytes)
copy(bitmapBytes[32*ii+padding:], bytes)
}
return &BatchHeader{
version: version,
batchIndex: batchIndex,
l1MessagePopped: nextIndex - totalL1MessagePoppedBefore,
totalL1MessagePopped: nextIndex,
dataHash: dataHash,
parentBatchHash: parentBatchHash,
skippedL1MessageBitmap: bitmapBytes,
}, nil
}
// Encode encodes the BatchHeader into RollupV2 BatchHeaderV0Codec Encoding.
func (b *BatchHeader) Encode() []byte {
batchBytes := make([]byte, 89+len(b.skippedL1MessageBitmap))
batchBytes[0] = b.version
binary.BigEndian.PutUint64(batchBytes[1:], b.batchIndex)
binary.BigEndian.PutUint64(batchBytes[9:], b.l1MessagePopped)
binary.BigEndian.PutUint64(batchBytes[17:], b.totalL1MessagePopped)
copy(batchBytes[25:], b.dataHash[:])
copy(batchBytes[57:], b.parentBatchHash[:])
copy(batchBytes[89:], b.skippedL1MessageBitmap[:])
return batchBytes
}
// Hash calculates the hash of the batch header.
func (b *BatchHeader) Hash() common.Hash {
return crypto.Keccak256Hash(b.Encode())
}

View file

@ -1,167 +0,0 @@
package rollup_sync_service
import (
"encoding/binary"
"errors"
"fmt"
"math"
"math/big"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil"
"github.com/scroll-tech/go-ethereum/core/types"
)
const blockContextByteSize = 60
// WrappedBlock contains the block's Header, Transactions and WithdrawTrieRoot hash.
type WrappedBlock struct {
Header *types.Header `json:"header"`
// Transactions is only used for recover types.Transactions, the from of types.TransactionData field is missing.
Transactions []*types.TransactionData `json:"transactions"`
WithdrawRoot common.Hash `json:"withdraw_trie_root,omitempty"`
}
// BlockContext represents the essential data of a block in the ScrollChain.
// It provides an overview of block attributes including hash values, block numbers, gas details, and transaction counts.
type BlockContext struct {
BlockHash common.Hash
ParentHash common.Hash
BlockNumber uint64
Timestamp uint64
BaseFee *big.Int
GasLimit uint64
NumTransactions uint16
NumL1Messages uint16
}
// numL1Messages returns the number of L1 messages in this block.
// This number is the sum of included and skipped L1 messages.
func (w *WrappedBlock) numL1Messages(totalL1MessagePoppedBefore uint64) uint64 {
var lastQueueIndex *uint64
for _, txData := range w.Transactions {
if txData.Type == types.L1MessageTxType {
lastQueueIndex = &txData.Nonce
}
}
if lastQueueIndex == nil {
return 0
}
// note: last queue index included before this block is totalL1MessagePoppedBefore - 1
// TODO: cache results
return *lastQueueIndex - totalL1MessagePoppedBefore + 1
}
// Encode encodes the WrappedBlock into RollupV2 BlockContext Encoding.
func (w *WrappedBlock) Encode(totalL1MessagePoppedBefore uint64) ([]byte, error) {
bytes := make([]byte, 60)
if !w.Header.Number.IsUint64() {
return nil, errors.New("block number is not uint64")
}
// note: numL1Messages includes skipped messages
numL1Messages := w.numL1Messages(totalL1MessagePoppedBefore)
if numL1Messages > math.MaxUint16 {
return nil, errors.New("number of L1 messages exceeds max uint16")
}
// note: numTransactions includes skipped messages
numL2Transactions := w.numL2Transactions()
numTransactions := numL1Messages + numL2Transactions
if numTransactions > math.MaxUint16 {
return nil, errors.New("number of transactions exceeds max uint16")
}
binary.BigEndian.PutUint64(bytes[0:], w.Header.Number.Uint64())
binary.BigEndian.PutUint64(bytes[8:], w.Header.Time)
// TODO: [16:47] Currently, baseFee is 0, because we disable EIP-1559.
binary.BigEndian.PutUint64(bytes[48:], w.Header.GasLimit)
binary.BigEndian.PutUint16(bytes[56:], uint16(numTransactions))
binary.BigEndian.PutUint16(bytes[58:], uint16(numL1Messages))
return bytes, nil
}
func txsToTxsData(txs types.Transactions) []*types.TransactionData {
txsData := make([]*types.TransactionData, len(txs))
for i, tx := range txs {
v, r, s := tx.RawSignatureValues()
nonce := tx.Nonce()
// We need QueueIndex in `NewBatchHeader`. However, `TransactionData`
// does not have this field. Since `L1MessageTx` do not have a nonce,
// we reuse this field for storing the queue index.
if msg := tx.AsL1MessageTx(); msg != nil {
nonce = msg.QueueIndex
}
txsData[i] = &types.TransactionData{
Type: tx.Type(),
TxHash: tx.Hash().String(),
Nonce: nonce,
ChainId: (*hexutil.Big)(tx.ChainId()),
Gas: tx.Gas(),
GasPrice: (*hexutil.Big)(tx.GasPrice()),
To: tx.To(),
Value: (*hexutil.Big)(tx.Value()),
Data: hexutil.Encode(tx.Data()),
IsCreate: tx.To() == nil,
V: (*hexutil.Big)(v),
R: (*hexutil.Big)(r),
S: (*hexutil.Big)(s),
}
}
return txsData
}
func convertTxDataToRLPEncoding(txData *types.TransactionData) ([]byte, error) {
data, err := hexutil.Decode(txData.Data)
if err != nil {
return nil, fmt.Errorf("failed to decode txData.Data: %s, err: %w", txData.Data, err)
}
tx := types.NewTx(&types.LegacyTx{
Nonce: txData.Nonce,
To: txData.To,
Value: txData.Value.ToInt(),
Gas: txData.Gas,
GasPrice: txData.GasPrice.ToInt(),
Data: data,
V: txData.V.ToInt(),
R: txData.R.ToInt(),
S: txData.S.ToInt(),
})
rlpTxData, err := tx.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("failed to marshal binary of the tx: %+v, err: %w", tx, err)
}
return rlpTxData, nil
}
func (w *WrappedBlock) numL2Transactions() uint64 {
var count uint64
for _, txData := range w.Transactions {
if txData.Type != types.L1MessageTxType {
count++
}
}
return count
}
func decodeBlockContext(encodedBlockContext []byte) (*BlockContext, error) {
if len(encodedBlockContext) != blockContextByteSize {
return nil, errors.New("block encoding is not 60 bytes long")
}
return &BlockContext{
BlockNumber: binary.BigEndian.Uint64(encodedBlockContext[0:8]),
Timestamp: binary.BigEndian.Uint64(encodedBlockContext[8:16]),
GasLimit: binary.BigEndian.Uint64(encodedBlockContext[48:56]),
NumTransactions: binary.BigEndian.Uint16(encodedBlockContext[56:58]),
NumL1Messages: binary.BigEndian.Uint16(encodedBlockContext[58:60]),
}, nil
}

View file

@ -1,155 +0,0 @@
package rollup_sync_service
import (
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"strings"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/rawdb"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/crypto"
)
// Chunk contains blocks to be encoded
type Chunk struct {
Blocks []*WrappedBlock `json:"blocks"`
}
// NumL1Messages returns the number of L1 messages in this chunk.
// This number is the sum of included and skipped L1 messages.
func (c *Chunk) NumL1Messages(totalL1MessagePoppedBefore uint64) uint64 {
var numL1Messages uint64
for _, block := range c.Blocks {
numL1MessagesInBlock := block.numL1Messages(totalL1MessagePoppedBefore)
numL1Messages += numL1MessagesInBlock
totalL1MessagePoppedBefore += numL1MessagesInBlock
}
// TODO: cache results
return numL1Messages
}
// Encode encodes the Chunk into RollupV2 Chunk Encoding.
func (c *Chunk) Encode(totalL1MessagePoppedBefore uint64) ([]byte, error) {
numBlocks := len(c.Blocks)
if numBlocks > 255 {
return nil, errors.New("number of blocks exceeds 1 byte")
}
if numBlocks == 0 {
return nil, errors.New("number of blocks is 0")
}
var chunkBytes []byte
chunkBytes = append(chunkBytes, byte(numBlocks))
var l2TxDataBytes []byte
for _, block := range c.Blocks {
blockBytes, err := block.Encode(totalL1MessagePoppedBefore)
if err != nil {
return nil, fmt.Errorf("failed to encode block: %v", err)
}
totalL1MessagePoppedBefore += block.numL1Messages(totalL1MessagePoppedBefore)
if len(blockBytes) != 60 {
return nil, fmt.Errorf("block encoding is not 60 bytes long %x", len(blockBytes))
}
chunkBytes = append(chunkBytes, blockBytes...)
// Append rlp-encoded l2Txs
for _, txData := range block.Transactions {
if txData.Type == types.L1MessageTxType {
continue
}
rlpTxData, err := convertTxDataToRLPEncoding(txData)
if err != nil {
return nil, err
}
var txLen [4]byte
binary.BigEndian.PutUint32(txLen[:], uint32(len(rlpTxData)))
l2TxDataBytes = append(l2TxDataBytes, txLen[:]...)
l2TxDataBytes = append(l2TxDataBytes, rlpTxData...)
}
}
chunkBytes = append(chunkBytes, l2TxDataBytes...)
return chunkBytes, nil
}
// Hash hashes the Chunk into RollupV2 Chunk Hash
func (c *Chunk) Hash(totalL1MessagePoppedBefore uint64) (common.Hash, error) {
chunkBytes, err := c.Encode(totalL1MessagePoppedBefore)
if err != nil {
return common.Hash{}, err
}
numBlocks := chunkBytes[0]
// concatenate block contexts
var dataBytes []byte
for i := 0; i < int(numBlocks); i++ {
// only the first 58 bytes of each BlockContext are needed for the hashing process
dataBytes = append(dataBytes, chunkBytes[1+60*i:60*i+59]...)
}
// concatenate l1 and l2 tx hashes
for _, block := range c.Blocks {
var l1TxHashes []byte
var l2TxHashes []byte
for _, txData := range block.Transactions {
txHash := strings.TrimPrefix(txData.TxHash, "0x")
hashBytes, err := hex.DecodeString(txHash)
if err != nil {
return common.Hash{}, err
}
if txData.Type == types.L1MessageTxType {
l1TxHashes = append(l1TxHashes, hashBytes...)
} else {
l2TxHashes = append(l2TxHashes, hashBytes...)
}
}
dataBytes = append(dataBytes, l1TxHashes...)
dataBytes = append(dataBytes, l2TxHashes...)
}
hash := crypto.Keccak256Hash(dataBytes)
return hash, nil
}
// DecodeChunkBlockRanges decodes the provided chunks into a list of block ranges. Each chunk
// contains information about multiple blocks, which are decoded and their ranges (from the
// start block to the end block) are returned.
func DecodeChunkBlockRanges(chunks [][]byte) ([]*rawdb.ChunkBlockRange, error) {
var chunkBlockRanges []*rawdb.ChunkBlockRange
for _, chunk := range chunks {
if len(chunk) < 1 {
return nil, fmt.Errorf("invalid chunk, length is less than 1")
}
numBlocks := int(chunk[0])
if len(chunk) < 1+numBlocks*blockContextByteSize {
return nil, fmt.Errorf("chunk size doesn't match with numBlocks, byte length of chunk: %v, expected length: %v", len(chunk), 1+numBlocks*blockContextByteSize)
}
blockContexts := make([]*BlockContext, numBlocks)
for i := 0; i < numBlocks; i++ {
startIdx := 1 + i*blockContextByteSize // add 1 to skip numBlocks byte
endIdx := startIdx + blockContextByteSize
blockContext, err := decodeBlockContext(chunk[startIdx:endIdx])
if err != nil {
return nil, err
}
blockContexts[i] = blockContext
}
chunkBlockRanges = append(chunkBlockRanges, &rawdb.ChunkBlockRange{
StartBlockNumber: blockContexts[0].BlockNumber,
EndBlockNumber: blockContexts[len(blockContexts)-1].BlockNumber,
})
}
return chunkBlockRanges, nil
}

View file

@ -56,7 +56,7 @@ func newL1Client(ctx context.Context, l1Client sync_service.EthClient, l1ChainId
} }
// fetcRollupEventsInRange retrieves and parses commit/revert/finalize rollup events between block numbers: [from, to]. // fetcRollupEventsInRange retrieves and parses commit/revert/finalize rollup events between block numbers: [from, to].
func (c *L1Client) fetchRollupEventsInRange(ctx context.Context, from, to uint64) ([]types.Log, error) { func (c *L1Client) fetchRollupEventsInRange(from, to uint64) ([]types.Log, error) {
log.Trace("L1Client fetchRollupEventsInRange", "fromBlock", from, "toBlock", to) log.Trace("L1Client fetchRollupEventsInRange", "fromBlock", from, "toBlock", to)
query := ethereum.FilterQuery{ query := ethereum.FilterQuery{
@ -80,8 +80,8 @@ func (c *L1Client) fetchRollupEventsInRange(ctx context.Context, from, to uint64
} }
// getLatestFinalizedBlockNumber fetches the block number of the latest finalized block from the L1 chain. // getLatestFinalizedBlockNumber fetches the block number of the latest finalized block from the L1 chain.
func (c *L1Client) getLatestFinalizedBlockNumber(ctx context.Context) (uint64, error) { func (c *L1Client) getLatestFinalizedBlockNumber() (uint64, error) {
header, err := c.client.HeaderByNumber(ctx, big.NewInt(int64(rpc.FinalizedBlockNumber))) header, err := c.client.HeaderByNumber(c.ctx, big.NewInt(int64(rpc.FinalizedBlockNumber)))
if err != nil { if err != nil {
return 0, err return 0, err
} }

View file

@ -26,11 +26,11 @@ func TestL1Client(t *testing.T) {
l1Client, err := newL1Client(ctx, mockClient, 11155111, scrollChainAddress, scrollChainABI) l1Client, err := newL1Client(ctx, mockClient, 11155111, scrollChainAddress, scrollChainABI)
require.NoError(t, err, "Failed to initialize L1Client") require.NoError(t, err, "Failed to initialize L1Client")
blockNumber, err := l1Client.getLatestFinalizedBlockNumber(ctx) blockNumber, err := l1Client.getLatestFinalizedBlockNumber()
assert.NoError(t, err, "Error getting latest confirmed block number") assert.NoError(t, err, "Error getting latest confirmed block number")
assert.Equal(t, uint64(36), blockNumber, "Unexpected block number") assert.Equal(t, uint64(36), blockNumber, "Unexpected block number")
logs, err := l1Client.fetchRollupEventsInRange(ctx, 0, blockNumber) logs, err := l1Client.fetchRollupEventsInRange(0, blockNumber)
assert.NoError(t, err, "Error fetching rollup events in range") assert.NoError(t, err, "Error fetching rollup events in range")
assert.Empty(t, logs, "Expected no logs from fetchRollupEventsInRange") assert.Empty(t, logs, "Expected no logs from fetchRollupEventsInRange")
} }

View file

@ -20,6 +20,9 @@ import (
"github.com/scroll-tech/go-ethereum/rollup/rcfg" "github.com/scroll-tech/go-ethereum/rollup/rcfg"
"github.com/scroll-tech/go-ethereum/rollup/sync_service" "github.com/scroll-tech/go-ethereum/rollup/sync_service"
"github.com/scroll-tech/go-ethereum/rollup/types/encoding"
"github.com/scroll-tech/go-ethereum/rollup/types/encoding/codecv0"
"github.com/scroll-tech/go-ethereum/rollup/types/encoding/codecv1"
"github.com/scroll-tech/go-ethereum/rollup/withdrawtrie" "github.com/scroll-tech/go-ethereum/rollup/withdrawtrie"
) )
@ -150,7 +153,7 @@ func (s *RollupSyncService) Stop() {
} }
func (s *RollupSyncService) fetchRollupEvents() { func (s *RollupSyncService) fetchRollupEvents() {
latestConfirmed, err := s.client.getLatestFinalizedBlockNumber(s.ctx) latestConfirmed, err := s.client.getLatestFinalizedBlockNumber()
if err != nil { if err != nil {
log.Warn("failed to get latest confirmed block number", "err", err) log.Warn("failed to get latest confirmed block number", "err", err)
return return
@ -170,7 +173,7 @@ func (s *RollupSyncService) fetchRollupEvents() {
to = latestConfirmed to = latestConfirmed
} }
logs, err := s.client.fetchRollupEventsInRange(s.ctx, from, to) logs, err := s.client.fetchRollupEventsInRange(from, to)
if err != nil { if err != nil {
log.Error("failed to fetch rollup events in range", "from block", from, "to block", to, "err", err) log.Error("failed to fetch rollup events in range", "from block", from, "to block", to, "err", err)
return return
@ -225,7 +228,7 @@ func (s *RollupSyncService) parseAndUpdateRollupEventLogs(logs []types.Log, endB
return fmt.Errorf("failed to get local node info, batch index: %v, err: %w", batchIndex, err) return fmt.Errorf("failed to get local node info, batch index: %v, err: %w", batchIndex, err)
} }
endBlock, finalizedBatchMeta, err := validateBatch(event, parentBatchMeta, chunks, s.stack) endBlock, finalizedBatchMeta, err := validateBatch(event, parentBatchMeta, chunks, s.bc.Config(), s.stack)
if err != nil { if err != nil {
return fmt.Errorf("fatal: validateBatch failed: finalize event: %v, err: %w", event, err) return fmt.Errorf("fatal: validateBatch failed: finalize event: %v, err: %w", event, err)
} }
@ -249,7 +252,7 @@ func (s *RollupSyncService) parseAndUpdateRollupEventLogs(logs []types.Log, endB
return nil return nil
} }
func (s *RollupSyncService) getLocalInfoForBatch(batchIndex uint64) (*rawdb.FinalizedBatchMeta, []*Chunk, error) { func (s *RollupSyncService) getLocalInfoForBatch(batchIndex uint64) (*rawdb.FinalizedBatchMeta, []*encoding.Chunk, error) {
chunkBlockRanges := rawdb.ReadBatchChunkRanges(s.db, batchIndex) chunkBlockRanges := rawdb.ReadBatchChunkRanges(s.db, batchIndex)
if len(chunkBlockRanges) == 0 { if len(chunkBlockRanges) == 0 {
return nil, nil, fmt.Errorf("failed to get batch chunk ranges, empty chunk block ranges") return nil, nil, fmt.Errorf("failed to get batch chunk ranges, empty chunk block ranges")
@ -277,21 +280,21 @@ func (s *RollupSyncService) getLocalInfoForBatch(batchIndex uint64) (*rawdb.Fina
return nil, nil, fmt.Errorf("local node is not synced up to the required block height: %v, local synced block height: %v", endBlockNumber, localSyncedBlockHeight) return nil, nil, fmt.Errorf("local node is not synced up to the required block height: %v, local synced block height: %v", endBlockNumber, localSyncedBlockHeight)
} }
chunks := make([]*Chunk, len(chunkBlockRanges)) chunks := make([]*encoding.Chunk, len(chunkBlockRanges))
for i, cr := range chunkBlockRanges { for i, cr := range chunkBlockRanges {
chunks[i] = &Chunk{Blocks: make([]*WrappedBlock, cr.EndBlockNumber-cr.StartBlockNumber+1)} chunks[i] = &encoding.Chunk{Blocks: make([]*encoding.Block, cr.EndBlockNumber-cr.StartBlockNumber+1)}
for j := cr.StartBlockNumber; j <= cr.EndBlockNumber; j++ { for j := cr.StartBlockNumber; j <= cr.EndBlockNumber; j++ {
block := s.bc.GetBlockByNumber(j) block := s.bc.GetBlockByNumber(j)
if block == nil { if block == nil {
return nil, nil, fmt.Errorf("failed to get block by number: %v", i) return nil, nil, fmt.Errorf("failed to get block by number: %v", i)
} }
txData := txsToTxsData(block.Transactions()) txData := encoding.TxsToTxsData(block.Transactions())
state, err := s.bc.StateAt(block.Root()) state, err := s.bc.StateAt(block.Root())
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to get block state, block: %v, err: %w", block.Hash().Hex(), err) return nil, nil, fmt.Errorf("failed to get block state, block: %v, err: %w", block.Hash().Hex(), err)
} }
withdrawRoot := withdrawtrie.ReadWTRSlot(rcfg.L2MessageQueueAddress, state) withdrawRoot := withdrawtrie.ReadWTRSlot(rcfg.L2MessageQueueAddress, state)
chunks[i].Blocks[j-cr.StartBlockNumber] = &WrappedBlock{ chunks[i].Blocks[j-cr.StartBlockNumber] = &encoding.Block{
Header: block.Header(), Header: block.Header(),
Transactions: txData, Transactions: txData,
WithdrawRoot: withdrawRoot, WithdrawRoot: withdrawRoot,
@ -362,22 +365,17 @@ func (s *RollupSyncService) decodeChunkBlockRanges(txData []byte) ([]*rawdb.Chun
SkippedL1MessageBitmap []byte SkippedL1MessageBitmap []byte
} }
var args commitBatchArgs var args commitBatchArgs
err = method.Inputs.Copy(&args, values) if err = method.Inputs.Copy(&args, values); err != nil {
if err != nil {
return nil, fmt.Errorf("failed to decode calldata into commitBatch args, values: %+v, err: %w", values, err) return nil, fmt.Errorf("failed to decode calldata into commitBatch args, values: %+v, err: %w", values, err)
} }
if args.Version != batchHeaderVersion { return decodeBlockRangesFromEncodedChunks(encoding.CodecVersion(args.Version), args.Chunks)
return nil, fmt.Errorf("unexpected batch version, expected: %v, got: %v", batchHeaderVersion, args.Version)
}
return DecodeChunkBlockRanges(args.Chunks)
} }
// validateBatch verifies the consistency between the L1 contract and L2 node data. // validateBatch verifies the consistency between the L1 contract and L2 node data.
// The function will terminate the node and exit if any consistency check fails. // The function will terminate the node and exit if any consistency check fails.
// It returns the number of the end block, a finalized batch meta data, and an error if any. // It returns the number of the end block, a finalized batch meta data, and an error if any.
func validateBatch(event *L1FinalizeBatchEvent, parentBatchMeta *rawdb.FinalizedBatchMeta, chunks []*Chunk, stack *node.Node) (uint64, *rawdb.FinalizedBatchMeta, error) { func validateBatch(event *L1FinalizeBatchEvent, parentBatchMeta *rawdb.FinalizedBatchMeta, chunks []*encoding.Chunk, chainCfg *params.ChainConfig, stack *node.Node) (uint64, *rawdb.FinalizedBatchMeta, error) {
if len(chunks) == 0 { if len(chunks) == 0 {
return 0, nil, fmt.Errorf("invalid argument: length of chunks is 0, batch index: %v", event.BatchIndex.Uint64()) return 0, nil, fmt.Errorf("invalid argument: length of chunks is 0, batch index: %v", event.BatchIndex.Uint64())
} }
@ -408,15 +406,31 @@ func validateBatch(event *L1FinalizeBatchEvent, parentBatchMeta *rawdb.Finalized
os.Exit(1) os.Exit(1)
} }
// Note: All params for NewBatchHeader are calculated locally based on the block data. // Note: All params of batch are calculated locally based on the block data.
batchHeader, err := NewBatchHeader(batchHeaderVersion, event.BatchIndex.Uint64(), parentBatchMeta.TotalL1MessagePopped, parentBatchMeta.BatchHash, chunks) batch := &encoding.Batch{
Index: event.BatchIndex.Uint64(),
TotalL1MessagePoppedBefore: parentBatchMeta.TotalL1MessagePopped,
ParentBatchHash: parentBatchMeta.BatchHash,
Chunks: chunks,
}
var localBatchHash common.Hash
if !chainCfg.IsBernoulli(startBlock.Header.Number) {
daBatch, err := codecv0.NewDABatch(batch)
if err != nil { if err != nil {
return 0, nil, fmt.Errorf("failed to construct batch header, batch index: %v, err: %w", event.BatchIndex.Uint64(), err) return 0, nil, fmt.Errorf("failed to create codecv0 DA batch, batch index: %v, err: %w", event.BatchIndex.Uint64(), err)
}
localBatchHash = daBatch.Hash()
} else {
daBatch, err := codecv1.NewDABatch(batch)
if err != nil {
return 0, nil, fmt.Errorf("failed to create codecv1 DA batch, batch index: %v, err: %w", event.BatchIndex.Uint64(), err)
}
localBatchHash = daBatch.Hash()
} }
// Note: If the batch headers match, this ensures the consistency of blocks and transactions // Note: If the batch headers match, this ensures the consistency of blocks and transactions
// (including skipped transactions) between L1 and L2. // (including skipped transactions) between L1 and L2.
localBatchHash := batchHeader.Hash()
if localBatchHash != event.BatchHash { if localBatchHash != event.BatchHash {
log.Error("Batch hash mismatch", "batch index", event.BatchIndex.Uint64(), "start block", startBlock.Header.Number.Uint64(), "end block", endBlock.Header.Number.Uint64(), "parent batch hash", parentBatchMeta.BatchHash.Hex(), "parent TotalL1MessagePopped", parentBatchMeta.TotalL1MessagePopped, "l1 finalized batch hash", event.BatchHash.Hex(), "l2 batch hash", localBatchHash.Hex()) log.Error("Batch hash mismatch", "batch index", event.BatchIndex.Uint64(), "start block", startBlock.Header.Number.Uint64(), "end block", endBlock.Header.Number.Uint64(), "parent batch hash", parentBatchMeta.BatchHash.Hex(), "parent TotalL1MessagePopped", parentBatchMeta.TotalL1MessagePopped, "l1 finalized batch hash", event.BatchHash.Hex(), "l2 batch hash", localBatchHash.Hex())
chunksJson, err := json.Marshal(chunks) chunksJson, err := json.Marshal(chunks)
@ -440,3 +454,59 @@ func validateBatch(event *L1FinalizeBatchEvent, parentBatchMeta *rawdb.Finalized
} }
return endBlock.Header.Number.Uint64(), finalizedBatchMeta, nil return endBlock.Header.Number.Uint64(), finalizedBatchMeta, nil
} }
// decodeBlockRangesFromEncodedChunks decodes the provided chunks into a list of block ranges.
func decodeBlockRangesFromEncodedChunks(codecVersion encoding.CodecVersion, chunks [][]byte) ([]*rawdb.ChunkBlockRange, error) {
var chunkBlockRanges []*rawdb.ChunkBlockRange
for _, chunk := range chunks {
if len(chunk) < 1 {
return nil, fmt.Errorf("invalid chunk, length is less than 1")
}
numBlocks := int(chunk[0])
switch codecVersion {
case encoding.CodecV0:
if len(chunk) < 1+numBlocks*60 {
return nil, fmt.Errorf("invalid chunk byte length, expected: %v, got: %v", 1+numBlocks*60, len(chunk))
}
daBlocks := make([]*codecv0.DABlock, numBlocks)
for i := 0; i < numBlocks; i++ {
startIdx := 1 + i*60 // add 1 to skip numBlocks byte
endIdx := startIdx + 60
daBlock, err := codecv0.DecodeDABlock(chunk[startIdx:endIdx])
if err != nil {
return nil, err
}
daBlocks[i] = daBlock
}
chunkBlockRanges = append(chunkBlockRanges, &rawdb.ChunkBlockRange{
StartBlockNumber: daBlocks[0].BlockNumber,
EndBlockNumber: daBlocks[len(daBlocks)-1].BlockNumber,
})
case encoding.CodecV1:
if len(chunk) != 1+numBlocks*60 {
return nil, fmt.Errorf("invalid chunk byte length, expected: %v, got: %v", 1+numBlocks*60, len(chunk))
}
daBlocks := make([]*codecv1.DABlock, numBlocks)
for i := 0; i < numBlocks; i++ {
startIdx := 1 + i*60 // add 1 to skip numBlocks byte
endIdx := startIdx + 60
daBlock, err := codecv1.DecodeDABlock(chunk[startIdx:endIdx])
if err != nil {
return nil, err
}
daBlocks[i] = daBlock
}
chunkBlockRanges = append(chunkBlockRanges, &rawdb.ChunkBlockRange{
StartBlockNumber: daBlocks[0].BlockNumber,
EndBlockNumber: daBlocks[len(daBlocks)-1].BlockNumber,
})
default:
return nil, fmt.Errorf("unexpected batch version %v", codecVersion)
}
}
return chunkBlockRanges, nil
}

View file

@ -19,6 +19,8 @@ import (
"github.com/scroll-tech/go-ethereum/ethdb/memorydb" "github.com/scroll-tech/go-ethereum/ethdb/memorydb"
"github.com/scroll-tech/go-ethereum/node" "github.com/scroll-tech/go-ethereum/node"
"github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/params"
"github.com/scroll-tech/go-ethereum/rollup/types/encoding"
) )
func TestRollupSyncServiceStartAndStop(t *testing.T) { func TestRollupSyncServiceStartAndStop(t *testing.T) {
@ -49,7 +51,7 @@ func TestRollupSyncServiceStartAndStop(t *testing.T) {
service.Stop() service.Stop()
} }
func TestDecodeChunkRanges(t *testing.T) { func TestDecodeChunkRangesCodecv0(t *testing.T) {
scrollChainABI, err := scrollChainMetaData.GetAbi() scrollChainABI, err := scrollChainMetaData.GetAbi()
require.NoError(t, err) require.NoError(t, err)
@ -57,17 +59,17 @@ func TestDecodeChunkRanges(t *testing.T) {
scrollChainABI: scrollChainABI, scrollChainABI: scrollChainABI,
} }
data, err := os.ReadFile("./testdata/commit_batch_transaction.json") data, err := os.ReadFile("./testdata/commitBatch_input_codecv0.json")
require.NoError(t, err, "Failed to read json file") require.NoError(t, err, "Failed to read json file")
type transactionJson struct { type tx struct {
CallData string `json:"calldata"` Input string `json:"input"`
} }
var txObj transactionJson var commitBatch tx
err = json.Unmarshal(data, &txObj) err = json.Unmarshal(data, &commitBatch)
require.NoError(t, err, "Failed to unmarshal transaction json") require.NoError(t, err, "Failed to unmarshal transaction json")
testTxData, err := hex.DecodeString(txObj.CallData[2:]) testTxData, err := hex.DecodeString(commitBatch.Input[2:])
if err != nil { if err != nil {
t.Fatalf("Failed to decode string: %v", err) t.Fatalf("Failed to decode string: %v", err)
} }
@ -78,14 +80,21 @@ func TestDecodeChunkRanges(t *testing.T) {
} }
expectedRanges := []*rawdb.ChunkBlockRange{ expectedRanges := []*rawdb.ChunkBlockRange{
{StartBlockNumber: 335921, EndBlockNumber: 335928}, {StartBlockNumber: 4435142, EndBlockNumber: 4435142},
{StartBlockNumber: 335929, EndBlockNumber: 335933}, {StartBlockNumber: 4435143, EndBlockNumber: 4435144},
{StartBlockNumber: 335934, EndBlockNumber: 335938}, {StartBlockNumber: 4435145, EndBlockNumber: 4435145},
{StartBlockNumber: 335939, EndBlockNumber: 335942}, {StartBlockNumber: 4435146, EndBlockNumber: 4435146},
{StartBlockNumber: 335943, EndBlockNumber: 335945}, {StartBlockNumber: 4435147, EndBlockNumber: 4435147},
{StartBlockNumber: 335946, EndBlockNumber: 335949}, {StartBlockNumber: 4435148, EndBlockNumber: 4435148},
{StartBlockNumber: 335950, EndBlockNumber: 335956}, {StartBlockNumber: 4435149, EndBlockNumber: 4435150},
{StartBlockNumber: 335957, EndBlockNumber: 335962}, {StartBlockNumber: 4435151, EndBlockNumber: 4435151},
{StartBlockNumber: 4435152, EndBlockNumber: 4435152},
{StartBlockNumber: 4435153, EndBlockNumber: 4435153},
{StartBlockNumber: 4435154, EndBlockNumber: 4435154},
{StartBlockNumber: 4435155, EndBlockNumber: 4435155},
{StartBlockNumber: 4435156, EndBlockNumber: 4435156},
{StartBlockNumber: 4435157, EndBlockNumber: 4435157},
{StartBlockNumber: 4435158, EndBlockNumber: 4435158},
} }
if len(expectedRanges) != len(ranges) { if len(expectedRanges) != len(ranges) {
@ -94,12 +103,63 @@ func TestDecodeChunkRanges(t *testing.T) {
for i := range ranges { for i := range ranges {
if *expectedRanges[i] != *ranges[i] { if *expectedRanges[i] != *ranges[i] {
t.Fatalf("Mismatch at index %d: expected %v, got %v", i, *expectedRanges[i], *ranges[i]) t.Errorf("Mismatch at index %d: expected %v, got %v", i, *expectedRanges[i], *ranges[i])
} }
} }
} }
func TestGetChunkRanges(t *testing.T) { func TestDecodeChunkRangesCodecv1(t *testing.T) {
scrollChainABI, err := scrollChainMetaData.GetAbi()
require.NoError(t, err)
service := &RollupSyncService{
scrollChainABI: scrollChainABI,
}
data, err := os.ReadFile("./testdata/commitBatch_input_codecv1.json")
require.NoError(t, err, "Failed to read json file")
type tx struct {
Input string `json:"input"`
}
var commitBatch tx
err = json.Unmarshal(data, &commitBatch)
require.NoError(t, err, "Failed to unmarshal transaction json")
testTxData, err := hex.DecodeString(commitBatch.Input[2:])
if err != nil {
t.Fatalf("Failed to decode string: %v", err)
}
ranges, err := service.decodeChunkBlockRanges(testTxData)
if err != nil {
t.Fatalf("Failed to decode chunk ranges: %v", err)
}
expectedRanges := []*rawdb.ChunkBlockRange{
{StartBlockNumber: 1690, EndBlockNumber: 1780},
{StartBlockNumber: 1781, EndBlockNumber: 1871},
{StartBlockNumber: 1872, EndBlockNumber: 1962},
{StartBlockNumber: 1963, EndBlockNumber: 2053},
{StartBlockNumber: 2054, EndBlockNumber: 2144},
{StartBlockNumber: 2145, EndBlockNumber: 2235},
{StartBlockNumber: 2236, EndBlockNumber: 2326},
{StartBlockNumber: 2327, EndBlockNumber: 2417},
{StartBlockNumber: 2418, EndBlockNumber: 2508},
}
if len(expectedRanges) != len(ranges) {
t.Fatalf("Expected range length %v, got %v", len(expectedRanges), len(ranges))
}
for i := range ranges {
if *expectedRanges[i] != *ranges[i] {
t.Errorf("Mismatch at index %d: expected %v, got %v", i, *expectedRanges[i], *ranges[i])
}
}
}
func TestGetChunkRangesCodecv0(t *testing.T) {
genesisConfig := &params.ChainConfig{ genesisConfig := &params.ChainConfig{
Scroll: params.ScrollConfig{ Scroll: params.ScrollConfig{
L1Config: &params.L1Config{ L1Config: &params.L1Config{
@ -110,7 +170,7 @@ func TestGetChunkRanges(t *testing.T) {
} }
db := rawdb.NewDatabase(memorydb.New()) db := rawdb.NewDatabase(memorydb.New())
rlpData, err := os.ReadFile("./testdata/commit_batch_tx.rlp") rlpData, err := os.ReadFile("./testdata/commitBatch_codecv0.rlp")
if err != nil { if err != nil {
t.Fatalf("Failed to read RLP data: %v", err) t.Fatalf("Failed to read RLP data: %v", err)
} }
@ -151,45 +211,80 @@ func TestGetChunkRanges(t *testing.T) {
} }
} }
func TestValidateBatch(t *testing.T) { func TestGetChunkRangesCodecv1(t *testing.T) {
templateBlockTrace1, err := os.ReadFile("./testdata/blockTrace_02.json") genesisConfig := &params.ChainConfig{
require.NoError(t, err) Scroll: params.ScrollConfig{
wrappedBlock1 := &WrappedBlock{} L1Config: &params.L1Config{
err = json.Unmarshal(templateBlockTrace1, wrappedBlock1) L1ChainId: 11155111,
require.NoError(t, err) ScrollChainAddress: common.HexToAddress("0x2D567EcE699Eabe5afCd141eDB7A4f2D0D6ce8a0"),
chunk1 := &Chunk{Blocks: []*WrappedBlock{wrappedBlock1}} },
},
}
db := rawdb.NewDatabase(memorydb.New())
templateBlockTrace2, err := os.ReadFile("./testdata/blockTrace_03.json") rlpData, err := os.ReadFile("./testdata/commitBatch_codecv1.rlp")
require.NoError(t, err) if err != nil {
wrappedBlock2 := &WrappedBlock{} t.Fatalf("Failed to read RLP data: %v", err)
err = json.Unmarshal(templateBlockTrace2, wrappedBlock2) }
require.NoError(t, err) l1Client := &mockEthClient{
chunk2 := &Chunk{Blocks: []*WrappedBlock{wrappedBlock2}} commitBatchRLP: rlpData,
}
bc := &core.BlockChain{}
stack, err := node.New(&node.DefaultConfig)
if err != nil {
t.Fatalf("Failed to new P2P node: %v", err)
}
defer stack.Close()
service, err := NewRollupSyncService(context.Background(), genesisConfig, db, l1Client, bc, stack)
if err != nil {
t.Fatalf("Failed to new rollup sync service: %v", err)
}
templateBlockTrace3, err := os.ReadFile("./testdata/blockTrace_04.json") vLog := &types.Log{
TxHash: common.HexToHash("0x1"),
}
ranges, err := service.getChunkRanges(1, vLog)
require.NoError(t, err) require.NoError(t, err)
wrappedBlock3 := &WrappedBlock{}
err = json.Unmarshal(templateBlockTrace3, wrappedBlock3) expectedRanges := []*rawdb.ChunkBlockRange{
require.NoError(t, err) {StartBlockNumber: 1, EndBlockNumber: 11},
chunk3 := &Chunk{Blocks: []*WrappedBlock{wrappedBlock3}} }
if len(expectedRanges) != len(ranges) {
t.Fatalf("Expected range length %v, got %v", len(expectedRanges), len(ranges))
}
for i := range ranges {
if *expectedRanges[i] != *ranges[i] {
t.Fatalf("Mismatch at index %d: expected %v, got %v", i, *expectedRanges[i], *ranges[i])
}
}
}
func TestValidateBatchCodecv0(t *testing.T) {
block1 := readBlockFromJSON(t, "./testdata/blockTrace_02.json")
chunk1 := &encoding.Chunk{Blocks: []*encoding.Block{block1}}
block2 := readBlockFromJSON(t, "./testdata/blockTrace_03.json")
chunk2 := &encoding.Chunk{Blocks: []*encoding.Block{block2}}
block3 := readBlockFromJSON(t, "./testdata/blockTrace_04.json")
chunk3 := &encoding.Chunk{Blocks: []*encoding.Block{block3}}
parentBatchMeta1 := &rawdb.FinalizedBatchMeta{} parentBatchMeta1 := &rawdb.FinalizedBatchMeta{}
event1 := &L1FinalizeBatchEvent{ event1 := &L1FinalizeBatchEvent{
BatchIndex: big.NewInt(0), BatchIndex: big.NewInt(0),
BatchHash: common.HexToHash("0xd0f52bc254646e639bf24cc34606319a111975b2fdc431b1381eb6199bc09790"), BatchHash: common.HexToHash("0xfd3ecf106ce993adc6db68e42ce701bfe638434395abdeeb871f7bd395ae2368"),
StateRoot: chunk3.Blocks[len(chunk3.Blocks)-1].Header.Root, StateRoot: chunk3.Blocks[len(chunk3.Blocks)-1].Header.Root,
WithdrawRoot: chunk3.Blocks[len(chunk3.Blocks)-1].WithdrawRoot, WithdrawRoot: chunk3.Blocks[len(chunk3.Blocks)-1].WithdrawRoot,
} }
endBlock1, finalizedBatchMeta1, err := validateBatch(event1, parentBatchMeta1, []*Chunk{chunk1, chunk2, chunk3}, nil)
endBlock1, finalizedBatchMeta1, err := validateBatch(event1, parentBatchMeta1, []*encoding.Chunk{chunk1, chunk2, chunk3}, &params.ChainConfig{}, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, uint64(13), endBlock1) assert.Equal(t, uint64(13), endBlock1)
templateBlockTrace4, err := os.ReadFile("./testdata/blockTrace_05.json") block4 := readBlockFromJSON(t, "./testdata/blockTrace_05.json")
require.NoError(t, err) chunk4 := &encoding.Chunk{Blocks: []*encoding.Block{block4}}
wrappedBlock4 := &WrappedBlock{}
err = json.Unmarshal(templateBlockTrace4, wrappedBlock4)
require.NoError(t, err)
chunk4 := &Chunk{Blocks: []*WrappedBlock{wrappedBlock4}}
parentBatchMeta2 := &rawdb.FinalizedBatchMeta{ parentBatchMeta2 := &rawdb.FinalizedBatchMeta{
BatchHash: event1.BatchHash, BatchHash: event1.BatchHash,
@ -200,11 +295,11 @@ func TestValidateBatch(t *testing.T) {
assert.Equal(t, parentBatchMeta2, finalizedBatchMeta1) assert.Equal(t, parentBatchMeta2, finalizedBatchMeta1)
event2 := &L1FinalizeBatchEvent{ event2 := &L1FinalizeBatchEvent{
BatchIndex: big.NewInt(1), BatchIndex: big.NewInt(1),
BatchHash: common.HexToHash("0xfb77bf8f3bf449126ebbf403fdccfcf78636e34d72d62eed8da0e8c9fd38fa63"), BatchHash: common.HexToHash("0xadb8e526c3fdc2045614158300789cd66e7a945efe5a484db00b5ef9a26016d7"),
StateRoot: chunk4.Blocks[len(chunk4.Blocks)-1].Header.Root, StateRoot: chunk4.Blocks[len(chunk4.Blocks)-1].Header.Root,
WithdrawRoot: chunk4.Blocks[len(chunk4.Blocks)-1].WithdrawRoot, WithdrawRoot: chunk4.Blocks[len(chunk4.Blocks)-1].WithdrawRoot,
} }
endBlock2, finalizedBatchMeta2, err := validateBatch(event2, parentBatchMeta2, []*Chunk{chunk4}, nil) endBlock2, finalizedBatchMeta2, err := validateBatch(event2, parentBatchMeta2, []*encoding.Chunk{chunk4}, &params.ChainConfig{}, nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, uint64(17), endBlock2) assert.Equal(t, uint64(17), endBlock2)
@ -216,3 +311,114 @@ func TestValidateBatch(t *testing.T) {
} }
assert.Equal(t, parentBatchMeta3, finalizedBatchMeta2) assert.Equal(t, parentBatchMeta3, finalizedBatchMeta2)
} }
func TestValidateBatchCodecv1(t *testing.T) {
block1 := readBlockFromJSON(t, "./testdata/blockTrace_02.json")
chunk1 := &encoding.Chunk{Blocks: []*encoding.Block{block1}}
block2 := readBlockFromJSON(t, "./testdata/blockTrace_03.json")
chunk2 := &encoding.Chunk{Blocks: []*encoding.Block{block2}}
block3 := readBlockFromJSON(t, "./testdata/blockTrace_04.json")
chunk3 := &encoding.Chunk{Blocks: []*encoding.Block{block3}}
parentBatchMeta1 := &rawdb.FinalizedBatchMeta{}
event1 := &L1FinalizeBatchEvent{
BatchIndex: big.NewInt(0),
BatchHash: common.HexToHash("0x73cb3310646716cb782702a0ec4ad33cf55633c85daf96b641953c5defe58031"),
StateRoot: chunk3.Blocks[len(chunk3.Blocks)-1].Header.Root,
WithdrawRoot: chunk3.Blocks[len(chunk3.Blocks)-1].WithdrawRoot,
}
endBlock1, finalizedBatchMeta1, err := validateBatch(event1, parentBatchMeta1, []*encoding.Chunk{chunk1, chunk2, chunk3}, &params.ChainConfig{BernoulliBlock: big.NewInt(0)}, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(13), endBlock1)
block4 := readBlockFromJSON(t, "./testdata/blockTrace_05.json")
chunk4 := &encoding.Chunk{Blocks: []*encoding.Block{block4}}
parentBatchMeta2 := &rawdb.FinalizedBatchMeta{
BatchHash: event1.BatchHash,
TotalL1MessagePopped: 11,
StateRoot: chunk3.Blocks[len(chunk3.Blocks)-1].Header.Root,
WithdrawRoot: chunk3.Blocks[len(chunk3.Blocks)-1].WithdrawRoot,
}
assert.Equal(t, parentBatchMeta2, finalizedBatchMeta1)
event2 := &L1FinalizeBatchEvent{
BatchIndex: big.NewInt(1),
BatchHash: common.HexToHash("0x8eb3f63fbf286bb51a49879bfc653c53c890621542c640e5b6163cffb5a47aa6"),
StateRoot: chunk4.Blocks[len(chunk4.Blocks)-1].Header.Root,
WithdrawRoot: chunk4.Blocks[len(chunk4.Blocks)-1].WithdrawRoot,
}
endBlock2, finalizedBatchMeta2, err := validateBatch(event2, parentBatchMeta2, []*encoding.Chunk{chunk4}, &params.ChainConfig{BernoulliBlock: big.NewInt(0)}, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(17), endBlock2)
parentBatchMeta3 := &rawdb.FinalizedBatchMeta{
BatchHash: event2.BatchHash,
TotalL1MessagePopped: 42,
StateRoot: chunk4.Blocks[len(chunk4.Blocks)-1].Header.Root,
WithdrawRoot: chunk4.Blocks[len(chunk4.Blocks)-1].WithdrawRoot,
}
assert.Equal(t, parentBatchMeta3, finalizedBatchMeta2)
}
func TestValidateBatchFromCodecv0ToCodecv1(t *testing.T) {
block1 := readBlockFromJSON(t, "./testdata/blockTrace_02.json")
chunk1 := &encoding.Chunk{Blocks: []*encoding.Block{block1}}
block2 := readBlockFromJSON(t, "./testdata/blockTrace_03.json")
chunk2 := &encoding.Chunk{Blocks: []*encoding.Block{block2}}
block3 := readBlockFromJSON(t, "./testdata/blockTrace_04.json")
chunk3 := &encoding.Chunk{Blocks: []*encoding.Block{block3}}
parentBatchMeta1 := &rawdb.FinalizedBatchMeta{}
event1 := &L1FinalizeBatchEvent{
BatchIndex: big.NewInt(0),
BatchHash: common.HexToHash("0xfd3ecf106ce993adc6db68e42ce701bfe638434395abdeeb871f7bd395ae2368"),
StateRoot: chunk3.Blocks[len(chunk3.Blocks)-1].Header.Root,
WithdrawRoot: chunk3.Blocks[len(chunk3.Blocks)-1].WithdrawRoot,
}
endBlock1, finalizedBatchMeta1, err := validateBatch(event1, parentBatchMeta1, []*encoding.Chunk{chunk1, chunk2, chunk3}, &params.ChainConfig{BernoulliBlock: big.NewInt(16)}, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(13), endBlock1)
block4 := readBlockFromJSON(t, "./testdata/blockTrace_05.json")
chunk4 := &encoding.Chunk{Blocks: []*encoding.Block{block4}}
parentBatchMeta2 := &rawdb.FinalizedBatchMeta{
BatchHash: event1.BatchHash,
TotalL1MessagePopped: 11,
StateRoot: chunk3.Blocks[len(chunk3.Blocks)-1].Header.Root,
WithdrawRoot: chunk3.Blocks[len(chunk3.Blocks)-1].WithdrawRoot,
}
assert.Equal(t, parentBatchMeta2, finalizedBatchMeta1)
event2 := &L1FinalizeBatchEvent{
BatchIndex: big.NewInt(1),
BatchHash: common.HexToHash("0x425ab2830087e2642f0407550d65f108ee93533063ef0bfab1263b0b3c8a4c9e"),
StateRoot: chunk4.Blocks[len(chunk4.Blocks)-1].Header.Root,
WithdrawRoot: chunk4.Blocks[len(chunk4.Blocks)-1].WithdrawRoot,
}
endBlock2, finalizedBatchMeta2, err := validateBatch(event2, parentBatchMeta2, []*encoding.Chunk{chunk4}, &params.ChainConfig{BernoulliBlock: big.NewInt(16)}, nil)
assert.NoError(t, err)
assert.Equal(t, uint64(17), endBlock2)
parentBatchMeta3 := &rawdb.FinalizedBatchMeta{
BatchHash: event2.BatchHash,
TotalL1MessagePopped: 42,
StateRoot: chunk4.Blocks[len(chunk4.Blocks)-1].Header.Root,
WithdrawRoot: chunk4.Blocks[len(chunk4.Blocks)-1].WithdrawRoot,
}
assert.Equal(t, parentBatchMeta3, finalizedBatchMeta2)
}
func readBlockFromJSON(t *testing.T, filename string) *encoding.Block {
data, err := os.ReadFile(filename)
assert.NoError(t, err)
block := &encoding.Block{}
assert.NoError(t, json.Unmarshal(data, block))
return block
}

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,64 @@
package encoding
import (
"fmt"
"math/big"
"github.com/scroll-tech/go-ethereum/core/types"
)
// ConstructSkippedBitmap constructs skipped L1 message bitmap of the batch.
func ConstructSkippedBitmap(batchIndex uint64, chunks []*Chunk, totalL1MessagePoppedBefore uint64) ([]byte, uint64, error) {
// skipped L1 message bitmap, an array of 256-bit bitmaps
var skippedBitmap []*big.Int
// the first queue index that belongs to this batch
baseIndex := totalL1MessagePoppedBefore
// the next queue index that we need to process
nextIndex := totalL1MessagePoppedBefore
for chunkID, chunk := range chunks {
for blockID, block := range chunk.Blocks {
for _, tx := range block.Transactions {
if tx.Type != types.L1MessageTxType {
continue
}
currentIndex := tx.Nonce
if currentIndex < nextIndex {
return nil, 0, fmt.Errorf("unexpected batch payload, expected queue index: %d, got: %d. Batch index: %d, chunk index in batch: %d, block index in chunk: %d, block hash: %v, transaction hash: %v", nextIndex, currentIndex, batchIndex, chunkID, blockID, block.Header.Hash(), tx.TxHash)
}
// mark skipped messages
for skippedIndex := nextIndex; skippedIndex < currentIndex; skippedIndex++ {
quo := int((skippedIndex - baseIndex) / 256)
rem := int((skippedIndex - baseIndex) % 256)
for len(skippedBitmap) <= quo {
bitmap := big.NewInt(0)
skippedBitmap = append(skippedBitmap, bitmap)
}
skippedBitmap[quo].SetBit(skippedBitmap[quo], rem, 1)
}
// process included message
quo := int((currentIndex - baseIndex) / 256)
for len(skippedBitmap) <= quo {
bitmap := big.NewInt(0)
skippedBitmap = append(skippedBitmap, bitmap)
}
nextIndex = currentIndex + 1
}
}
}
bitmapBytes := make([]byte, len(skippedBitmap)*32)
for ii, num := range skippedBitmap {
bytes := num.Bytes()
padding := 32 - len(bytes)
copy(bitmapBytes[32*ii+padding:], bytes)
}
return bitmapBytes, nextIndex, nil
}

View file

@ -0,0 +1,480 @@
package codecv0
import (
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"math"
"math/big"
"strings"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/rollup/types/encoding"
)
// CodecV0Version denotes the version of the codec.
const CodecV0Version = 0
// DABlock represents a Data Availability Block.
type DABlock struct {
BlockNumber uint64
Timestamp uint64
BaseFee *big.Int
GasLimit uint64
NumTransactions uint16
NumL1Messages uint16
}
// DAChunk groups consecutive DABlocks with their transactions.
type DAChunk struct {
Blocks []*DABlock
Transactions [][]*types.TransactionData
}
// DABatch contains metadata about a batch of DAChunks.
type DABatch struct {
Version uint8
BatchIndex uint64
L1MessagePopped uint64
TotalL1MessagePopped uint64
DataHash common.Hash
ParentBatchHash common.Hash
SkippedL1MessageBitmap []byte
}
// NewDABlock creates a new DABlock from the given encoding.Block and the total number of L1 messages popped before.
func NewDABlock(block *encoding.Block, totalL1MessagePoppedBefore uint64) (*DABlock, error) {
if !block.Header.Number.IsUint64() {
return nil, errors.New("block number is not uint64")
}
// note: numL1Messages includes skipped messages
numL1Messages := block.NumL1Messages(totalL1MessagePoppedBefore)
if numL1Messages > math.MaxUint16 {
return nil, errors.New("number of L1 messages exceeds max uint16")
}
// note: numTransactions includes skipped messages
numL2Transactions := block.NumL2Transactions()
numTransactions := numL1Messages + numL2Transactions
if numTransactions > math.MaxUint16 {
return nil, errors.New("number of transactions exceeds max uint16")
}
daBlock := DABlock{
BlockNumber: block.Header.Number.Uint64(),
Timestamp: block.Header.Time,
BaseFee: block.Header.BaseFee,
GasLimit: block.Header.GasLimit,
NumTransactions: uint16(numTransactions),
NumL1Messages: uint16(numL1Messages),
}
return &daBlock, nil
}
// Encode serializes the DABlock into a slice of bytes.
func (b *DABlock) Encode() []byte {
bytes := make([]byte, 60)
binary.BigEndian.PutUint64(bytes[0:], b.BlockNumber)
binary.BigEndian.PutUint64(bytes[8:], b.Timestamp)
if b.BaseFee != nil {
binary.BigEndian.PutUint64(bytes[40:], b.BaseFee.Uint64())
}
binary.BigEndian.PutUint64(bytes[48:], b.GasLimit)
binary.BigEndian.PutUint16(bytes[56:], b.NumTransactions)
binary.BigEndian.PutUint16(bytes[58:], b.NumL1Messages)
return bytes
}
// DecodeDABlock takes a byte slice and decodes it into a DABlock.
func DecodeDABlock(bytes []byte) (*DABlock, error) {
if len(bytes) != 60 {
return nil, errors.New("block encoding is not 60 bytes long")
}
block := &DABlock{
BlockNumber: binary.BigEndian.Uint64(bytes[0:8]),
Timestamp: binary.BigEndian.Uint64(bytes[8:16]),
BaseFee: new(big.Int).SetUint64(binary.BigEndian.Uint64(bytes[40:48])),
GasLimit: binary.BigEndian.Uint64(bytes[48:56]),
NumTransactions: binary.BigEndian.Uint16(bytes[56:58]),
NumL1Messages: binary.BigEndian.Uint16(bytes[58:60]),
}
return block, nil
}
// NewDAChunk creates a new DAChunk from the given encoding.Chunk and the total number of L1 messages popped before.
func NewDAChunk(chunk *encoding.Chunk, totalL1MessagePoppedBefore uint64) (*DAChunk, error) {
var blocks []*DABlock
var txs [][]*types.TransactionData
if chunk == nil {
return nil, errors.New("chunk is nil")
}
if len(chunk.Blocks) == 0 {
return nil, errors.New("number of blocks is 0")
}
if len(chunk.Blocks) > 255 {
return nil, errors.New("number of blocks exceeds 1 byte")
}
for _, block := range chunk.Blocks {
b, err := NewDABlock(block, totalL1MessagePoppedBefore)
if err != nil {
return nil, err
}
blocks = append(blocks, b)
totalL1MessagePoppedBefore += block.NumL1Messages(totalL1MessagePoppedBefore)
txs = append(txs, block.Transactions)
}
daChunk := DAChunk{
Blocks: blocks,
Transactions: txs,
}
return &daChunk, nil
}
// Encode serializes the DAChunk into a slice of bytes.
func (c *DAChunk) Encode() ([]byte, error) {
var chunkBytes []byte
chunkBytes = append(chunkBytes, byte(len(c.Blocks)))
var l2TxDataBytes []byte
for _, block := range c.Blocks {
chunkBytes = append(chunkBytes, block.Encode()...)
}
for _, blockTxs := range c.Transactions {
for _, txData := range blockTxs {
if txData.Type == types.L1MessageTxType {
continue
}
var txLen [4]byte
rlpTxData, err := encoding.ConvertTxDataToRLPEncoding(txData)
if err != nil {
return nil, err
}
binary.BigEndian.PutUint32(txLen[:], uint32(len(rlpTxData)))
l2TxDataBytes = append(l2TxDataBytes, txLen[:]...)
l2TxDataBytes = append(l2TxDataBytes, rlpTxData...)
}
}
chunkBytes = append(chunkBytes, l2TxDataBytes...)
return chunkBytes, nil
}
// Hash computes the hash of the DAChunk data.
func (c *DAChunk) Hash() (common.Hash, error) {
chunkBytes, err := c.Encode()
if err != nil {
return common.Hash{}, err
}
if len(chunkBytes) == 0 {
return common.Hash{}, errors.New("chunk data is empty and cannot be processed")
}
numBlocks := chunkBytes[0]
// concatenate block contexts
var dataBytes []byte
for i := 0; i < int(numBlocks); i++ {
// only the first 58 bytes of each BlockContext are needed for the hashing process
dataBytes = append(dataBytes, chunkBytes[1+60*i:60*i+59]...)
}
// concatenate l1 and l2 tx hashes
for _, blockTxs := range c.Transactions {
var l1TxHashes []byte
var l2TxHashes []byte
for _, txData := range blockTxs {
txHash := strings.TrimPrefix(txData.TxHash, "0x")
hashBytes, err := hex.DecodeString(txHash)
if err != nil {
return common.Hash{}, fmt.Errorf("failed to decode tx hash from TransactionData: hash=%v, err=%w", txData.TxHash, err)
}
if txData.Type == types.L1MessageTxType {
l1TxHashes = append(l1TxHashes, hashBytes...)
} else {
l2TxHashes = append(l2TxHashes, hashBytes...)
}
}
dataBytes = append(dataBytes, l1TxHashes...)
dataBytes = append(dataBytes, l2TxHashes...)
}
hash := crypto.Keccak256Hash(dataBytes)
return hash, nil
}
// NewDABatch creates a DABatch from the provided encoding.Batch.
func NewDABatch(batch *encoding.Batch) (*DABatch, error) {
// compute batch data hash
var dataBytes []byte
totalL1MessagePoppedBeforeChunk := batch.TotalL1MessagePoppedBefore
for _, chunk := range batch.Chunks {
// build data hash
daChunk, err := NewDAChunk(chunk, totalL1MessagePoppedBeforeChunk)
if err != nil {
return nil, err
}
totalL1MessagePoppedBeforeChunk += chunk.NumL1Messages(totalL1MessagePoppedBeforeChunk)
daChunkHash, err := daChunk.Hash()
if err != nil {
return nil, err
}
dataBytes = append(dataBytes, daChunkHash.Bytes()...)
}
// compute data hash
dataHash := crypto.Keccak256Hash(dataBytes)
// skipped L1 messages bitmap
bitmapBytes, totalL1MessagePoppedAfter, err := encoding.ConstructSkippedBitmap(batch.Index, batch.Chunks, batch.TotalL1MessagePoppedBefore)
if err != nil {
return nil, err
}
daBatch := DABatch{
Version: CodecV0Version,
BatchIndex: batch.Index,
L1MessagePopped: totalL1MessagePoppedAfter - batch.TotalL1MessagePoppedBefore,
TotalL1MessagePopped: totalL1MessagePoppedAfter,
DataHash: dataHash,
ParentBatchHash: batch.ParentBatchHash,
SkippedL1MessageBitmap: bitmapBytes,
}
return &daBatch, nil
}
// NewDABatchFromBytes attempts to decode the given byte slice into a DABatch.
func NewDABatchFromBytes(data []byte) (*DABatch, error) {
if len(data) < 89 {
return nil, fmt.Errorf("insufficient data for DABatch, expected at least 89 bytes but got %d", len(data))
}
b := &DABatch{
Version: data[0],
BatchIndex: binary.BigEndian.Uint64(data[1:9]),
L1MessagePopped: binary.BigEndian.Uint64(data[9:17]),
TotalL1MessagePopped: binary.BigEndian.Uint64(data[17:25]),
DataHash: common.BytesToHash(data[25:57]),
ParentBatchHash: common.BytesToHash(data[57:89]),
SkippedL1MessageBitmap: data[89:],
}
return b, nil
}
// Encode serializes the DABatch into bytes.
func (b *DABatch) Encode() []byte {
batchBytes := make([]byte, 89+len(b.SkippedL1MessageBitmap))
batchBytes[0] = b.Version
binary.BigEndian.PutUint64(batchBytes[1:], b.BatchIndex)
binary.BigEndian.PutUint64(batchBytes[9:], b.L1MessagePopped)
binary.BigEndian.PutUint64(batchBytes[17:], b.TotalL1MessagePopped)
copy(batchBytes[25:], b.DataHash[:])
copy(batchBytes[57:], b.ParentBatchHash[:])
copy(batchBytes[89:], b.SkippedL1MessageBitmap[:])
return batchBytes
}
// Hash computes the hash of the serialized DABatch.
func (b *DABatch) Hash() common.Hash {
bytes := b.Encode()
return crypto.Keccak256Hash(bytes)
}
// DecodeFromCalldata attempts to decode a DABatch and an array of DAChunks from the provided calldata byte slice.
func DecodeFromCalldata(data []byte) (*DABatch, []*DAChunk, error) {
// TODO: implement this function.
return nil, nil, nil
}
// CalldataNonZeroByteGas is the gas consumption per non zero byte in calldata.
const CalldataNonZeroByteGas = 16
// GetKeccak256Gas calculates the gas cost for computing the keccak256 hash of a given size.
func GetKeccak256Gas(size uint64) uint64 {
return GetMemoryExpansionCost(size) + 30 + 6*((size+31)/32)
}
// GetMemoryExpansionCost calculates the cost of memory expansion for a given memoryByteSize.
func GetMemoryExpansionCost(memoryByteSize uint64) uint64 {
memorySizeWord := (memoryByteSize + 31) / 32
memoryCost := (memorySizeWord*memorySizeWord)/512 + (3 * memorySizeWord)
return memoryCost
}
// EstimateBlockL1CommitCalldataSize calculates the calldata size in l1 commit for this block approximately.
// TODO: The calculation could be more accurate by using 58 + len(l2TxDataBytes) (see Chunk).
// This needs to be adjusted in the future.
func EstimateBlockL1CommitCalldataSize(b *encoding.Block) (uint64, error) {
var size uint64
for _, txData := range b.Transactions {
if txData.Type == types.L1MessageTxType {
continue
}
size += 4 // 4 bytes payload length
txPayloadLength, err := getTxPayloadLength(txData)
if err != nil {
return 0, err
}
size += txPayloadLength
}
size += 60 // 60 bytes BlockContext
return size, nil
}
// EstimateBlockL1CommitGas calculates the total L1 commit gas for this block approximately.
func EstimateBlockL1CommitGas(b *encoding.Block) (uint64, error) {
var total uint64
var numL1Messages uint64
for _, txData := range b.Transactions {
if txData.Type == types.L1MessageTxType {
numL1Messages++
continue
}
txPayloadLength, err := getTxPayloadLength(txData)
if err != nil {
return 0, err
}
total += CalldataNonZeroByteGas * txPayloadLength // an over-estimate: treat each byte as non-zero
total += CalldataNonZeroByteGas * 4 // 4 bytes payload length
total += GetKeccak256Gas(txPayloadLength) // l2 tx hash
}
// 60 bytes BlockContext calldata
total += CalldataNonZeroByteGas * 60
// sload
total += 2100 * numL1Messages // numL1Messages times cold sload in L1MessageQueue
// staticcall
total += 100 * numL1Messages // numL1Messages times call to L1MessageQueue
total += 100 * numL1Messages // numL1Messages times warm address access to L1MessageQueue
total += GetMemoryExpansionCost(36) * numL1Messages // staticcall to proxy
total += 100 * numL1Messages // read admin in proxy
total += 100 * numL1Messages // read impl in proxy
total += 100 * numL1Messages // access impl
total += GetMemoryExpansionCost(36) * numL1Messages // delegatecall to impl
return total, nil
}
// EstimateChunkL1CommitCalldataSize calculates the calldata size needed for committing a chunk to L1 approximately.
func EstimateChunkL1CommitCalldataSize(c *encoding.Chunk) (uint64, error) {
var totalL1CommitCalldataSize uint64
for _, block := range c.Blocks {
blockL1CommitCalldataSize, err := EstimateBlockL1CommitCalldataSize(block)
if err != nil {
return 0, err
}
totalL1CommitCalldataSize += blockL1CommitCalldataSize
}
return totalL1CommitCalldataSize, nil
}
// EstimateChunkL1CommitGas calculates the total L1 commit gas for this chunk approximately.
func EstimateChunkL1CommitGas(c *encoding.Chunk) (uint64, error) {
var totalTxNum uint64
var totalL1CommitGas uint64
for _, block := range c.Blocks {
totalTxNum += uint64(len(block.Transactions))
blockL1CommitGas, err := EstimateBlockL1CommitGas(block)
if err != nil {
return 0, err
}
totalL1CommitGas += blockL1CommitGas
}
numBlocks := uint64(len(c.Blocks))
totalL1CommitGas += 100 * numBlocks // numBlocks times warm sload
totalL1CommitGas += CalldataNonZeroByteGas // numBlocks field of chunk encoding in calldata
totalL1CommitGas += CalldataNonZeroByteGas * numBlocks * 60 // numBlocks of BlockContext in chunk
totalL1CommitGas += GetKeccak256Gas(58*numBlocks + 32*totalTxNum) // chunk hash
return totalL1CommitGas, nil
}
// EstimateBatchL1CommitGas calculates the total L1 commit gas for this batch approximately.
func EstimateBatchL1CommitGas(b *encoding.Batch) (uint64, error) {
var totalL1CommitGas uint64
// Add extra gas costs
totalL1CommitGas += 100000 // constant to account for ops like _getAdmin, _implementation, _requireNotPaused, etc
totalL1CommitGas += 4 * 2100 // 4 one-time cold sload for commitBatch
totalL1CommitGas += 20000 // 1 time sstore
totalL1CommitGas += 21000 // base fee for tx
totalL1CommitGas += CalldataNonZeroByteGas // version in calldata
// adjusting gas:
// add 1 time cold sload (2100 gas) for L1MessageQueue
// add 1 time cold address access (2600 gas) for L1MessageQueue
// minus 1 time warm sload (100 gas) & 1 time warm address access (100 gas)
totalL1CommitGas += (2100 + 2600 - 100 - 100)
totalL1CommitGas += GetKeccak256Gas(89 + 32) // parent batch header hash, length is estimated as 89 (constant part)+ 32 (1 skippedL1MessageBitmap)
totalL1CommitGas += CalldataNonZeroByteGas * (89 + 32) // parent batch header in calldata
// adjust batch data hash gas cost
totalL1CommitGas += GetKeccak256Gas(uint64(32 * len(b.Chunks)))
totalL1MessagePoppedBefore := b.TotalL1MessagePoppedBefore
for _, chunk := range b.Chunks {
chunkL1CommitGas, err := EstimateChunkL1CommitGas(chunk)
if err != nil {
return 0, err
}
totalL1CommitGas += chunkL1CommitGas
totalL1MessagePoppedInChunk := chunk.NumL1Messages(totalL1MessagePoppedBefore)
totalL1MessagePoppedBefore += totalL1MessagePoppedInChunk
totalL1CommitGas += CalldataNonZeroByteGas * (32 * (totalL1MessagePoppedInChunk + 255) / 256)
totalL1CommitGas += GetKeccak256Gas(89 + 32*(totalL1MessagePoppedInChunk+255)/256)
totalL1CommitCalldataSize, err := EstimateChunkL1CommitCalldataSize(chunk)
if err != nil {
return 0, err
}
totalL1CommitGas += GetMemoryExpansionCost(totalL1CommitCalldataSize)
}
return totalL1CommitGas, nil
}
// EstimateBatchL1CommitCalldataSize calculates the calldata size in l1 commit for this batch approximately.
func EstimateBatchL1CommitCalldataSize(b *encoding.Batch) (uint64, error) {
var totalL1CommitCalldataSize uint64
for _, chunk := range b.Chunks {
chunkL1CommitCalldataSize, err := EstimateChunkL1CommitCalldataSize(chunk)
if err != nil {
return 0, err
}
totalL1CommitCalldataSize += chunkL1CommitCalldataSize
}
return totalL1CommitCalldataSize, nil
}
func getTxPayloadLength(txData *types.TransactionData) (uint64, error) {
rlpTxData, err := encoding.ConvertTxDataToRLPEncoding(txData)
if err != nil {
return 0, err
}
return uint64(len(rlpTxData)), nil
}

View file

@ -0,0 +1,522 @@
package codecv1
import (
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"math"
"math/big"
"strings"
"github.com/scroll-tech/go-ethereum/accounts/abi"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/crypto"
"github.com/scroll-tech/go-ethereum/crypto/kzg4844"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/rollup/types/encoding"
)
var (
// BLSModulus is the BLS modulus defined in EIP-4844.
BLSModulus *big.Int
// BlobDataProofArgs defines the argument types for `_blobDataProof` in `finalizeBatchWithProof4844`.
BlobDataProofArgs abi.Arguments
// MaxNumChunks is the maximum number of chunks that a batch can contain.
MaxNumChunks int = 15
)
func init() {
// initialize modulus
modulus, success := new(big.Int).SetString("52435875175126190479447740508185965837690552500527637822603658699938581184513", 10)
if !success {
log.Crit("BLSModulus conversion failed")
}
BLSModulus = modulus
// initialize arguments
bytes32Type, err1 := abi.NewType("bytes32", "bytes32", nil)
bytes48Type, err2 := abi.NewType("bytes48", "bytes48", nil)
if err1 != nil || err2 != nil {
log.Crit("Failed to initialize abi types", "err1", err1, "err2", err2)
}
BlobDataProofArgs = abi.Arguments{
{Type: bytes32Type, Name: "z"},
{Type: bytes32Type, Name: "y"},
{Type: bytes48Type, Name: "commitment"},
{Type: bytes48Type, Name: "proof"},
}
}
// CodecV1Version denotes the version of the codec.
const CodecV1Version = 1
// DABlock represents a Data Availability Block.
type DABlock struct {
BlockNumber uint64
Timestamp uint64
BaseFee *big.Int
GasLimit uint64
NumTransactions uint16
NumL1Messages uint16
}
// DAChunk groups consecutive DABlocks with their transactions.
type DAChunk struct {
Blocks []*DABlock
Transactions [][]*types.TransactionData
}
// DABatch contains metadata about a batch of DAChunks.
type DABatch struct {
// header
Version uint8
BatchIndex uint64
L1MessagePopped uint64
TotalL1MessagePopped uint64
DataHash common.Hash
BlobVersionedHash common.Hash
ParentBatchHash common.Hash
SkippedL1MessageBitmap []byte
// blob payload
blob *kzg4844.Blob
z *kzg4844.Point
}
// NewDABlock creates a new DABlock from the given encoding.Block and the total number of L1 messages popped before.
func NewDABlock(block *encoding.Block, totalL1MessagePoppedBefore uint64) (*DABlock, error) {
if !block.Header.Number.IsUint64() {
return nil, errors.New("block number is not uint64")
}
// note: numL1Messages includes skipped messages
numL1Messages := block.NumL1Messages(totalL1MessagePoppedBefore)
if numL1Messages > math.MaxUint16 {
return nil, errors.New("number of L1 messages exceeds max uint16")
}
// note: numTransactions includes skipped messages
numL2Transactions := block.NumL2Transactions()
numTransactions := numL1Messages + numL2Transactions
if numTransactions > math.MaxUint16 {
return nil, errors.New("number of transactions exceeds max uint16")
}
daBlock := DABlock{
BlockNumber: block.Header.Number.Uint64(),
Timestamp: block.Header.Time,
BaseFee: block.Header.BaseFee,
GasLimit: block.Header.GasLimit,
NumTransactions: uint16(numTransactions),
NumL1Messages: uint16(numL1Messages),
}
return &daBlock, nil
}
// Encode serializes the DABlock into a slice of bytes.
func (b *DABlock) Encode() []byte {
bytes := make([]byte, 60)
binary.BigEndian.PutUint64(bytes[0:], b.BlockNumber)
binary.BigEndian.PutUint64(bytes[8:], b.Timestamp)
if b.BaseFee != nil {
binary.BigEndian.PutUint64(bytes[40:], b.BaseFee.Uint64())
}
binary.BigEndian.PutUint64(bytes[48:], b.GasLimit)
binary.BigEndian.PutUint16(bytes[56:], b.NumTransactions)
binary.BigEndian.PutUint16(bytes[58:], b.NumL1Messages)
return bytes
}
// DecodeDABlock takes a byte slice and decodes it into a DABlock.
func DecodeDABlock(bytes []byte) (*DABlock, error) {
if len(bytes) != 60 {
return nil, errors.New("block encoding is not 60 bytes long")
}
block := &DABlock{
BlockNumber: binary.BigEndian.Uint64(bytes[0:8]),
Timestamp: binary.BigEndian.Uint64(bytes[8:16]),
BaseFee: new(big.Int).SetUint64(binary.BigEndian.Uint64(bytes[40:48])),
GasLimit: binary.BigEndian.Uint64(bytes[48:56]),
NumTransactions: binary.BigEndian.Uint16(bytes[56:58]),
NumL1Messages: binary.BigEndian.Uint16(bytes[58:60]),
}
return block, nil
}
// NewDAChunk creates a new DAChunk from the given encoding.Chunk and the total number of L1 messages popped before.
func NewDAChunk(chunk *encoding.Chunk, totalL1MessagePoppedBefore uint64) (*DAChunk, error) {
var blocks []*DABlock
var txs [][]*types.TransactionData
for _, block := range chunk.Blocks {
b, err := NewDABlock(block, totalL1MessagePoppedBefore)
if err != nil {
return nil, err
}
blocks = append(blocks, b)
totalL1MessagePoppedBefore += block.NumL1Messages(totalL1MessagePoppedBefore)
txs = append(txs, block.Transactions)
}
daChunk := DAChunk{
Blocks: blocks,
Transactions: txs,
}
return &daChunk, nil
}
// Encode serializes the DAChunk into a slice of bytes.
func (c *DAChunk) Encode() []byte {
var chunkBytes []byte
chunkBytes = append(chunkBytes, byte(len(c.Blocks)))
for _, block := range c.Blocks {
blockBytes := block.Encode()
chunkBytes = append(chunkBytes, blockBytes...)
}
return chunkBytes
}
// Hash computes the hash of the DAChunk data.
func (c *DAChunk) Hash() (common.Hash, error) {
var dataBytes []byte
// concatenate block contexts
for _, block := range c.Blocks {
encodedBlock := block.Encode()
// only the first 58 bytes are used in the hashing process
dataBytes = append(dataBytes, encodedBlock[:58]...)
}
// concatenate l1 tx hashes
for _, blockTxs := range c.Transactions {
for _, txData := range blockTxs {
if txData.Type == types.L1MessageTxType {
txHash := strings.TrimPrefix(txData.TxHash, "0x")
hashBytes, err := hex.DecodeString(txHash)
if err != nil {
return common.Hash{}, err
}
if len(hashBytes) != 32 {
return common.Hash{}, fmt.Errorf("unexpected hash: %s", txData.TxHash)
}
dataBytes = append(dataBytes, hashBytes...)
}
}
}
hash := crypto.Keccak256Hash(dataBytes)
return hash, nil
}
// NewDABatch creates a DABatch from the provided encoding.Batch.
func NewDABatch(batch *encoding.Batch) (*DABatch, error) {
// this encoding can only support a fixed number of chunks per batch
if len(batch.Chunks) > MaxNumChunks {
return nil, fmt.Errorf("too many chunks in batch")
}
if len(batch.Chunks) == 0 {
return nil, fmt.Errorf("too few chunks in batch")
}
// batch data hash
dataHash, err := computeBatchDataHash(batch.Chunks, batch.TotalL1MessagePoppedBefore)
if err != nil {
return nil, err
}
// skipped L1 messages bitmap
bitmapBytes, totalL1MessagePoppedAfter, err := encoding.ConstructSkippedBitmap(batch.Index, batch.Chunks, batch.TotalL1MessagePoppedBefore)
if err != nil {
return nil, err
}
// blob payload
blob, z, err := constructBlobPayload(batch.Chunks)
if err != nil {
return nil, err
}
// blob versioned hash
c, err := kzg4844.BlobToCommitment(*blob)
if err != nil {
return nil, fmt.Errorf("failed to create blob commitment")
}
blobVersionedHash := kzg4844.CalcBlobHashV1(sha256.New(), &c)
daBatch := DABatch{
Version: CodecV1Version,
BatchIndex: batch.Index,
L1MessagePopped: totalL1MessagePoppedAfter - batch.TotalL1MessagePoppedBefore,
TotalL1MessagePopped: totalL1MessagePoppedAfter,
DataHash: dataHash,
BlobVersionedHash: blobVersionedHash,
ParentBatchHash: batch.ParentBatchHash,
SkippedL1MessageBitmap: bitmapBytes,
blob: blob,
z: z,
}
return &daBatch, nil
}
// computeBatchDataHash computes the data hash of the batch.
// Note: The batch hash and batch data hash are two different hashes,
// the former is used for identifying a badge in the contracts,
// the latter is used in the public input to the provers.
func computeBatchDataHash(chunks []*encoding.Chunk, totalL1MessagePoppedBefore uint64) (common.Hash, error) {
var dataBytes []byte
totalL1MessagePoppedBeforeChunk := totalL1MessagePoppedBefore
for _, chunk := range chunks {
daChunk, err := NewDAChunk(chunk, totalL1MessagePoppedBeforeChunk)
if err != nil {
return common.Hash{}, err
}
totalL1MessagePoppedBeforeChunk += chunk.NumL1Messages(totalL1MessagePoppedBeforeChunk)
chunkHash, err := daChunk.Hash()
if err != nil {
return common.Hash{}, err
}
dataBytes = append(dataBytes, chunkHash.Bytes()...)
}
dataHash := crypto.Keccak256Hash(dataBytes)
return dataHash, nil
}
// constructBlobPayload constructs the 4844 blob payload.
func constructBlobPayload(chunks []*encoding.Chunk) (*kzg4844.Blob, *kzg4844.Point, error) {
// metadata consists of num_chunks (2 bytes) and chunki_size (4 bytes per chunk)
metadataLength := 2 + MaxNumChunks*4
// the raw (un-padded) blob payload
blobBytes := make([]byte, metadataLength)
// challenge digest preimage
// 1 hash for metadata and 1 for each chunk
challengePreimage := make([]byte, (1+MaxNumChunks)*32)
// the chunk data hash used for calculating the challenge preimage
var chunkDataHash common.Hash
// blob metadata: num_chunks
binary.BigEndian.PutUint16(blobBytes[0:], uint16(len(chunks)))
// encode blob metadata and L2 transactions,
// and simultaneously also build challenge preimage
for chunkID, chunk := range chunks {
currentChunkStartIndex := len(blobBytes)
for _, block := range chunk.Blocks {
for _, tx := range block.Transactions {
if tx.Type != types.L1MessageTxType {
// encode L2 txs into blob payload
rlpTxData, err := encoding.ConvertTxDataToRLPEncoding(tx)
if err != nil {
return nil, nil, err
}
blobBytes = append(blobBytes, rlpTxData...)
}
}
}
// blob metadata: chunki_size
if chunkSize := len(blobBytes) - currentChunkStartIndex; chunkSize != 0 {
binary.BigEndian.PutUint32(blobBytes[2+4*chunkID:], uint32(chunkSize))
}
// challenge: compute chunk data hash
chunkDataHash = crypto.Keccak256Hash(blobBytes[currentChunkStartIndex:])
copy(challengePreimage[32+chunkID*32:], chunkDataHash[:])
}
// if we have fewer than MaxNumChunks chunks, the rest
// of the blob metadata is correctly initialized to 0,
// but we need to add padding to the challenge preimage
for chunkID := len(chunks); chunkID < MaxNumChunks; chunkID++ {
// use the last chunk's data hash as padding
copy(challengePreimage[32+chunkID*32:], chunkDataHash[:])
}
// challenge: compute metadata hash
hash := crypto.Keccak256Hash(blobBytes[0:metadataLength])
copy(challengePreimage[0:], hash[:])
// convert raw data to BLSFieldElements
blob, err := makeBlobCanonical(blobBytes)
if err != nil {
return nil, nil, err
}
// compute z = challenge_digest % BLS_MODULUS
challengeDigest := crypto.Keccak256Hash(challengePreimage)
pointBigInt := new(big.Int).Mod(new(big.Int).SetBytes(challengeDigest[:]), BLSModulus)
pointBytes := pointBigInt.Bytes()
// the challenge point z
var z kzg4844.Point
start := 32 - len(pointBytes)
copy(z[start:], pointBytes)
return blob, &z, nil
}
// makeBlobCanonical converts the raw blob data into the canonical blob representation of 4096 BLSFieldElements.
func makeBlobCanonical(blobBytes []byte) (*kzg4844.Blob, error) {
// blob contains 131072 bytes but we can only utilize 31/32 of these
if len(blobBytes) > 126976 {
return nil, fmt.Errorf("oversized batch payload")
}
// the canonical (padded) blob payload
var blob kzg4844.Blob
// encode blob payload by prepending every 31 bytes with 1 zero byte
index := 0
for from := 0; from < len(blobBytes); from += 31 {
to := from + 31
if to > len(blobBytes) {
to = len(blobBytes)
}
copy(blob[index+1:], blobBytes[from:to])
index += 32
}
return &blob, nil
}
// NewDABatchFromBytes attempts to decode the given byte slice into a DABatch.
// Note: This function only populates the batch header, it leaves the blob-related fields empty.
func NewDABatchFromBytes(data []byte) (*DABatch, error) {
if len(data) < 121 {
return nil, fmt.Errorf("insufficient data for DABatch, expected at least 121 bytes but got %d", len(data))
}
b := &DABatch{
Version: data[0],
BatchIndex: binary.BigEndian.Uint64(data[1:9]),
L1MessagePopped: binary.BigEndian.Uint64(data[9:17]),
TotalL1MessagePopped: binary.BigEndian.Uint64(data[17:25]),
DataHash: common.BytesToHash(data[25:57]),
BlobVersionedHash: common.BytesToHash(data[57:89]),
ParentBatchHash: common.BytesToHash(data[89:121]),
SkippedL1MessageBitmap: data[121:],
}
return b, nil
}
// Encode serializes the DABatch into bytes.
func (b *DABatch) Encode() []byte {
batchBytes := make([]byte, 121+len(b.SkippedL1MessageBitmap))
batchBytes[0] = b.Version
binary.BigEndian.PutUint64(batchBytes[1:], b.BatchIndex)
binary.BigEndian.PutUint64(batchBytes[9:], b.L1MessagePopped)
binary.BigEndian.PutUint64(batchBytes[17:], b.TotalL1MessagePopped)
copy(batchBytes[25:], b.DataHash[:])
copy(batchBytes[57:], b.BlobVersionedHash[:])
copy(batchBytes[89:], b.ParentBatchHash[:])
copy(batchBytes[121:], b.SkippedL1MessageBitmap[:])
return batchBytes
}
// Hash computes the hash of the serialized DABatch.
func (b *DABatch) Hash() common.Hash {
bytes := b.Encode()
return crypto.Keccak256Hash(bytes)
}
// BlobDataProof computes the abi-encoded blob verification data.
func (b *DABatch) BlobDataProof() ([]byte, error) {
if b.blob == nil {
return nil, errors.New("called BlobDataProof with empty blob")
}
if b.z == nil {
return nil, errors.New("called BlobDataProof with empty z")
}
commitment, err := kzg4844.BlobToCommitment(*b.blob)
if err != nil {
return nil, fmt.Errorf("failed to create blob commitment")
}
proof, y, err := kzg4844.ComputeProof(*b.blob, *b.z)
if err != nil {
log.Crit("failed to create KZG proof at point", "err", err, "z", hex.EncodeToString(b.z[:]))
}
// Memory layout of ``_blobDataProof``:
// | z | y | kzg_commitment | kzg_proof |
// |---------|---------|----------------|-----------|
// | bytes32 | bytes32 | bytes48 | bytes48 |
values := []interface{}{*b.z, y, commitment, proof}
return BlobDataProofArgs.Pack(values...)
}
// Blob returns the blob of the batch.
func (b *DABatch) Blob() *kzg4844.Blob {
return b.blob
}
// DecodeFromCalldata attempts to decode a DABatch and an array of DAChunks from the provided calldata byte slice.
func DecodeFromCalldata(data []byte) (*DABatch, []*DAChunk, error) {
// TODO: implement this function.
return nil, nil, nil
}
// EstimateChunkL1CommitBlobSize estimates the size of the L1 commit blob for a single chunk.
func EstimateChunkL1CommitBlobSize(c *encoding.Chunk) (uint64, error) {
metadataSize := uint64(2 + 4*MaxNumChunks) // over-estimate: adding metadata length
chunkDataSize, err := chunkL1CommitBlobDataSize(c)
if err != nil {
return 0, err
}
paddedSize := ((metadataSize + chunkDataSize + 30) / 31) * 32
return paddedSize, nil
}
// EstimateBatchL1CommitBlobSize estimates the total size of the L1 commit blob for a batch.
func EstimateBatchL1CommitBlobSize(b *encoding.Batch) (uint64, error) {
metadataSize := uint64(2 + 4*MaxNumChunks)
var batchDataSize uint64
for _, c := range b.Chunks {
chunkDataSize, err := chunkL1CommitBlobDataSize(c)
if err != nil {
return 0, err
}
batchDataSize += chunkDataSize
}
paddedSize := ((metadataSize + batchDataSize + 30) / 31) * 32
return paddedSize, nil
}
func chunkL1CommitBlobDataSize(c *encoding.Chunk) (uint64, error) {
var dataSize uint64
for _, block := range c.Blocks {
for _, tx := range block.Transactions {
if tx.Type != types.L1MessageTxType {
rlpTxData, err := encoding.ConvertTxDataToRLPEncoding(tx)
if err != nil {
return 0, err
}
dataSize += uint64(len(rlpTxData))
}
}
}
return dataSize, nil
}

259
rollup/types/encoding/da.go Normal file
View file

@ -0,0 +1,259 @@
package encoding
import (
"fmt"
"github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/common/hexutil"
"github.com/scroll-tech/go-ethereum/core/types"
)
// CodecVersion defines the version of encoder and decoder.
type CodecVersion int
const (
// CodecV0 represents the version 0 of the encoder and decoder.
CodecV0 CodecVersion = iota
// CodecV1 represents the version 1 of the encoder and decoder.
CodecV1
)
// Block represents an L2 block.
type Block struct {
Header *types.Header
Transactions []*types.TransactionData
WithdrawRoot common.Hash `json:"withdraw_trie_root,omitempty"`
RowConsumption *types.RowConsumption `json:"row_consumption,omitempty"`
}
// Chunk represents a group of blocks.
type Chunk struct {
Blocks []*Block `json:"blocks"`
}
// Batch represents a batch of chunks.
type Batch struct {
Index uint64
TotalL1MessagePoppedBefore uint64
ParentBatchHash common.Hash
Chunks []*Chunk
}
// NumL1Messages returns the number of L1 messages in this block.
// This number is the sum of included and skipped L1 messages.
func (b *Block) NumL1Messages(totalL1MessagePoppedBefore uint64) uint64 {
var lastQueueIndex *uint64
for _, txData := range b.Transactions {
if txData.Type == types.L1MessageTxType {
lastQueueIndex = &txData.Nonce
}
}
if lastQueueIndex == nil {
return 0
}
// note: last queue index included before this block is totalL1MessagePoppedBefore - 1
// TODO: cache results
return *lastQueueIndex - totalL1MessagePoppedBefore + 1
}
// NumL2Transactions returns the number of L2 transactions in this block.
func (b *Block) NumL2Transactions() uint64 {
var count uint64
for _, txData := range b.Transactions {
if txData.Type != types.L1MessageTxType {
count++
}
}
return count
}
// NumL1Messages returns the number of L1 messages in this chunk.
// This number is the sum of included and skipped L1 messages.
func (c *Chunk) NumL1Messages(totalL1MessagePoppedBefore uint64) uint64 {
var numL1Messages uint64
for _, block := range c.Blocks {
numL1MessagesInBlock := block.NumL1Messages(totalL1MessagePoppedBefore)
numL1Messages += numL1MessagesInBlock
totalL1MessagePoppedBefore += numL1MessagesInBlock
}
// TODO: cache results
return numL1Messages
}
// ConvertTxDataToRLPEncoding transforms []*TransactionData into []*types.Transaction.
func ConvertTxDataToRLPEncoding(txData *types.TransactionData) ([]byte, error) {
data, err := hexutil.Decode(txData.Data)
if err != nil {
return nil, fmt.Errorf("failed to decode txData.Data: data=%v, err=%w", txData.Data, err)
}
var tx *types.Transaction
switch txData.Type {
case types.LegacyTxType:
tx = types.NewTx(&types.LegacyTx{
Nonce: txData.Nonce,
To: txData.To,
Value: txData.Value.ToInt(),
Gas: txData.Gas,
GasPrice: txData.GasPrice.ToInt(),
Data: data,
V: txData.V.ToInt(),
R: txData.R.ToInt(),
S: txData.S.ToInt(),
})
case types.AccessListTxType:
tx = types.NewTx(&types.AccessListTx{
ChainID: txData.ChainId.ToInt(),
Nonce: txData.Nonce,
To: txData.To,
Value: txData.Value.ToInt(),
Gas: txData.Gas,
GasPrice: txData.GasPrice.ToInt(),
Data: data,
AccessList: txData.AccessList,
V: txData.V.ToInt(),
R: txData.R.ToInt(),
S: txData.S.ToInt(),
})
case types.DynamicFeeTxType:
tx = types.NewTx(&types.DynamicFeeTx{
ChainID: txData.ChainId.ToInt(),
Nonce: txData.Nonce,
To: txData.To,
Value: txData.Value.ToInt(),
Gas: txData.Gas,
GasTipCap: txData.GasTipCap.ToInt(),
GasFeeCap: txData.GasFeeCap.ToInt(),
Data: data,
AccessList: txData.AccessList,
V: txData.V.ToInt(),
R: txData.R.ToInt(),
S: txData.S.ToInt(),
})
case types.L1MessageTxType: // L1MessageTxType is not supported
default:
return nil, fmt.Errorf("unsupported tx type: %d", txData.Type)
}
rlpTxData, err := tx.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("failed to marshal binary of the tx: tx=%v, err=%w", tx, err)
}
return rlpTxData, nil
}
// CrcMax calculates the maximum row consumption of crc.
func (c *Chunk) CrcMax() (uint64, error) {
// Map sub-circuit name to row count
crc := make(map[string]uint64)
// Iterate over blocks, accumulate row consumption
for _, block := range c.Blocks {
if block.RowConsumption == nil {
return 0, fmt.Errorf("block (%d, %v) has nil RowConsumption", block.Header.Number, block.Header.Hash().Hex())
}
for _, subCircuit := range *block.RowConsumption {
crc[subCircuit.Name] += subCircuit.RowNumber
}
}
// Find the maximum row consumption
var maxVal uint64
for _, value := range crc {
if value > maxVal {
maxVal = value
}
}
// Return the maximum row consumption
return maxVal, nil
}
// NumTransactions calculates the total number of transactions in a Chunk.
func (c *Chunk) NumTransactions() uint64 {
var totalTxNum uint64
for _, block := range c.Blocks {
totalTxNum += uint64(len(block.Transactions))
}
return totalTxNum
}
// NumL2Transactions calculates the total number of L2 transactions in a Chunk.
func (c *Chunk) NumL2Transactions() uint64 {
var totalTxNum uint64
for _, block := range c.Blocks {
totalTxNum += block.NumL2Transactions()
}
return totalTxNum
}
// L2GasUsed calculates the total gas of L2 transactions in a Chunk.
func (c *Chunk) L2GasUsed() uint64 {
var totalTxNum uint64
for _, block := range c.Blocks {
totalTxNum += block.Header.GasUsed
}
return totalTxNum
}
// StateRoot gets the state root after committing/finalizing the batch.
func (b *Batch) StateRoot() common.Hash {
numChunks := len(b.Chunks)
if len(b.Chunks) == 0 {
return common.Hash{}
}
lastChunkBlockNum := len(b.Chunks[numChunks-1].Blocks)
return b.Chunks[len(b.Chunks)-1].Blocks[lastChunkBlockNum-1].Header.Root
}
// WithdrawRoot gets the withdraw root after committing/finalizing the batch.
func (b *Batch) WithdrawRoot() common.Hash {
numChunks := len(b.Chunks)
if len(b.Chunks) == 0 {
return common.Hash{}
}
lastChunkBlockNum := len(b.Chunks[numChunks-1].Blocks)
return b.Chunks[len(b.Chunks)-1].Blocks[lastChunkBlockNum-1].WithdrawRoot
}
// TxsToTxsData converts transactions to a TransactionData array.
func TxsToTxsData(txs types.Transactions) []*types.TransactionData {
txsData := make([]*types.TransactionData, len(txs))
for i, tx := range txs {
v, r, s := tx.RawSignatureValues()
nonce := tx.Nonce()
// We need QueueIndex in `NewBatchHeader`. However, `TransactionData`
// does not have this field. Since `L1MessageTx` do not have a nonce,
// we reuse this field for storing the queue index.
if msg := tx.AsL1MessageTx(); msg != nil {
nonce = msg.QueueIndex
}
txsData[i] = &types.TransactionData{
Type: tx.Type(),
TxHash: tx.Hash().String(),
Nonce: nonce,
ChainId: (*hexutil.Big)(tx.ChainId()),
Gas: tx.Gas(),
GasPrice: (*hexutil.Big)(tx.GasPrice()),
GasTipCap: (*hexutil.Big)(tx.GasTipCap()),
GasFeeCap: (*hexutil.Big)(tx.GasFeeCap()),
To: tx.To(),
Value: (*hexutil.Big)(tx.Value()),
Data: hexutil.Encode(tx.Data()),
IsCreate: tx.To() == nil,
AccessList: tx.AccessList(),
V: (*hexutil.Big)(v),
R: (*hexutil.Big)(r),
S: (*hexutil.Big)(s),
}
}
return txsData
}