mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-25 06:06:44 +00:00
feat(rollup-verifier): support codecv1 (#678)
This commit is contained in:
parent
06f4573e49
commit
55b03ff405
17 changed files with 1732 additions and 521 deletions
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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())
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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{
|
||||||
if err != nil {
|
Index: event.BatchIndex.Uint64(),
|
||||||
return 0, nil, fmt.Errorf("failed to construct batch header, batch index: %v, err: %w", event.BatchIndex.Uint64(), err)
|
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 {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 := ¶ms.ChainConfig{
|
genesisConfig := ¶ms.ChainConfig{
|
||||||
Scroll: params.ScrollConfig{
|
Scroll: params.ScrollConfig{
|
||||||
L1Config: ¶ms.L1Config{
|
L1Config: ¶ms.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 := ¶ms.ChainConfig{
|
||||||
require.NoError(t, err)
|
Scroll: params.ScrollConfig{
|
||||||
wrappedBlock1 := &WrappedBlock{}
|
L1Config: ¶ms.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}, ¶ms.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}, ¶ms.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}, ¶ms.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}, ¶ms.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}, ¶ms.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}, ¶ms.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
|
||||||
|
}
|
||||||
|
|
|
||||||
BIN
rollup/rollup_sync_service/testdata/commitBatch_codecv1.rlp
vendored
Normal file
BIN
rollup/rollup_sync_service/testdata/commitBatch_codecv1.rlp
vendored
Normal file
Binary file not shown.
27
rollup/rollup_sync_service/testdata/commitBatch_input_codecv0.json
vendored
Normal file
27
rollup/rollup_sync_service/testdata/commitBatch_input_codecv0.json
vendored
Normal file
File diff suppressed because one or more lines are too long
31
rollup/rollup_sync_service/testdata/commitBatch_input_codecv1.json
vendored
Normal file
31
rollup/rollup_sync_service/testdata/commitBatch_input_codecv1.json
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
64
rollup/types/encoding/bitmap.go
Normal file
64
rollup/types/encoding/bitmap.go
Normal 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
|
||||||
|
}
|
||||||
480
rollup/types/encoding/codecv0/codecv0.go
Normal file
480
rollup/types/encoding/codecv0/codecv0.go
Normal 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
|
||||||
|
}
|
||||||
522
rollup/types/encoding/codecv1/codecv1.go
Normal file
522
rollup/types/encoding/codecv1/codecv1.go
Normal 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
259
rollup/types/encoding/da.go
Normal 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
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue