From 7665c1e60755a51eae3b6f54ce2bd492fcc4f1ba Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Tue, 11 Jun 2024 23:13:36 +0200 Subject: [PATCH 01/17] add decoding mehods --- encoding/bitmap.go | 24 +++++++ encoding/codecv0/codecv0.go | 64 +++++++++++++++++ encoding/codecv1/codecv1.go | 139 ++++++++++++++++++++++++++++++++++++ encoding/codecv2/codecv2.go | 81 +++++++++++++++++++++ 4 files changed, 308 insertions(+) diff --git a/encoding/bitmap.go b/encoding/bitmap.go index 7ada6d6..f1a67cb 100644 --- a/encoding/bitmap.go +++ b/encoding/bitmap.go @@ -63,3 +63,27 @@ func ConstructSkippedBitmap(batchIndex uint64, chunks []*Chunk, totalL1MessagePo return bitmapBytes, nextIndex, nil } + +// DecodeBitmap decodes skipped L1 message bitmap of the batch from bytes to big.Int's +func DecodeBitmap(skippedL1MessageBitmap []byte, totalL1MessagePopped int) ([]*big.Int, error) { + length := len(skippedL1MessageBitmap) + if length%32 != 0 { + return nil, fmt.Errorf("skippedL1MessageBitmap length doesn't match, skippedL1MessageBitmap length should be equal 0 modulo 32, length of skippedL1MessageBitmap: %v", length) + } + if length*8 < totalL1MessagePopped { + return nil, fmt.Errorf("skippedL1MessageBitmap length is too small, skippedL1MessageBitmap length should be at least %v, length of skippedL1MessageBitmap: %v", (totalL1MessagePopped+7)/8, length) + } + var skippedBitmap []*big.Int + for index := 0; index < length/32; index++ { + bitmap := big.NewInt(0).SetBytes(skippedL1MessageBitmap[index*32 : index*32+32]) + skippedBitmap = append(skippedBitmap, bitmap) + } + return skippedBitmap, nil +} + +// IsL1MessageSkipped checks if index is skipped in bitmap +func IsL1MessageSkipped(skippedBitmap []*big.Int, index uint64) bool { + quo := index / 256 + rem := index % 256 + return skippedBitmap[quo].Bit(int(rem)) != 0 +} diff --git a/encoding/codecv0/codecv0.go b/encoding/codecv0/codecv0.go index 4c66291..e69c26e 100644 --- a/encoding/codecv0/codecv0.go +++ b/encoding/codecv0/codecv0.go @@ -16,6 +16,8 @@ import ( "github.com/scroll-tech/da-codec/encoding" ) +const BlockContextByteSize = 60 + // DABlock represents a Data Availability Block. type DABlock struct { BlockNumber uint64 @@ -32,6 +34,12 @@ type DAChunk struct { Transactions [][]*types.TransactionData } +// DAChunkRawTx groups consecutive DABlocks with their transactions. +type DAChunkRawTx struct { + Blocks []*DABlock + Transactions []types.Transactions +} + // DABatch contains metadata about a batch of DAChunks. type DABatch struct { Version uint8 @@ -171,6 +179,62 @@ func (c *DAChunk) Encode() ([]byte, error) { return chunkBytes, nil } +// DecodeDAChunksRawTx takes a byte slice and decodes it into a []DAChunkRawTx. +func DecodeDAChunksRawTx(bytes [][]byte) ([]*DAChunkRawTx, error) { + var chunks []*DAChunkRawTx + for _, chunk := range bytes { + 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) + } + + blocks := make([]*DABlock, numBlocks) + for i := 0; i < numBlocks; i++ { + startIdx := 1 + i*BlockContextByteSize // add 1 to skip numBlocks byte + endIdx := startIdx + BlockContextByteSize + err := blocks[i].Decode(chunk[startIdx:endIdx]) + if err != nil { + return nil, err + } + } + + var transactions []types.Transactions + currentIndex := 1 + numBlocks*BlockContextByteSize + for _, block := range blocks { + var blockTransactions types.Transactions + var txNum int = int(block.NumTransactions - block.NumL1Messages) + for i := 0; i < txNum; i++ { + if len(chunk) < currentIndex+4 { + return nil, fmt.Errorf("chunk size doesn't match, next tx size is less then 4, byte length of chunk: %v, expected length: %v", len(chunk), currentIndex+4) + } + txLen := int(binary.BigEndian.Uint32(chunk[currentIndex : currentIndex+4])) + if len(chunk) < currentIndex+4+txLen { + return nil, fmt.Errorf("chunk size doesn't match with next tx length, byte length of chunk: %v, expected length: %v", len(chunk), currentIndex+4+txLen) + } + txData := chunk[currentIndex+4 : currentIndex+4+txLen] + tx := &types.Transaction{} + err := tx.UnmarshalBinary(txData) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal tx, err: %w", err) + } + blockTransactions = append(blockTransactions, tx) + currentIndex += 4 + txLen + } + transactions = append(transactions, blockTransactions) + } + + chunks = append(chunks, &DAChunkRawTx{ + Blocks: blocks, + Transactions: transactions, + }) + } + return chunks, nil +} + // Hash computes the hash of the DAChunk data. func (c *DAChunk) Hash() (common.Hash, error) { chunkBytes, err := c.Encode() diff --git a/encoding/codecv1/codecv1.go b/encoding/codecv1/codecv1.go index 5f30b7d..c0158da 100644 --- a/encoding/codecv1/codecv1.go +++ b/encoding/codecv1/codecv1.go @@ -26,6 +26,8 @@ var BLSModulus = new(big.Int).SetBytes(common.FromHex("0x73eda753299d7d483339d80 // MaxNumChunks is the maximum number of chunks that a batch can contain. const MaxNumChunks = 15 +const BlockContextByteSize = 60 + // DABlock represents a Data Availability Block. type DABlock struct { BlockNumber uint64 @@ -42,6 +44,12 @@ type DAChunk struct { Transactions [][]*types.TransactionData } +// DAChunkRawTx groups consecutive DABlocks with their transactions. +type DAChunkRawTx struct { + Blocks []*DABlock + Transactions []types.Transactions +} + // DABatch contains metadata about a batch of DAChunks. type DABatch struct { // header @@ -156,6 +164,39 @@ func (c *DAChunk) Encode() []byte { return chunkBytes } +// DecodeDAChunksRawTx takes a byte slice and decodes it into a []DAChunkRawTx. +func DecodeDAChunksRawTx(bytes [][]byte) ([]*DAChunkRawTx, error) { + var chunks []*DAChunkRawTx + for _, chunk := range bytes { + 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) + } + + blocks := make([]*DABlock, numBlocks) + for i := 0; i < numBlocks; i++ { + startIdx := 1 + i*BlockContextByteSize // add 1 to skip numBlocks byte + endIdx := startIdx + BlockContextByteSize + err := blocks[i].Decode(chunk[startIdx:endIdx]) + if err != nil { + return nil, err + } + } + + var transactions []types.Transactions + + chunks = append(chunks, &DAChunkRawTx{ + Blocks: blocks, + Transactions: transactions, + }) + } + return chunks, nil +} + // Hash computes the hash of the DAChunk data. func (c *DAChunk) Hash() (common.Hash, error) { var dataBytes []byte @@ -374,6 +415,104 @@ func MakeBlobCanonical(blobBytes []byte) (*kzg4844.Blob, error) { return &blob, nil } +// DecodeTxsFromBlob decodes txs from blob bytes and writes to chunks +func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*DAChunkRawTx) error { + blobBytes := BytesFromBlobCanonical(blob) + numChunks := int(binary.BigEndian.Uint16(blobBytes[0:2])) + if numChunks != len(chunks) { + return fmt.Errorf("blob chunk number is not same as calldata, blob num chunks: %d, calldata num chunks: %d", numChunks, len(chunks)) + } + index := 2 + MaxNumChunks*4 + for chunkID, chunk := range chunks { + var transactions []types.Transactions + chunkSize := int(binary.BigEndian.Uint32(blobBytes[2+4*chunkID : 2+4*chunkID+4])) + + chunkBytes := blobBytes[index : index+chunkSize] + curIndex := 0 + for _, block := range chunk.Blocks { + var blockTransactions types.Transactions + var txNum = int(block.NumTransactions - block.NumL1Messages) + for i := 0; i < txNum; i++ { + tx, nextIndex, err := GetNextTx(chunkBytes, curIndex) + if err != nil { + return fmt.Errorf("couldn't decode next tx from blob bytes: %w, index: %d", err, index+curIndex+4) + } + curIndex = nextIndex + blockTransactions = append(blockTransactions, tx) + } + transactions = append(transactions, blockTransactions) + } + chunk.Transactions = transactions + index += chunkSize + } + return nil +} + +var errSmallLength error = fmt.Errorf("length of blob bytes is too small") + +// GetNextTx parses blob bytes to find length of payload of next Tx and decode it +func GetNextTx(bytes []byte, index int) (*types.Transaction, int, error) { + var nextIndex int + length := len(bytes) + if length < index+1 { + return nil, 0, errSmallLength + } + var txBytes []byte + if bytes[index] <= 0x7f { + // the first byte is transaction type, rlp encoding begins from next byte + txBytes = append(txBytes, bytes[index]) + index++ + + } + if length < index+1 { + return nil, 0, errSmallLength + } + if bytes[index] >= 0xc0 && bytes[index] <= 0xf7 { + // length of payload is simply bytes[index] - 0xc0 + payloadLen := int(bytes[index] - 0xc0) + if length < index+1+payloadLen { + return nil, 0, errSmallLength + } + txBytes = append(txBytes, bytes[index:index+1+payloadLen]...) + nextIndex = index + 1 + payloadLen + } else if bytes[index] > 0xf7 { + // the length of payload is encoded in next bytes[index] - 0xf7 bytes + // length of bytes representation of length of payload + lenPayloadLen := int(bytes[index] - 0xf7) + if length < index+1+lenPayloadLen { + return nil, 0, errSmallLength + } + lenBytes := bytes[index+1 : index+1+lenPayloadLen] + for len(lenBytes) < 8 { + lenBytes = append([]byte{0x0}, lenBytes...) + } + payloadLen := binary.BigEndian.Uint64(lenBytes) + + if length < index+1+lenPayloadLen+int(payloadLen) { + return nil, 0, errSmallLength + } + txBytes = append(txBytes, bytes[index:index+1+lenPayloadLen+int(payloadLen)]...) + nextIndex = index + 1 + lenPayloadLen + int(payloadLen) + } else { + return nil, 0, fmt.Errorf("incorrect format of rlp encoding") + } + tx := &types.Transaction{} + err := tx.UnmarshalBinary(txBytes) + if err != nil { + return nil, 0, fmt.Errorf("failed to unmarshal tx, err: %w", err) + } + return tx, nextIndex, nil +} + +// BytesFromBlobCanonical converts the canonical blob representation into the raw blob data +func BytesFromBlobCanonical(blob *kzg4844.Blob) [126976]byte { + var blobBytes [126976]byte + for from := 0; from < len(blob); from += 32 { + copy(blobBytes[from/32*31:], blob[from+1:from+32]) + } + return blobBytes +} + // 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) { diff --git a/encoding/codecv2/codecv2.go b/encoding/codecv2/codecv2.go index 719b88a..d0fae47 100644 --- a/encoding/codecv2/codecv2.go +++ b/encoding/codecv2/codecv2.go @@ -29,6 +29,8 @@ import ( // MaxNumChunks is the maximum number of chunks that a batch can contain. const MaxNumChunks = 45 +const BlockContextByteSize = 60 + // DABlock represents a Data Availability Block. type DABlock struct { BlockNumber uint64 @@ -45,6 +47,12 @@ type DAChunk struct { Transactions [][]*types.TransactionData } +// DAChunkRawTx groups consecutive DABlocks with their transactions. +type DAChunkRawTx struct { + Blocks []*DABlock + Transactions []types.Transactions +} + // DABatch contains metadata about a batch of DAChunks. type DABatch struct { // header @@ -159,6 +167,39 @@ func (c *DAChunk) Encode() []byte { return chunkBytes } +// DecodeDAChunksRawTx takes a byte slice and decodes it into a []DAChunkRawTx. +func DecodeDAChunksRawTx(bytes [][]byte) ([]*DAChunkRawTx, error) { + var chunks []*DAChunkRawTx + for _, chunk := range bytes { + 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) + } + + blocks := make([]*DABlock, numBlocks) + for i := 0; i < numBlocks; i++ { + startIdx := 1 + i*BlockContextByteSize // add 1 to skip numBlocks byte + endIdx := startIdx + BlockContextByteSize + err := blocks[i].Decode(chunk[startIdx:endIdx]) + if err != nil { + return nil, err + } + } + + var transactions []types.Transactions + + chunks = append(chunks, &DAChunkRawTx{ + Blocks: blocks, + Transactions: transactions, + }) + } + return chunks, nil +} + // Hash computes the hash of the DAChunk data. func (c *DAChunk) Hash() (common.Hash, error) { var dataBytes []byte @@ -341,6 +382,46 @@ func constructBlobPayload(chunks []*encoding.Chunk, useMockTxData bool) (*kzg484 return blob, blobVersionedHash, &z, nil } +// DecodeTxsFromBlob decodes txs from blob bytes and writes to chunks +func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*DAChunkRawTx) error { + blobBytes := codecv1.BytesFromBlobCanonical(blob) + + // todo: decompress + // blobBytes, err := decompressScrollBatchBytes(blobBytes) + // if err != nil { + // return err + // } + + numChunks := int(binary.BigEndian.Uint16(blobBytes[0:2])) + if numChunks != len(chunks) { + return fmt.Errorf("blob chunk number is not same as calldata, blob num chunks: %d, calldata num chunks: %d", numChunks, len(chunks)) + } + index := 2 + MaxNumChunks*4 + for chunkID, chunk := range chunks { + var transactions []types.Transactions + chunkSize := int(binary.BigEndian.Uint32(blobBytes[2+4*chunkID : 2+4*chunkID+4])) + + chunkBytes := blobBytes[index : index+chunkSize] + curIndex := 0 + for _, block := range chunk.Blocks { + var blockTransactions types.Transactions + var txNum = int(block.NumTransactions - block.NumL1Messages) + for i := 0; i < txNum; i++ { + tx, nextIndex, err := codecv1.GetNextTx(chunkBytes, curIndex) + if err != nil { + return fmt.Errorf("couldn't decode next tx from blob bytes: %w, index: %d", err, index+curIndex+4) + } + curIndex = nextIndex + blockTransactions = append(blockTransactions, tx) + } + transactions = append(transactions, blockTransactions) + } + chunk.Transactions = transactions + index += chunkSize + } + return 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) { From 129fde69007441a587676317e7584ea7c0a0130e Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Wed, 12 Jun 2024 18:12:59 +0200 Subject: [PATCH 02/17] add tests for codecv0 and ccodecv1 --- encoding/bitmap.go | 3 ++ encoding/codecv0/codecv0.go | 1 + encoding/codecv0/codecv0_test.go | 45 ++++++++++++++++++++++++++ encoding/codecv1/codecv1.go | 1 + encoding/codecv1/codecv1_test.go | 55 ++++++++++++++++++++++++++++++++ encoding/codecv2/codecv2.go | 1 + 6 files changed, 106 insertions(+) diff --git a/encoding/bitmap.go b/encoding/bitmap.go index f1a67cb..5631983 100644 --- a/encoding/bitmap.go +++ b/encoding/bitmap.go @@ -83,6 +83,9 @@ func DecodeBitmap(skippedL1MessageBitmap []byte, totalL1MessagePopped int) ([]*b // IsL1MessageSkipped checks if index is skipped in bitmap func IsL1MessageSkipped(skippedBitmap []*big.Int, index uint64) bool { + if index > uint64(len(skippedBitmap))*256 { + return false + } quo := index / 256 rem := index % 256 return skippedBitmap[quo].Bit(int(rem)) != 0 diff --git a/encoding/codecv0/codecv0.go b/encoding/codecv0/codecv0.go index e69c26e..38f0ecd 100644 --- a/encoding/codecv0/codecv0.go +++ b/encoding/codecv0/codecv0.go @@ -196,6 +196,7 @@ func DecodeDAChunksRawTx(bytes [][]byte) ([]*DAChunkRawTx, error) { for i := 0; i < numBlocks; i++ { startIdx := 1 + i*BlockContextByteSize // add 1 to skip numBlocks byte endIdx := startIdx + BlockContextByteSize + blocks[i] = &DABlock{} err := blocks[i].Decode(chunk[startIdx:endIdx]) if err != nil { return nil, err diff --git a/encoding/codecv0/codecv0_test.go b/encoding/codecv0/codecv0_test.go index 330a826..72d58a2 100644 --- a/encoding/codecv0/codecv0_test.go +++ b/encoding/codecv0/codecv0_test.go @@ -264,6 +264,38 @@ func TestCodecV0(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 61, len(chunkBytes2)) + daChunksRawTx, err := DecodeDAChunksRawTx([][]byte{chunkBytes1, chunkBytes2}) + assert.NoError(t, err) + // assert number of chunks + assert.Equal(t, 2, len(daChunksRawTx)) + + // assert block in first chunk + assert.Equal(t, 3, len(daChunksRawTx[0].Blocks)) + assert.Equal(t, daChunk1.Blocks[0], daChunksRawTx[0].Blocks[0]) + assert.Equal(t, daChunk1.Blocks[1], daChunksRawTx[0].Blocks[1]) + daChunksRawTx[0].Blocks[2].BaseFee = nil + assert.Equal(t, daChunk1.Blocks[2], daChunksRawTx[0].Blocks[2]) + + // assert block in second chunk + assert.Equal(t, 1, len(daChunksRawTx[1].Blocks)) + daChunksRawTx[1].Blocks[0].BaseFee = nil + assert.Equal(t, daChunk2.Blocks[0], daChunksRawTx[1].Blocks[0]) + + // assert transactions in first chunk + assert.Equal(t, 3, len(daChunksRawTx[0].Transactions)) + // here number of transactions in encoded and decoded chunks may be different, because decodec chunks doesn't contain l1msgs + assert.Equal(t, 2, len(daChunksRawTx[0].Transactions[0])) + assert.Equal(t, 1, len(daChunksRawTx[0].Transactions[1])) + assert.Equal(t, 1, len(daChunksRawTx[0].Transactions[2])) + + assert.EqualValues(t, daChunk1.Transactions[0][0].TxHash, daChunksRawTx[0].Transactions[0][0].Hash().String()) + assert.EqualValues(t, daChunk1.Transactions[0][1].TxHash, daChunksRawTx[0].Transactions[0][1].Hash().String()) + + // assert transactions in second chunk + assert.Equal(t, 1, len(daChunksRawTx[1].Transactions)) + // here number of transactions in encoded and decoded chunks may be different, because decodec chunks doesn't contain l1msgs + assert.Equal(t, 0, len(daChunksRawTx[1].Transactions[0])) + batch = &encoding.Batch{ Index: 1, TotalL1MessagePoppedBefore: 0, @@ -297,6 +329,19 @@ func TestCodecV0(t *testing.T) { decodedBatchHexString = hex.EncodeToString(decodedBatchBytes) assert.Equal(t, batchHexString, decodedBatchHexString) + decodedBitmap, err := encoding.DecodeBitmap(decodedDABatch.SkippedL1MessageBitmap, int(decodedDABatch.L1MessagePopped)) + assert.NoError(t, err) + assert.True(t, encoding.IsL1MessageSkipped(decodedBitmap, 0)) + assert.True(t, encoding.IsL1MessageSkipped(decodedBitmap, 9)) + assert.False(t, encoding.IsL1MessageSkipped(decodedBitmap, 10)) + assert.True(t, encoding.IsL1MessageSkipped(decodedBitmap, 11)) + assert.True(t, encoding.IsL1MessageSkipped(decodedBitmap, 36)) + assert.False(t, encoding.IsL1MessageSkipped(decodedBitmap, 37)) + assert.False(t, encoding.IsL1MessageSkipped(decodedBitmap, 38)) + assert.False(t, encoding.IsL1MessageSkipped(decodedBitmap, 39)) + assert.False(t, encoding.IsL1MessageSkipped(decodedBitmap, 40)) + assert.False(t, encoding.IsL1MessageSkipped(decodedBitmap, 41)) + // Test case: many consecutive L1 Msgs in 1 bitmap, no leading skipped msgs. chunk = &encoding.Chunk{ Blocks: []*encoding.Block{block4}, diff --git a/encoding/codecv1/codecv1.go b/encoding/codecv1/codecv1.go index c0158da..a63dc4a 100644 --- a/encoding/codecv1/codecv1.go +++ b/encoding/codecv1/codecv1.go @@ -181,6 +181,7 @@ func DecodeDAChunksRawTx(bytes [][]byte) ([]*DAChunkRawTx, error) { for i := 0; i < numBlocks; i++ { startIdx := 1 + i*BlockContextByteSize // add 1 to skip numBlocks byte endIdx := startIdx + BlockContextByteSize + blocks[i] = &DABlock{} err := blocks[i].Decode(chunk[startIdx:endIdx]) if err != nil { return nil, err diff --git a/encoding/codecv1/codecv1_test.go b/encoding/codecv1/codecv1_test.go index b914ed6..6a6fcf8 100644 --- a/encoding/codecv1/codecv1_test.go +++ b/encoding/codecv1/codecv1_test.go @@ -473,6 +473,61 @@ func TestCodecV1BatchBlob(t *testing.T) { assert.Equal(t, "0x01b63f87bdd2caa8d43500d47ee59204f61af95339483c62ff436c6beabf47bf", batch.BlobVersionedHash.Hex()) } +func TestCodecV1Decode(t *testing.T) { + trace0 := readBlockFromJSON(t, "../testdata/blockTrace_02.json") + trace1 := readBlockFromJSON(t, "../testdata/blockTrace_03.json") + chunk0 := &encoding.Chunk{Blocks: []*encoding.Block{trace0, trace1}} + daChunk0, err := NewDAChunk(chunk0, 0) + assert.NoError(t, err) + chunkBytes0 := daChunk0.Encode() + + trace2 := readBlockFromJSON(t, "../testdata/blockTrace_04.json") + trace3 := readBlockFromJSON(t, "../testdata/blockTrace_05.json") + chunk1 := &encoding.Chunk{Blocks: []*encoding.Block{trace2, trace3}} + daChunk1, err := NewDAChunk(chunk1, 0) + assert.NoError(t, err) + chunkBytes1 := daChunk1.Encode() + + originalBatch := &encoding.Batch{Chunks: []*encoding.Chunk{chunk0, chunk1}} + batch, err := NewDABatch(originalBatch) + assert.NoError(t, err) + + daChunksRawTx, err := DecodeDAChunksRawTx([][]byte{chunkBytes0, chunkBytes1}) + assert.NoError(t, err) + // assert number of chunks + assert.Equal(t, 2, len(daChunksRawTx)) + + // assert block in first chunk + assert.Equal(t, 2, len(daChunksRawTx[0].Blocks)) + assert.Equal(t, daChunk0.Blocks[0], daChunksRawTx[0].Blocks[0]) + assert.Equal(t, daChunk0.Blocks[1], daChunksRawTx[0].Blocks[1]) + + // assert block in second chunk + assert.Equal(t, 2, len(daChunksRawTx[1].Blocks)) + daChunksRawTx[1].Blocks[0].BaseFee = nil + assert.Equal(t, daChunk1.Blocks[0], daChunksRawTx[1].Blocks[0]) + daChunksRawTx[1].Blocks[1].BaseFee = nil + assert.Equal(t, daChunk1.Blocks[1], daChunksRawTx[1].Blocks[1]) + + blob := batch.Blob() + DecodeTxsFromBlob(blob, daChunksRawTx) + + // assert transactions in first chunk + assert.Equal(t, 2, len(daChunksRawTx[0].Transactions)) + // here number of transactions in encoded and decoded chunks may be different, because decodec chunks doesn't contain l1msgs + assert.Equal(t, 2, len(daChunksRawTx[0].Transactions[0])) + assert.Equal(t, 1, len(daChunksRawTx[0].Transactions[1])) + + assert.EqualValues(t, daChunk0.Transactions[0][0].TxHash, daChunksRawTx[0].Transactions[0][0].Hash().String()) + assert.EqualValues(t, daChunk0.Transactions[0][1].TxHash, daChunksRawTx[0].Transactions[0][1].Hash().String()) + + // assert transactions in second chunk + assert.Equal(t, 2, len(daChunksRawTx[1].Transactions)) + // here number of transactions in encoded and decoded chunks may be different, because decodec chunks doesn't contain l1msgs + assert.Equal(t, 1, len(daChunksRawTx[1].Transactions[0])) + assert.Equal(t, 0, len(daChunksRawTx[1].Transactions[1])) +} + func TestCodecV1BatchChallenge(t *testing.T) { trace2 := readBlockFromJSON(t, "../testdata/blockTrace_02.json") chunk2 := &encoding.Chunk{Blocks: []*encoding.Block{trace2}} diff --git a/encoding/codecv2/codecv2.go b/encoding/codecv2/codecv2.go index d0fae47..a18acae 100644 --- a/encoding/codecv2/codecv2.go +++ b/encoding/codecv2/codecv2.go @@ -184,6 +184,7 @@ func DecodeDAChunksRawTx(bytes [][]byte) ([]*DAChunkRawTx, error) { for i := 0; i < numBlocks; i++ { startIdx := 1 + i*BlockContextByteSize // add 1 to skip numBlocks byte endIdx := startIdx + BlockContextByteSize + blocks[i] = &DABlock{} err := blocks[i].Decode(chunk[startIdx:endIdx]) if err != nil { return nil, err From 0ee1e04338c9b26afb2921ec8cd5bb3da4da3219 Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Fri, 14 Jun 2024 16:51:41 +0200 Subject: [PATCH 03/17] decompressing --- encoding/codecv2/codecv2.go | 33 +++++++++++++++++++++++++++----- encoding/codecv2/codecv2_test.go | 13 +++++++++++++ go.mod | 3 +++ go.sum | 6 ++++++ 4 files changed, 50 insertions(+), 5 deletions(-) diff --git a/encoding/codecv2/codecv2.go b/encoding/codecv2/codecv2.go index a18acae..fbdbad1 100644 --- a/encoding/codecv2/codecv2.go +++ b/encoding/codecv2/codecv2.go @@ -17,10 +17,13 @@ import ( "strings" "unsafe" + "github.com/DataDog/zstd" + zstd2 "github.com/klauspost/compress/zstd" "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/valyala/gozstd" "github.com/scroll-tech/da-codec/encoding" "github.com/scroll-tech/da-codec/encoding/codecv1" @@ -385,13 +388,13 @@ func constructBlobPayload(chunks []*encoding.Chunk, useMockTxData bool) (*kzg484 // DecodeTxsFromBlob decodes txs from blob bytes and writes to chunks func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*DAChunkRawTx) error { - blobBytes := codecv1.BytesFromBlobCanonical(blob) + compressedBytes := codecv1.BytesFromBlobCanonical(blob) // todo: decompress - // blobBytes, err := decompressScrollBatchBytes(blobBytes) - // if err != nil { - // return err - // } + blobBytes, err := decompressScrollBatchBytes(compressedBytes[:]) + if err != nil { + return err + } numChunks := int(binary.BigEndian.Uint16(blobBytes[0:2])) if numChunks != len(chunks) { @@ -605,3 +608,23 @@ func compressScrollBatchBytes(batchBytes []byte) ([]byte, error) { return outbuf[:int(outbufSize)], nil } + +// decompressScrollBatchBytes decompresses the given bytes into scroll batch bytes +func decompressScrollBatchBytes(compressedBytes []byte) ([]byte, error) { + data, err := gozstd.Decompress(nil, compressedBytes) + + return data, err +} + +// decompressScrollBatchBytes decompresses the given bytes into scroll batch bytes +func decompressScrollBatchBytes1(compressedBytes []byte) ([]byte, error) { + data, err := zstd.Decompress(nil, compressedBytes) + + return data, err +} + +// decompressScrollBatchBytes decompresses the given bytes into scroll batch bytes +func decompressScrollBatchBytes2(compressedBytes []byte) ([]byte, error) { + var decoder, _ = zstd2.NewReader(nil, zstd2.WithDecoderConcurrency(0)) + return decoder.DecodeAll(compressedBytes, nil) +} diff --git a/encoding/codecv2/codecv2_test.go b/encoding/codecv2/codecv2_test.go index 9309012..71ca862 100644 --- a/encoding/codecv2/codecv2_test.go +++ b/encoding/codecv2/codecv2_test.go @@ -391,6 +391,19 @@ func TestCodecV2BatchDataHash(t *testing.T) { assert.Equal(t, "0x9b0f37c563d27d9717ab16d47075df996c54fe110130df6b11bfd7230e134767", batch.DataHash.Hex()) } +func TestCompressDecompress(t *testing.T) { + blobString := "00" + "0001" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + blobBytes, err := hex.DecodeString(blobString) + assert.NoError(t, err) + + compressed, err := compressScrollBatchBytes(blobBytes) + assert.NoError(t, err) + + decompressed, err := decompressScrollBatchBytes2(compressed) + assert.NoError(t, err) + assert.Equal(t, blobBytes, decompressed) +} + func TestCodecV2BatchBlob(t *testing.T) { trace2 := readBlockFromJSON(t, "../testdata/blockTrace_02.json") chunk2 := &encoding.Chunk{Blocks: []*encoding.Block{trace2}} diff --git a/go.mod b/go.mod index 8d5696e..04ddc7d 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,11 @@ module github.com/scroll-tech/da-codec go 1.21 require ( + github.com/DataDog/zstd v1.5.5 + github.com/klauspost/compress v1.17.9 github.com/scroll-tech/go-ethereum v1.10.14-0.20240607130425-e2becce6a1a4 github.com/stretchr/testify v1.9.0 + github.com/valyala/gozstd v1.21.1 ) require ( diff --git a/go.sum b/go.sum index 29a3574..330d0de 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= +github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= @@ -46,6 +48,8 @@ github.com/iden3/go-iden3-crypto v0.0.15/go.mod h1:dLpM4vEPJ3nDHzhWFXDjzkn1qHoBe github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -88,6 +92,8 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/valyala/gozstd v1.21.1 h1:TQFZVTk5zo7iJcX3o4XYBJujPdO31LFb4fVImwK873A= +github.com/valyala/gozstd v1.21.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= From 4cb6ab7bef134dfe3667b559af3d8c121d41e84f Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Fri, 14 Jun 2024 18:55:44 +0200 Subject: [PATCH 04/17] add decompressing for codecv2 --- encoding/codecv1/codecv1_test.go | 3 +- encoding/codecv2/codecv2.go | 40 +++++++++-------- encoding/codecv2/codecv2_test.go | 76 ++++++++++++++++++++++++++++++-- go.mod | 2 - go.sum | 4 -- 5 files changed, 97 insertions(+), 28 deletions(-) diff --git a/encoding/codecv1/codecv1_test.go b/encoding/codecv1/codecv1_test.go index 6a6fcf8..fad4b4c 100644 --- a/encoding/codecv1/codecv1_test.go +++ b/encoding/codecv1/codecv1_test.go @@ -510,7 +510,8 @@ func TestCodecV1Decode(t *testing.T) { assert.Equal(t, daChunk1.Blocks[1], daChunksRawTx[1].Blocks[1]) blob := batch.Blob() - DecodeTxsFromBlob(blob, daChunksRawTx) + err = DecodeTxsFromBlob(blob, daChunksRawTx) + assert.NoError(t, err) // assert transactions in first chunk assert.Equal(t, 2, len(daChunksRawTx[0].Transactions)) diff --git a/encoding/codecv2/codecv2.go b/encoding/codecv2/codecv2.go index fbdbad1..f5a9dcd 100644 --- a/encoding/codecv2/codecv2.go +++ b/encoding/codecv2/codecv2.go @@ -7,23 +7,23 @@ char* compress_scroll_batch_bytes(uint8_t* src, uint64_t src_size, uint8_t* outp import "C" import ( + "bytes" "crypto/sha256" "encoding/binary" "encoding/hex" "errors" "fmt" + "io" "math" "math/big" "strings" "unsafe" "github.com/DataDog/zstd" - zstd2 "github.com/klauspost/compress/zstd" "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/valyala/gozstd" "github.com/scroll-tech/da-codec/encoding" "github.com/scroll-tech/da-codec/encoding/codecv1" @@ -389,9 +389,9 @@ func constructBlobPayload(chunks []*encoding.Chunk, useMockTxData bool) (*kzg484 // DecodeTxsFromBlob decodes txs from blob bytes and writes to chunks func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*DAChunkRawTx) error { compressedBytes := codecv1.BytesFromBlobCanonical(blob) + magics := []byte{0x28, 0xb5, 0x2f, 0xfd} - // todo: decompress - blobBytes, err := decompressScrollBatchBytes(compressedBytes[:]) + blobBytes, err := decompressScrollBatchBytes(append(magics, compressedBytes[:]...)) if err != nil { return err } @@ -611,20 +611,24 @@ func compressScrollBatchBytes(batchBytes []byte) ([]byte, error) { // decompressScrollBatchBytes decompresses the given bytes into scroll batch bytes func decompressScrollBatchBytes(compressedBytes []byte) ([]byte, error) { - data, err := gozstd.Decompress(nil, compressedBytes) + // decompress data in stream and in batches of bytes, because we don't know actual length of compressed data + var res []byte + batchOfBytes := make([]byte, 1000) - return data, err -} - -// decompressScrollBatchBytes decompresses the given bytes into scroll batch bytes -func decompressScrollBatchBytes1(compressedBytes []byte) ([]byte, error) { - data, err := zstd.Decompress(nil, compressedBytes) - - return data, err -} + r := bytes.NewReader(compressedBytes) + zr := zstd.NewReader(r) -// decompressScrollBatchBytes decompresses the given bytes into scroll batch bytes -func decompressScrollBatchBytes2(compressedBytes []byte) ([]byte, error) { - var decoder, _ = zstd2.NewReader(nil, zstd2.WithDecoderConcurrency(0)) - return decoder.DecodeAll(compressedBytes, nil) + for { + i, err := zr.Read(batchOfBytes) + if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF { + _ = zr.Close() + return nil, err + } + res = append(res, batchOfBytes[:i]...) + if i == 0 || err == io.EOF || err == io.ErrUnexpectedEOF { + break + } + } + _ = zr.Close() + return res, nil } diff --git a/encoding/codecv2/codecv2_test.go b/encoding/codecv2/codecv2_test.go index 71ca862..d6fcb68 100644 --- a/encoding/codecv2/codecv2_test.go +++ b/encoding/codecv2/codecv2_test.go @@ -3,6 +3,7 @@ package codecv2 import ( "encoding/hex" "encoding/json" + "fmt" "os" "strings" "testing" @@ -14,6 +15,7 @@ import ( "github.com/scroll-tech/da-codec/encoding" "github.com/scroll-tech/da-codec/encoding/codecv0" + "github.com/scroll-tech/da-codec/encoding/codecv1" ) func TestCodecV2BlockEncode(t *testing.T) { @@ -392,16 +394,84 @@ func TestCodecV2BatchDataHash(t *testing.T) { } func TestCompressDecompress(t *testing.T) { - blobString := "00" + "0001" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + blobString := "00" + "0001" + "000000e6" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00" + "00" + "000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + + // tx payload + "00f87180843b9aec2e8307a12094c0c4c8baea3f6acb49b6e1fb9e2adeceeacb000ca28a152d02c7e14af60000008083019ecea0ab07ae99c67aa78e7ba5cf670081e90cc32b219b1de102513d56548a41e86df514a034cbd19feacd73e8ce6400d00c4d1996b9b5243c578fd7f51bfaec288bbaf42a8bf87101843b9aec2e830007a1209401bae6bf68e9a03fb2bc0615b1bf0d69ce9411ed8a152d02c7e14a00f60000008083019ecea0f039985866d8256f10c1be4f7b2cace28d8f20bde2007e2604393eb095b7f77316a05a3e6e81065f2b4604bcec5bd4aba68483599600fc3f879380aac1c09c6eed32f1" blobBytes, err := hex.DecodeString(blobString) assert.NoError(t, err) compressed, err := compressScrollBatchBytes(blobBytes) assert.NoError(t, err) - decompressed, err := decompressScrollBatchBytes2(compressed) + blob, err := codecv1.MakeBlobCanonical(compressed) assert.NoError(t, err) - assert.Equal(t, blobBytes, decompressed) + + res := codecv1.BytesFromBlobCanonical(blob) + compressedBytes := res[:] + magics := []byte{0x28, 0xb5, 0x2f, 0xfd} + compressedBytes = append(magics, compressedBytes...) + + decompressedBlobBytes, err := decompressScrollBatchBytes(compressedBytes) + + assert.NoError(t, err) + assert.Equal(t, blobBytes, decompressedBlobBytes) +} + +func TestCodecV2Decode(t *testing.T) { + trace0 := readBlockFromJSON(t, "../testdata/blockTrace_02.json") + trace1 := readBlockFromJSON(t, "../testdata/blockTrace_03.json") + chunk0 := &encoding.Chunk{Blocks: []*encoding.Block{trace0, trace1}} + daChunk0, err := NewDAChunk(chunk0, 0) + assert.NoError(t, err) + chunkBytes0 := daChunk0.Encode() + + trace2 := readBlockFromJSON(t, "../testdata/blockTrace_04.json") + trace3 := readBlockFromJSON(t, "../testdata/blockTrace_05.json") + chunk1 := &encoding.Chunk{Blocks: []*encoding.Block{trace2, trace3}} + daChunk1, err := NewDAChunk(chunk1, 0) + assert.NoError(t, err) + chunkBytes1 := daChunk1.Encode() + + originalBatch := &encoding.Batch{Chunks: []*encoding.Chunk{chunk0, chunk1}} + batch, err := NewDABatch(originalBatch) + assert.NoError(t, err) + + daChunksRawTx, err := DecodeDAChunksRawTx([][]byte{chunkBytes0, chunkBytes1}) + assert.NoError(t, err) + // assert number of chunks + assert.Equal(t, 2, len(daChunksRawTx)) + + // assert block in first chunk + assert.Equal(t, 2, len(daChunksRawTx[0].Blocks)) + assert.Equal(t, daChunk0.Blocks[0], daChunksRawTx[0].Blocks[0]) + assert.Equal(t, daChunk0.Blocks[1], daChunksRawTx[0].Blocks[1]) + + // assert block in second chunk + assert.Equal(t, 2, len(daChunksRawTx[1].Blocks)) + daChunksRawTx[1].Blocks[0].BaseFee = nil + assert.Equal(t, daChunk1.Blocks[0], daChunksRawTx[1].Blocks[0]) + daChunksRawTx[1].Blocks[1].BaseFee = nil + assert.Equal(t, daChunk1.Blocks[1], daChunksRawTx[1].Blocks[1]) + + blob := batch.Blob() + fmt.Println(len(blob)) + err = DecodeTxsFromBlob(blob, daChunksRawTx) + assert.NoError(t, err) + + // assert transactions in first chunk + assert.Equal(t, 2, len(daChunksRawTx[0].Transactions)) + // here number of transactions in encoded and decoded chunks may be different, because decodec chunks doesn't contain l1msgs + assert.Equal(t, 2, len(daChunksRawTx[0].Transactions[0])) + assert.Equal(t, 1, len(daChunksRawTx[0].Transactions[1])) + + assert.EqualValues(t, daChunk0.Transactions[0][0].TxHash, daChunksRawTx[0].Transactions[0][0].Hash().String()) + assert.EqualValues(t, daChunk0.Transactions[0][1].TxHash, daChunksRawTx[0].Transactions[0][1].Hash().String()) + + // assert transactions in second chunk + assert.Equal(t, 2, len(daChunksRawTx[1].Transactions)) + // here number of transactions in encoded and decoded chunks may be different, because decodec chunks doesn't contain l1msgs + assert.Equal(t, 1, len(daChunksRawTx[1].Transactions[0])) + assert.Equal(t, 0, len(daChunksRawTx[1].Transactions[1])) } func TestCodecV2BatchBlob(t *testing.T) { diff --git a/go.mod b/go.mod index 04ddc7d..c9e044f 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,8 @@ go 1.21 require ( github.com/DataDog/zstd v1.5.5 - github.com/klauspost/compress v1.17.9 github.com/scroll-tech/go-ethereum v1.10.14-0.20240607130425-e2becce6a1a4 github.com/stretchr/testify v1.9.0 - github.com/valyala/gozstd v1.21.1 ) require ( diff --git a/go.sum b/go.sum index 330d0de..0c10ac5 100644 --- a/go.sum +++ b/go.sum @@ -48,8 +48,6 @@ github.com/iden3/go-iden3-crypto v0.0.15/go.mod h1:dLpM4vEPJ3nDHzhWFXDjzkn1qHoBe github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -92,8 +90,6 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/valyala/gozstd v1.21.1 h1:TQFZVTk5zo7iJcX3o4XYBJujPdO31LFb4fVImwK873A= -github.com/valyala/gozstd v1.21.1/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= From c3e19b33fa49f388b28e090b44590875bd6f3017 Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Fri, 14 Jun 2024 19:06:32 +0200 Subject: [PATCH 05/17] fix --- encoding/codecv2/codecv2_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/encoding/codecv2/codecv2_test.go b/encoding/codecv2/codecv2_test.go index d6fcb68..48e1c81 100644 --- a/encoding/codecv2/codecv2_test.go +++ b/encoding/codecv2/codecv2_test.go @@ -3,7 +3,6 @@ package codecv2 import ( "encoding/hex" "encoding/json" - "fmt" "os" "strings" "testing" @@ -454,7 +453,6 @@ func TestCodecV2Decode(t *testing.T) { assert.Equal(t, daChunk1.Blocks[1], daChunksRawTx[1].Blocks[1]) blob := batch.Blob() - fmt.Println(len(blob)) err = DecodeTxsFromBlob(blob, daChunksRawTx) assert.NoError(t, err) From 4d863639aab05ea5ee7a77b09f41bd57600769a5 Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Fri, 14 Jun 2024 20:02:30 +0200 Subject: [PATCH 06/17] change zstd library from c binding to full go port --- encoding/codecv2/codecv2.go | 24 +++++++++++++----------- go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/encoding/codecv2/codecv2.go b/encoding/codecv2/codecv2.go index f5a9dcd..ffda5af 100644 --- a/encoding/codecv2/codecv2.go +++ b/encoding/codecv2/codecv2.go @@ -13,13 +13,12 @@ import ( "encoding/hex" "errors" "fmt" - "io" "math" "math/big" "strings" "unsafe" - "github.com/DataDog/zstd" + "github.com/klauspost/compress/zstd" "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/crypto" @@ -613,22 +612,25 @@ func compressScrollBatchBytes(batchBytes []byte) ([]byte, error) { func decompressScrollBatchBytes(compressedBytes []byte) ([]byte, error) { // decompress data in stream and in batches of bytes, because we don't know actual length of compressed data var res []byte - batchOfBytes := make([]byte, 1000) + readBatchSize := 131072 + batchOfBytes := make([]byte, readBatchSize) r := bytes.NewReader(compressedBytes) - zr := zstd.NewReader(r) + zr, err := zstd.NewReader(r) + if err != nil { + return nil, err + } + defer zr.Close() for { - i, err := zr.Read(batchOfBytes) - if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF { - _ = zr.Close() - return nil, err - } + i, _ := zr.Read(batchOfBytes) // res = append(res, batchOfBytes[:i]...) - if i == 0 || err == io.EOF || err == io.ErrUnexpectedEOF { + if i < readBatchSize { break } } - _ = zr.Close() + if len(res) == 0 { + return nil, fmt.Errorf("failed to decompress blob bytes") + } return res, nil } diff --git a/go.mod b/go.mod index c9e044f..0a84dd2 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/scroll-tech/da-codec go 1.21 require ( - github.com/DataDog/zstd v1.5.5 github.com/scroll-tech/go-ethereum v1.10.14-0.20240607130425-e2becce6a1a4 github.com/stretchr/testify v1.9.0 ) @@ -20,6 +19,7 @@ require ( github.com/go-stack/stack v1.8.1 // indirect github.com/holiman/uint256 v1.2.4 // indirect github.com/iden3/go-iden3-crypto v0.0.15 // indirect + github.com/klauspost/compress v1.17.9 github.com/kr/text v0.2.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 0c10ac5..0a2e1b6 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= -github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= @@ -48,6 +46,8 @@ github.com/iden3/go-iden3-crypto v0.0.15/go.mod h1:dLpM4vEPJ3nDHzhWFXDjzkn1qHoBe github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= From e74a7b9d8a91cae78b1ba7a682326919617e2094 Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Thu, 27 Jun 2024 12:35:42 +0200 Subject: [PATCH 07/17] handle error --- encoding/codecv2/codecv2.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/encoding/codecv2/codecv2.go b/encoding/codecv2/codecv2.go index ffda5af..839b35d 100644 --- a/encoding/codecv2/codecv2.go +++ b/encoding/codecv2/codecv2.go @@ -623,9 +623,9 @@ func decompressScrollBatchBytes(compressedBytes []byte) ([]byte, error) { defer zr.Close() for { - i, _ := zr.Read(batchOfBytes) // - res = append(res, batchOfBytes[:i]...) - if i < readBatchSize { + i, err := zr.Read(batchOfBytes) + res = append(res, batchOfBytes[:i]...) // append already decoded bytes even if we meet error + if i < readBatchSize || err != nil { break } } From 7998e80de0149a74420bcb34d79252c70c28f72e Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Thu, 27 Jun 2024 13:01:44 +0200 Subject: [PATCH 08/17] sync with main --- encoding/codecv1/codecv1.go | 5 +---- encoding/codecv2/codecv2.go | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/encoding/codecv1/codecv1.go b/encoding/codecv1/codecv1.go index af8b669..2ea6289 100644 --- a/encoding/codecv1/codecv1.go +++ b/encoding/codecv1/codecv1.go @@ -35,10 +35,7 @@ type DABlock = codecv0.DABlock type DAChunk codecv0.DAChunk // DAChunkRawTx groups consecutive DABlocks with their transactions. -type DAChunkRawTx struct { - Blocks []*DABlock - Transactions []types.Transactions -} +type DAChunkRawTx codecv0.DAChunkRawTx // DABatch contains metadata about a batch of DAChunks. type DABatch struct { diff --git a/encoding/codecv2/codecv2.go b/encoding/codecv2/codecv2.go index 0d63d78..8ca05fc 100644 --- a/encoding/codecv2/codecv2.go +++ b/encoding/codecv2/codecv2.go @@ -42,10 +42,7 @@ type DABlock = codecv1.DABlock type DAChunk = codecv1.DAChunk // DAChunkRawTx groups consecutive DABlocks with their transactions. -type DAChunkRawTx = struct { - Blocks []*DABlock - Transactions []types.Transactions -} +type DAChunkRawTx codecv1.DAChunkRawTx // DABatch contains metadata about a batch of DAChunks. type DABatch struct { From cb02d638c45f1a4871a0ab4d4c39024f30afb43f Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Mon, 8 Jul 2024 10:49:45 +0200 Subject: [PATCH 09/17] add v3 decoding --- encoding/codecv3/codecv3.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/encoding/codecv3/codecv3.go b/encoding/codecv3/codecv3.go index 0466be5..89fceaf 100644 --- a/encoding/codecv3/codecv3.go +++ b/encoding/codecv3/codecv3.go @@ -18,12 +18,17 @@ import ( // MaxNumChunks is the maximum number of chunks that a batch can contain. const MaxNumChunks = codecv2.MaxNumChunks +const BlockContextByteSize = codecv2.BlockContextByteSize + // DABlock represents a Data Availability Block. type DABlock = codecv2.DABlock // DAChunk groups consecutive DABlocks with their transactions. type DAChunk = codecv2.DAChunk +// DAChunkRawTx groups consecutive DABlocks with their transactions. +type DAChunkRawTx codecv2.DAChunkRawTx + // DABatch contains metadata about a batch of DAChunks. type DABatch struct { // header @@ -52,6 +57,11 @@ func NewDAChunk(chunk *encoding.Chunk, totalL1MessagePoppedBefore uint64) (*DACh return codecv2.NewDAChunk(chunk, totalL1MessagePoppedBefore) } +// DecodeDAChunksRawTx takes a byte slice and decodes it into a []DAChunkRawTx. +func DecodeDAChunksRawTx(bytes [][]byte) ([]*codecv2.DAChunkRawTx, error) { + return codecv2.DecodeDAChunksRawTx(bytes) +} + // 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 @@ -122,6 +132,11 @@ func ConstructBlobPayload(chunks []*encoding.Chunk, useMockTxData bool) (*kzg484 return codecv2.ConstructBlobPayload(chunks, useMockTxData) } +// DecodeTxsFromBlob decodes txs from blob bytes and writes to chunks +func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*codecv2.DAChunkRawTx) error { + return codecv2.DecodeTxsFromBlob(blob, chunks) +} + // NewDABatchFromBytes decodes 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) { From 15d6aa44314020b941339ab8ee56fb16d35548c2 Mon Sep 17 00:00:00 2001 From: jonastheis <4181434+jonastheis@users.noreply.github.com> Date: Thu, 25 Jul 2024 11:09:10 +0800 Subject: [PATCH 10/17] refactor: make DAChunkRawTx an alias --- encoding/codecv1/codecv1.go | 2 +- encoding/codecv2/codecv2.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/encoding/codecv1/codecv1.go b/encoding/codecv1/codecv1.go index 2ea6289..a069c19 100644 --- a/encoding/codecv1/codecv1.go +++ b/encoding/codecv1/codecv1.go @@ -35,7 +35,7 @@ type DABlock = codecv0.DABlock type DAChunk codecv0.DAChunk // DAChunkRawTx groups consecutive DABlocks with their transactions. -type DAChunkRawTx codecv0.DAChunkRawTx +type DAChunkRawTx = codecv0.DAChunkRawTx // DABatch contains metadata about a batch of DAChunks. type DABatch struct { diff --git a/encoding/codecv2/codecv2.go b/encoding/codecv2/codecv2.go index 8ca05fc..5c5b0ae 100644 --- a/encoding/codecv2/codecv2.go +++ b/encoding/codecv2/codecv2.go @@ -17,6 +17,7 @@ import ( "unsafe" "github.com/klauspost/compress/zstd" + "github.com/scroll-tech/go-ethereum/accounts/abi" "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/core/types" @@ -42,7 +43,7 @@ type DABlock = codecv1.DABlock type DAChunk = codecv1.DAChunk // DAChunkRawTx groups consecutive DABlocks with their transactions. -type DAChunkRawTx codecv1.DAChunkRawTx +type DAChunkRawTx = codecv1.DAChunkRawTx // DABatch contains metadata about a batch of DAChunks. type DABatch struct { From 46a287bd48c013ab5cf409e9418012af09ef82cd Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Mon, 29 Jul 2024 17:28:09 +0200 Subject: [PATCH 11/17] address comments --- encoding/codecv0/codecv0.go | 4 ++-- encoding/codecv1/codecv1.go | 6 +++--- encoding/codecv2/codecv2.go | 36 ++++-------------------------------- encoding/codecv3/codecv3.go | 8 ++++---- 4 files changed, 13 insertions(+), 41 deletions(-) diff --git a/encoding/codecv0/codecv0.go b/encoding/codecv0/codecv0.go index 4cd1aa4..601484b 100644 --- a/encoding/codecv0/codecv0.go +++ b/encoding/codecv0/codecv0.go @@ -187,7 +187,7 @@ func (c *DAChunk) Encode() ([]byte, error) { return chunkBytes, nil } -// DecodeDAChunksRawTx takes a byte slice and decodes it into a []DAChunkRawTx. +// DecodeDAChunksRawTx takes a byte slice and decodes it into a []*DAChunkRawTx. func DecodeDAChunksRawTx(bytes [][]byte) ([]*DAChunkRawTx, error) { var chunks []*DAChunkRawTx for _, chunk := range bytes { @@ -215,7 +215,7 @@ func DecodeDAChunksRawTx(bytes [][]byte) ([]*DAChunkRawTx, error) { currentIndex := 1 + numBlocks*BlockContextByteSize for _, block := range blocks { var blockTransactions types.Transactions - var txNum int = int(block.NumTransactions - block.NumL1Messages) + txNum := int(block.NumTransactions - block.NumL1Messages) for i := 0; i < txNum; i++ { if len(chunk) < currentIndex+4 { return nil, fmt.Errorf("chunk size doesn't match, next tx size is less then 4, byte length of chunk: %v, expected length: %v", len(chunk), currentIndex+4) diff --git a/encoding/codecv1/codecv1.go b/encoding/codecv1/codecv1.go index a069c19..cfc11c3 100644 --- a/encoding/codecv1/codecv1.go +++ b/encoding/codecv1/codecv1.go @@ -103,7 +103,7 @@ func (c *DAChunk) Encode() []byte { return chunkBytes } -// DecodeDAChunksRawTx takes a byte slice and decodes it into a []DAChunkRawTx. +// DecodeDAChunksRawTx takes a byte slice and decodes it into a []*DAChunkRawTx. func DecodeDAChunksRawTx(bytes [][]byte) ([]*DAChunkRawTx, error) { var chunks []*DAChunkRawTx for _, chunk := range bytes { @@ -131,7 +131,7 @@ func DecodeDAChunksRawTx(bytes [][]byte) ([]*DAChunkRawTx, error) { chunks = append(chunks, &DAChunkRawTx{ Blocks: blocks, - Transactions: transactions, + Transactions: transactions, // Transactions field is still empty in the phase of DecodeDAChunksRawTx. }) } return chunks, nil @@ -371,7 +371,7 @@ func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*DAChunkRawTx) error { curIndex := 0 for _, block := range chunk.Blocks { var blockTransactions types.Transactions - var txNum = int(block.NumTransactions - block.NumL1Messages) + txNum := int(block.NumTransactions - block.NumL1Messages) for i := 0; i < txNum; i++ { tx, nextIndex, err := GetNextTx(chunkBytes, curIndex) if err != nil { diff --git a/encoding/codecv2/codecv2.go b/encoding/codecv2/codecv2.go index 1e2f5ce..fb0c7cc 100644 --- a/encoding/codecv2/codecv2.go +++ b/encoding/codecv2/codecv2.go @@ -73,38 +73,9 @@ func NewDAChunk(chunk *encoding.Chunk, totalL1MessagePoppedBefore uint64) (*DACh return codecv1.NewDAChunk(chunk, totalL1MessagePoppedBefore) } -// DecodeDAChunksRawTx takes a byte slice and decodes it into a []DAChunkRawTx. +// DecodeDAChunksRawTx takes a byte slice and decodes it into a []*DAChunkRawTx. func DecodeDAChunksRawTx(bytes [][]byte) ([]*DAChunkRawTx, error) { - var chunks []*DAChunkRawTx - for _, chunk := range bytes { - 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) - } - - blocks := make([]*DABlock, numBlocks) - for i := 0; i < numBlocks; i++ { - startIdx := 1 + i*BlockContextByteSize // add 1 to skip numBlocks byte - endIdx := startIdx + BlockContextByteSize - blocks[i] = &DABlock{} - err := blocks[i].Decode(chunk[startIdx:endIdx]) - if err != nil { - return nil, err - } - } - - var transactions []types.Transactions - - chunks = append(chunks, &DAChunkRawTx{ - Blocks: blocks, - Transactions: transactions, - }) - } - return chunks, nil + return codecv1.DecodeDAChunksRawTx(bytes) } // NewDABatch creates a DABatch from the provided encoding.Batch. @@ -287,7 +258,7 @@ func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*DAChunkRawTx) error { curIndex := 0 for _, block := range chunk.Blocks { var blockTransactions types.Transactions - var txNum = int(block.NumTransactions - block.NumL1Messages) + txNum := int(block.NumTransactions - block.NumL1Messages) for i := 0; i < txNum; i++ { tx, nextIndex, err := codecv1.GetNextTx(chunkBytes, curIndex) if err != nil { @@ -564,6 +535,7 @@ func decompressScrollBatchBytes(compressedBytes []byte) ([]byte, error) { for { i, err := zr.Read(batchOfBytes) res = append(res, batchOfBytes[:i]...) // append already decoded bytes even if we meet error + // the error here is supposed to be EOF or similar that indicates that buffer has been read until the if i < readBatchSize || err != nil { break } diff --git a/encoding/codecv3/codecv3.go b/encoding/codecv3/codecv3.go index 91c2bb0..4d27d8d 100644 --- a/encoding/codecv3/codecv3.go +++ b/encoding/codecv3/codecv3.go @@ -27,7 +27,7 @@ type DABlock = codecv2.DABlock type DAChunk = codecv2.DAChunk // DAChunkRawTx groups consecutive DABlocks with their transactions. -type DAChunkRawTx codecv2.DAChunkRawTx +type DAChunkRawTx = codecv2.DAChunkRawTx // DABatch contains metadata about a batch of DAChunks. type DABatch struct { @@ -57,8 +57,8 @@ func NewDAChunk(chunk *encoding.Chunk, totalL1MessagePoppedBefore uint64) (*DACh return codecv2.NewDAChunk(chunk, totalL1MessagePoppedBefore) } -// DecodeDAChunksRawTx takes a byte slice and decodes it into a []DAChunkRawTx. -func DecodeDAChunksRawTx(bytes [][]byte) ([]*codecv2.DAChunkRawTx, error) { +// DecodeDAChunksRawTx takes a byte slice and decodes it into a []*DAChunkRawTx. +func DecodeDAChunksRawTx(bytes [][]byte) ([]*DAChunkRawTx, error) { return codecv2.DecodeDAChunksRawTx(bytes) } @@ -133,7 +133,7 @@ func ConstructBlobPayload(chunks []*encoding.Chunk, useMockTxData bool) (*kzg484 } // DecodeTxsFromBlob decodes txs from blob bytes and writes to chunks -func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*codecv2.DAChunkRawTx) error { +func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*DAChunkRawTx) error { return codecv2.DecodeTxsFromBlob(blob, chunks) } From 31de3201bd1ab1a4dd77b6f31cc9b11535551e8c Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Mon, 29 Jul 2024 17:30:40 +0200 Subject: [PATCH 12/17] comment --- encoding/codecv2/codecv2.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/encoding/codecv2/codecv2.go b/encoding/codecv2/codecv2.go index 10758b3..30b53c1 100644 --- a/encoding/codecv2/codecv2.go +++ b/encoding/codecv2/codecv2.go @@ -535,7 +535,8 @@ func decompressScrollBatchBytes(compressedBytes []byte) ([]byte, error) { for { i, err := zr.Read(batchOfBytes) res = append(res, batchOfBytes[:i]...) // append already decoded bytes even if we meet error - // the error here is supposed to be EOF or similar that indicates that buffer has been read until the + // the error here is supposed to be EOF or similar that indicates that buffer has been read until the end + // we should return all data that read by this moment if i < readBatchSize || err != nil { break } From d7c9669828082e709d3fb00965051ae7231ada0b Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Fri, 2 Aug 2024 10:46:06 +0200 Subject: [PATCH 13/17] comment --- encoding/codecv1/codecv1.go | 2 +- encoding/codecv2/codecv2.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/encoding/codecv1/codecv1.go b/encoding/codecv1/codecv1.go index cfc11c3..9017361 100644 --- a/encoding/codecv1/codecv1.go +++ b/encoding/codecv1/codecv1.go @@ -26,7 +26,7 @@ var BLSModulus = new(big.Int).SetBytes(common.FromHex("0x73eda753299d7d483339d80 // MaxNumChunks is the maximum number of chunks that a batch can contain. const MaxNumChunks = 15 -const BlockContextByteSize = 60 +const BlockContextByteSize = codecv0.BlockContextByteSize // DABlock represents a Data Availability Block. type DABlock = codecv0.DABlock diff --git a/encoding/codecv2/codecv2.go b/encoding/codecv2/codecv2.go index 30b53c1..fc16dc5 100644 --- a/encoding/codecv2/codecv2.go +++ b/encoding/codecv2/codecv2.go @@ -35,7 +35,7 @@ var BLSModulus = codecv1.BLSModulus // MaxNumChunks is the maximum number of chunks that a batch can contain. const MaxNumChunks = 45 -const BlockContextByteSize = 60 +const BlockContextByteSize = codecv1.BlockContextByteSize // DABlock represents a Data Availability Block. type DABlock = codecv1.DABlock From 76f6081fd251ffb8f9ed10584322752fd93c821f Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Tue, 20 Aug 2024 13:08:15 +0200 Subject: [PATCH 14/17] address comments --- encoding/codecv0/codecv0.go | 20 +++++++++++--------- encoding/codecv1/codecv1.go | 15 ++++++++++----- encoding/codecv2/codecv2.go | 30 +----------------------------- 3 files changed, 22 insertions(+), 43 deletions(-) diff --git a/encoding/codecv0/codecv0.go b/encoding/codecv0/codecv0.go index 601484b..2cc8e8e 100644 --- a/encoding/codecv0/codecv0.go +++ b/encoding/codecv0/codecv0.go @@ -17,6 +17,7 @@ import ( ) const BlockContextByteSize = 60 +const TxLenByteSize = 4 // DABlock represents a Data Availability Block. type DABlock struct { @@ -34,7 +35,7 @@ type DAChunk struct { Transactions [][]*types.TransactionData } -// DAChunkRawTx groups consecutive DABlocks with their transactions. +// DAChunkRawTx groups consecutive DABlocks with their L2 transactions, L1 msgs are loaded in another place. type DAChunkRawTx struct { Blocks []*DABlock Transactions []types.Transactions @@ -215,23 +216,24 @@ func DecodeDAChunksRawTx(bytes [][]byte) ([]*DAChunkRawTx, error) { currentIndex := 1 + numBlocks*BlockContextByteSize for _, block := range blocks { var blockTransactions types.Transactions + // ignore L1 msg transactions from the block, consider only L2 transactions txNum := int(block.NumTransactions - block.NumL1Messages) for i := 0; i < txNum; i++ { - if len(chunk) < currentIndex+4 { - return nil, fmt.Errorf("chunk size doesn't match, next tx size is less then 4, byte length of chunk: %v, expected length: %v", len(chunk), currentIndex+4) + if len(chunk) < currentIndex+TxLenByteSize { + return nil, fmt.Errorf("chunk size doesn't match, next tx size is less then 4, byte length of chunk: %v, expected minimum length: %v, txNum without l1 msgs: %d", len(chunk), currentIndex+TxLenByteSize, i) } - txLen := int(binary.BigEndian.Uint32(chunk[currentIndex : currentIndex+4])) - if len(chunk) < currentIndex+4+txLen { - return nil, fmt.Errorf("chunk size doesn't match with next tx length, byte length of chunk: %v, expected length: %v", len(chunk), currentIndex+4+txLen) + txLen := int(binary.BigEndian.Uint32(chunk[currentIndex : currentIndex+TxLenByteSize])) + if len(chunk) < currentIndex+TxLenByteSize+txLen { + return nil, fmt.Errorf("chunk size doesn't match with next tx length, byte length of chunk: %v, expected minimum length: %v, txNum without l1 msgs: %d", len(chunk), currentIndex+TxLenByteSize+txLen, i) } - txData := chunk[currentIndex+4 : currentIndex+4+txLen] + txData := chunk[currentIndex+TxLenByteSize : currentIndex+TxLenByteSize+txLen] tx := &types.Transaction{} err := tx.UnmarshalBinary(txData) if err != nil { - return nil, fmt.Errorf("failed to unmarshal tx, err: %w", err) + return nil, fmt.Errorf("failed to unmarshal tx, pos of tx in chunk bytes: %d. tx num without l1 msgs: %d, err: %w", currentIndex, i, err) } blockTransactions = append(blockTransactions, tx) - currentIndex += 4 + txLen + currentIndex += TxLenByteSize + txLen } transactions = append(transactions, blockTransactions) } diff --git a/encoding/codecv1/codecv1.go b/encoding/codecv1/codecv1.go index 9017361..3bb8a8d 100644 --- a/encoding/codecv1/codecv1.go +++ b/encoding/codecv1/codecv1.go @@ -104,6 +104,7 @@ func (c *DAChunk) Encode() []byte { } // DecodeDAChunksRawTx takes a byte slice and decodes it into a []*DAChunkRawTx. +// From codecv1 tx data posted to blobs, not to chunk bytes in calldata func DecodeDAChunksRawTx(bytes [][]byte) ([]*DAChunkRawTx, error) { var chunks []*DAChunkRawTx for _, chunk := range bytes { @@ -131,7 +132,7 @@ func DecodeDAChunksRawTx(bytes [][]byte) ([]*DAChunkRawTx, error) { chunks = append(chunks, &DAChunkRawTx{ Blocks: blocks, - Transactions: transactions, // Transactions field is still empty in the phase of DecodeDAChunksRawTx. + Transactions: transactions, // Transactions field is still empty in the phase of DecodeDAChunksRawTx, because txs moved to bobs and filled in DecodeTxsFromBlob method. }) } return chunks, nil @@ -355,9 +356,8 @@ func MakeBlobCanonical(blobBytes []byte) (*kzg4844.Blob, error) { return &blob, nil } -// DecodeTxsFromBlob decodes txs from blob bytes and writes to chunks -func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*DAChunkRawTx) error { - blobBytes := BytesFromBlobCanonical(blob) +// DecodeTxsFromBytes decodes txs from blob bytes and writes to chunks +func DecodeTxsFromBytes(blobBytes []byte, chunks []*DAChunkRawTx) error { numChunks := int(binary.BigEndian.Uint16(blobBytes[0:2])) if numChunks != len(chunks) { return fmt.Errorf("blob chunk number is not same as calldata, blob num chunks: %d, calldata num chunks: %d", numChunks, len(chunks)) @@ -388,6 +388,12 @@ func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*DAChunkRawTx) error { return nil } +// DecodeTxsFromBlob decodes txs from blob bytes and writes to chunks +func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*DAChunkRawTx) error { + blobBytes := BytesFromBlobCanonical(blob) + return DecodeTxsFromBytes(blobBytes[:], chunks) +} + var errSmallLength error = fmt.Errorf("length of blob bytes is too small") // GetNextTx parses blob bytes to find length of payload of next Tx and decode it @@ -402,7 +408,6 @@ func GetNextTx(bytes []byte, index int) (*types.Transaction, int, error) { // the first byte is transaction type, rlp encoding begins from next byte txBytes = append(txBytes, bytes[index]) index++ - } if length < index+1 { return nil, 0, errSmallLength diff --git a/encoding/codecv2/codecv2.go b/encoding/codecv2/codecv2.go index eda354a..c1a86fe 100644 --- a/encoding/codecv2/codecv2.go +++ b/encoding/codecv2/codecv2.go @@ -244,35 +244,7 @@ func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*DAChunkRawTx) error { if err != nil { return err } - - numChunks := int(binary.BigEndian.Uint16(blobBytes[0:2])) - if numChunks != len(chunks) { - return fmt.Errorf("blob chunk number is not same as calldata, blob num chunks: %d, calldata num chunks: %d", numChunks, len(chunks)) - } - index := 2 + MaxNumChunks*4 - for chunkID, chunk := range chunks { - var transactions []types.Transactions - chunkSize := int(binary.BigEndian.Uint32(blobBytes[2+4*chunkID : 2+4*chunkID+4])) - - chunkBytes := blobBytes[index : index+chunkSize] - curIndex := 0 - for _, block := range chunk.Blocks { - var blockTransactions types.Transactions - txNum := int(block.NumTransactions - block.NumL1Messages) - for i := 0; i < txNum; i++ { - tx, nextIndex, err := codecv1.GetNextTx(chunkBytes, curIndex) - if err != nil { - return fmt.Errorf("couldn't decode next tx from blob bytes: %w, index: %d", err, index+curIndex+4) - } - curIndex = nextIndex - blockTransactions = append(blockTransactions, tx) - } - transactions = append(transactions, blockTransactions) - } - chunk.Transactions = transactions - index += chunkSize - } - return nil + return codecv1.DecodeTxsFromBytes(blobBytes, chunks) } // MakeBlobCanonical converts the raw blob data into the canonical blob representation of 4096 BLSFieldElements. From c41a465f1e7cad00c6d0d2b18c9aa69478954c94 Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Tue, 20 Aug 2024 13:20:24 +0200 Subject: [PATCH 15/17] fix test --- encoding/codecv1/codecv1.go | 8 ++++---- encoding/codecv2/codecv2.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/encoding/codecv1/codecv1.go b/encoding/codecv1/codecv1.go index 3bb8a8d..e08a46f 100644 --- a/encoding/codecv1/codecv1.go +++ b/encoding/codecv1/codecv1.go @@ -104,7 +104,7 @@ func (c *DAChunk) Encode() []byte { } // DecodeDAChunksRawTx takes a byte slice and decodes it into a []*DAChunkRawTx. -// From codecv1 tx data posted to blobs, not to chunk bytes in calldata +// Beginning from codecv1 tx data posted to blobs, not to chunk bytes in calldata func DecodeDAChunksRawTx(bytes [][]byte) ([]*DAChunkRawTx, error) { var chunks []*DAChunkRawTx for _, chunk := range bytes { @@ -357,12 +357,12 @@ func MakeBlobCanonical(blobBytes []byte) (*kzg4844.Blob, error) { } // DecodeTxsFromBytes decodes txs from blob bytes and writes to chunks -func DecodeTxsFromBytes(blobBytes []byte, chunks []*DAChunkRawTx) error { +func DecodeTxsFromBytes(blobBytes []byte, chunks []*DAChunkRawTx, maxNumChunks int) error { numChunks := int(binary.BigEndian.Uint16(blobBytes[0:2])) if numChunks != len(chunks) { return fmt.Errorf("blob chunk number is not same as calldata, blob num chunks: %d, calldata num chunks: %d", numChunks, len(chunks)) } - index := 2 + MaxNumChunks*4 + index := 2 + maxNumChunks*4 for chunkID, chunk := range chunks { var transactions []types.Transactions chunkSize := int(binary.BigEndian.Uint32(blobBytes[2+4*chunkID : 2+4*chunkID+4])) @@ -391,7 +391,7 @@ func DecodeTxsFromBytes(blobBytes []byte, chunks []*DAChunkRawTx) error { // DecodeTxsFromBlob decodes txs from blob bytes and writes to chunks func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*DAChunkRawTx) error { blobBytes := BytesFromBlobCanonical(blob) - return DecodeTxsFromBytes(blobBytes[:], chunks) + return DecodeTxsFromBytes(blobBytes[:], chunks, MaxNumChunks) } var errSmallLength error = fmt.Errorf("length of blob bytes is too small") diff --git a/encoding/codecv2/codecv2.go b/encoding/codecv2/codecv2.go index c1a86fe..b26024f 100644 --- a/encoding/codecv2/codecv2.go +++ b/encoding/codecv2/codecv2.go @@ -244,7 +244,7 @@ func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*DAChunkRawTx) error { if err != nil { return err } - return codecv1.DecodeTxsFromBytes(blobBytes, chunks) + return codecv1.DecodeTxsFromBytes(blobBytes, chunks, MaxNumChunks) } // MakeBlobCanonical converts the raw blob data into the canonical blob representation of 4096 BLSFieldElements. From b7214d9781ca2bfc6a1e6795d974b2edf6591890 Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Mon, 26 Aug 2024 12:47:25 +0200 Subject: [PATCH 16/17] support v4 --- encoding/codecv0/codecv0_test.go | 3 ++- encoding/codecv1/codecv1.go | 14 ++--------- encoding/codecv1/codecv1_test.go | 3 ++- encoding/codecv2/codecv2.go | 38 +++-------------------------- encoding/codecv2/codecv2_test.go | 12 +++++----- encoding/codecv3/codecv3.go | 4 +--- encoding/codecv3/codecv3_test.go | 5 ++-- encoding/codecv4/codecv4.go | 26 ++++++++++++++++++++ encoding/codecv4/codecv4_test.go | 5 ++-- encoding/da.go | 41 ++++++++++++++++++++++++++++++++ encoding/da_test.go | 3 ++- 11 files changed, 91 insertions(+), 63 deletions(-) diff --git a/encoding/codecv0/codecv0_test.go b/encoding/codecv0/codecv0_test.go index 72d58a2..0a5b514 100644 --- a/encoding/codecv0/codecv0_test.go +++ b/encoding/codecv0/codecv0_test.go @@ -7,9 +7,10 @@ import ( "os" "testing" + "github.com/stretchr/testify/assert" + "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/log" - "github.com/stretchr/testify/assert" "github.com/scroll-tech/da-codec/encoding" ) diff --git a/encoding/codecv1/codecv1.go b/encoding/codecv1/codecv1.go index e1e50d2..e0bb749 100644 --- a/encoding/codecv1/codecv1.go +++ b/encoding/codecv1/codecv1.go @@ -29,7 +29,7 @@ type DABlock = codecv0.DABlock // DAChunk groups consecutive DABlocks with their transactions. type DAChunk codecv0.DAChunk -// DAChunkRawTx groups consecutive DABlocks with their transactions. +// DAChunkRawTx groups consecutive DABlocks with their L2 transactions, L1 msgs are loaded in another place. type DAChunkRawTx = codecv0.DAChunkRawTx // DABatch contains metadata about a batch of DAChunks. @@ -326,7 +326,6 @@ func constructBlobPayload(chunks []*encoding.Chunk, useMockTxData bool) (*kzg484 return blob, blobVersionedHash, &z, nil } - // DecodeTxsFromBytes decodes txs from blob bytes and writes to chunks func DecodeTxsFromBytes(blobBytes []byte, chunks []*DAChunkRawTx, maxNumChunks int) error { numChunks := int(binary.BigEndian.Uint16(blobBytes[0:2])) @@ -361,7 +360,7 @@ func DecodeTxsFromBytes(blobBytes []byte, chunks []*DAChunkRawTx, maxNumChunks i // DecodeTxsFromBlob decodes txs from blob bytes and writes to chunks func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*DAChunkRawTx) error { - blobBytes := BytesFromBlobCanonical(blob) + blobBytes := encoding.BytesFromBlobCanonical(blob) return DecodeTxsFromBytes(blobBytes[:], chunks, MaxNumChunks) } @@ -420,15 +419,6 @@ func GetNextTx(bytes []byte, index int) (*types.Transaction, int, error) { return tx, nextIndex, nil } -// BytesFromBlobCanonical converts the canonical blob representation into the raw blob data -func BytesFromBlobCanonical(blob *kzg4844.Blob) [126976]byte { - var blobBytes [126976]byte - for from := 0; from < len(blob); from += 32 { - copy(blobBytes[from/32*31:], blob[from+1:from+32]) - } - return blobBytes -} - // NewDABatchFromBytes decodes 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) { diff --git a/encoding/codecv1/codecv1_test.go b/encoding/codecv1/codecv1_test.go index fad4b4c..6522c59 100644 --- a/encoding/codecv1/codecv1_test.go +++ b/encoding/codecv1/codecv1_test.go @@ -7,10 +7,11 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/crypto/kzg4844" - "github.com/stretchr/testify/assert" "github.com/scroll-tech/da-codec/encoding" "github.com/scroll-tech/da-codec/encoding/codecv0" diff --git a/encoding/codecv2/codecv2.go b/encoding/codecv2/codecv2.go index 4754647..ccfb3e6 100644 --- a/encoding/codecv2/codecv2.go +++ b/encoding/codecv2/codecv2.go @@ -1,7 +1,6 @@ package codecv2 import ( - "bytes" "crypto/sha256" "encoding/binary" "encoding/hex" @@ -9,8 +8,6 @@ import ( "fmt" "math/big" - zstd1 "github.com/klauspost/compress/zstd" - "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/crypto" @@ -33,7 +30,7 @@ type DABlock = codecv1.DABlock // DAChunk groups consecutive DABlocks with their transactions. type DAChunk = codecv1.DAChunk -// DAChunkRawTx groups consecutive DABlocks with their transactions. +// DAChunkRawTx groups consecutive DABlocks with their L2 transactions, L1 msgs are loaded in another place. type DAChunkRawTx = codecv1.DAChunkRawTx // DABatch contains metadata about a batch of DAChunks. @@ -232,10 +229,10 @@ func ConstructBlobPayload(chunks []*encoding.Chunk, useMockTxData bool) (*kzg484 // DecodeTxsFromBlob decodes txs from blob bytes and writes to chunks func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*DAChunkRawTx) error { - compressedBytes := codecv1.BytesFromBlobCanonical(blob) + compressedBytes := encoding.BytesFromBlobCanonical(blob) magics := []byte{0x28, 0xb5, 0x2f, 0xfd} - blobBytes, err := decompressScrollBatchBytes(append(magics, compressedBytes[:]...)) + blobBytes, err := encoding.DecompressScrollBatchBytes(append(magics, compressedBytes[:]...)) if err != nil { return err } @@ -404,32 +401,3 @@ func EstimateChunkL1CommitGas(c *encoding.Chunk) uint64 { func EstimateBatchL1CommitGas(b *encoding.Batch) uint64 { return codecv1.EstimateBatchL1CommitGas(b) } - -// decompressScrollBatchBytes decompresses the given bytes into scroll batch bytes -func decompressScrollBatchBytes(compressedBytes []byte) ([]byte, error) { - // decompress data in stream and in batches of bytes, because we don't know actual length of compressed data - var res []byte - readBatchSize := 131072 - batchOfBytes := make([]byte, readBatchSize) - - r := bytes.NewReader(compressedBytes) - zr, err := zstd1.NewReader(r) - if err != nil { - return nil, err - } - defer zr.Close() - - for { - i, err := zr.Read(batchOfBytes) - res = append(res, batchOfBytes[:i]...) // append already decoded bytes even if we meet error - // the error here is supposed to be EOF or similar that indicates that buffer has been read until the end - // we should return all data that read by this moment - if i < readBatchSize || err != nil { - break - } - } - if len(res) == 0 { - return nil, fmt.Errorf("failed to decompress blob bytes") - } - return res, nil -} diff --git a/encoding/codecv2/codecv2_test.go b/encoding/codecv2/codecv2_test.go index d530882..5456db4 100644 --- a/encoding/codecv2/codecv2_test.go +++ b/encoding/codecv2/codecv2_test.go @@ -7,16 +7,16 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "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/stretchr/testify/assert" - "github.com/stretchr/testify/require" "github.com/scroll-tech/da-codec/encoding" "github.com/scroll-tech/da-codec/encoding/codecv0" - "github.com/scroll-tech/da-codec/encoding/codecv1" "github.com/scroll-tech/da-codec/encoding/zstd" ) @@ -395,7 +395,7 @@ func TestCodecV2BatchDataHash(t *testing.T) { assert.Equal(t, "0x9b0f37c563d27d9717ab16d47075df996c54fe110130df6b11bfd7230e134767", batch.DataHash.Hex()) } -func TestCompressDecompress(t *testing.T) { +func TestCodecV2CompressDecompress(t *testing.T) { blobString := "00" + "0001" + "000000e6" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00" + "00" + "000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + "00000000" + // tx payload "00f87180843b9aec2e8307a12094c0c4c8baea3f6acb49b6e1fb9e2adeceeacb000ca28a152d02c7e14af60000008083019ecea0ab07ae99c67aa78e7ba5cf670081e90cc32b219b1de102513d56548a41e86df514a034cbd19feacd73e8ce6400d00c4d1996b9b5243c578fd7f51bfaec288bbaf42a8bf87101843b9aec2e830007a1209401bae6bf68e9a03fb2bc0615b1bf0d69ce9411ed8a152d02c7e14a00f60000008083019ecea0f039985866d8256f10c1be4f7b2cace28d8f20bde2007e2604393eb095b7f77316a05a3e6e81065f2b4604bcec5bd4aba68483599600fc3f879380aac1c09c6eed32f1" @@ -408,12 +408,12 @@ func TestCompressDecompress(t *testing.T) { blob, err := encoding.MakeBlobCanonical(compressed) assert.NoError(t, err) - res := codecv1.BytesFromBlobCanonical(blob) + res := encoding.BytesFromBlobCanonical(blob) compressedBytes := res[:] magics := []byte{0x28, 0xb5, 0x2f, 0xfd} compressedBytes = append(magics, compressedBytes...) - decompressedBlobBytes, err := decompressScrollBatchBytes(compressedBytes) + decompressedBlobBytes, err := encoding.DecompressScrollBatchBytes(compressedBytes) assert.NoError(t, err) assert.Equal(t, blobBytes, decompressedBlobBytes) diff --git a/encoding/codecv3/codecv3.go b/encoding/codecv3/codecv3.go index 1149ad5..da184ea 100644 --- a/encoding/codecv3/codecv3.go +++ b/encoding/codecv3/codecv3.go @@ -17,15 +17,13 @@ import ( // MaxNumChunks is the maximum number of chunks that a batch can contain. const MaxNumChunks = codecv2.MaxNumChunks -const BlockContextByteSize = codecv2.BlockContextByteSize - // DABlock represents a Data Availability Block. type DABlock = codecv2.DABlock // DAChunk groups consecutive DABlocks with their transactions. type DAChunk = codecv2.DAChunk -// DAChunkRawTx groups consecutive DABlocks with their transactions. +// DAChunkRawTx groups consecutive DABlocks with their L2 transactions, L1 msgs are loaded in another place. type DAChunkRawTx = codecv2.DAChunkRawTx // DABatch contains metadata about a batch of DAChunks. diff --git a/encoding/codecv3/codecv3_test.go b/encoding/codecv3/codecv3_test.go index fef0c12..2a917fd 100644 --- a/encoding/codecv3/codecv3_test.go +++ b/encoding/codecv3/codecv3_test.go @@ -7,12 +7,13 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "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/stretchr/testify/assert" - "github.com/stretchr/testify/require" "github.com/scroll-tech/da-codec/encoding" "github.com/scroll-tech/da-codec/encoding/codecv0" diff --git a/encoding/codecv4/codecv4.go b/encoding/codecv4/codecv4.go index 4c43ff0..7ad6f99 100644 --- a/encoding/codecv4/codecv4.go +++ b/encoding/codecv4/codecv4.go @@ -15,6 +15,7 @@ import ( "github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/da-codec/encoding" + "github.com/scroll-tech/da-codec/encoding/codecv1" "github.com/scroll-tech/da-codec/encoding/codecv3" "github.com/scroll-tech/da-codec/encoding/zstd" ) @@ -28,6 +29,9 @@ type DABlock = codecv3.DABlock // DAChunk groups consecutive DABlocks with their transactions. type DAChunk = codecv3.DAChunk +// DAChunkRawTx groups consecutive DABlocks with their L2 transactions, L1 msgs are loaded in another place. +type DAChunkRawTx = codecv3.DAChunkRawTx + // DABatch contains metadata about a batch of DAChunks. type DABatch struct { // header @@ -59,6 +63,11 @@ func NewDAChunk(chunk *encoding.Chunk, totalL1MessagePoppedBefore uint64) (*DACh return codecv3.NewDAChunk(chunk, totalL1MessagePoppedBefore) } +// DecodeDAChunksRawTx takes a byte slice and decodes it into a []*DAChunkRawTx. +func DecodeDAChunksRawTx(bytes [][]byte) ([]*DAChunkRawTx, error) { + return codecv3.DecodeDAChunksRawTx(bytes) +} + // NewDABatch creates a DABatch from the provided encoding.Batch. func NewDABatch(batch *encoding.Batch, enableCompress bool) (*DABatch, error) { // this encoding can only support a fixed number of chunks per batch @@ -239,6 +248,23 @@ func ConstructBlobPayload(chunks []*encoding.Chunk, enableCompress bool, useMock return blob, blobVersionedHash, &z, blobBytes, nil } +// DecodeTxsFromBlob decodes txs from blob bytes and writes to chunks +func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*DAChunkRawTx) error { + rawBytes := encoding.BytesFromBlobCanonical(blob) + + // if first byte is 1 - data compressed, 0 - not compressed + if rawBytes[0] == 0x1 { + magics := []byte{0x28, 0xb5, 0x2f, 0xfd} + blobBytes, err := encoding.DecompressScrollBatchBytes(append(magics, rawBytes[1:]...)) + if err != nil { + return err + } + return codecv1.DecodeTxsFromBytes(blobBytes, chunks, MaxNumChunks) + } else { + return codecv1.DecodeTxsFromBytes(rawBytes[1:], chunks, MaxNumChunks) + } +} + // NewDABatchFromBytes decodes 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) { diff --git a/encoding/codecv4/codecv4_test.go b/encoding/codecv4/codecv4_test.go index fa1eee0..a1b13cf 100644 --- a/encoding/codecv4/codecv4_test.go +++ b/encoding/codecv4/codecv4_test.go @@ -6,12 +6,13 @@ import ( "os" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "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/stretchr/testify/assert" - "github.com/stretchr/testify/require" "github.com/scroll-tech/da-codec/encoding" "github.com/scroll-tech/da-codec/encoding/codecv0" diff --git a/encoding/da.go b/encoding/da.go index 3adf08c..219e254 100644 --- a/encoding/da.go +++ b/encoding/da.go @@ -1,10 +1,13 @@ package encoding import ( + "bytes" "encoding/binary" "fmt" "math/big" + "github.com/klauspost/compress/zstd" + "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common/hexutil" "github.com/scroll-tech/go-ethereum/core/types" @@ -354,6 +357,44 @@ func MakeBlobCanonical(blobBytes []byte) (*kzg4844.Blob, error) { return &blob, nil } +// BytesFromBlobCanonical converts the canonical blob representation into the raw blob data +func BytesFromBlobCanonical(blob *kzg4844.Blob) [126976]byte { + var blobBytes [126976]byte + for from := 0; from < len(blob); from += 32 { + copy(blobBytes[from/32*31:], blob[from+1:from+32]) + } + return blobBytes +} + +// DecompressScrollBatchBytes decompresses the given bytes into scroll batch bytes +func DecompressScrollBatchBytes(compressedBytes []byte) ([]byte, error) { + // decompress data in stream and in batches of bytes, because we don't know actual length of compressed data + var res []byte + readBatchSize := 131072 + batchOfBytes := make([]byte, readBatchSize) + + r := bytes.NewReader(compressedBytes) + zr, err := zstd.NewReader(r) + if err != nil { + return nil, err + } + defer zr.Close() + + for { + i, err := zr.Read(batchOfBytes) + res = append(res, batchOfBytes[:i]...) // append already decoded bytes even if we meet error + // the error here is supposed to be EOF or similar that indicates that buffer has been read until the end + // we should return all data that read by this moment + if i < readBatchSize || err != nil { + break + } + } + if len(res) == 0 { + return nil, fmt.Errorf("failed to decompress blob bytes") + } + return res, nil +} + // CalculatePaddedBlobSize calculates the required size on blob storage // where every 32 bytes can store only 31 bytes of actual data, with the first byte being zero. func CalculatePaddedBlobSize(dataSize uint64) uint64 { diff --git a/encoding/da_test.go b/encoding/da_test.go index 1665b43..0481597 100644 --- a/encoding/da_test.go +++ b/encoding/da_test.go @@ -5,10 +5,11 @@ import ( "os" "testing" + "github.com/stretchr/testify/assert" + "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/log" - "github.com/stretchr/testify/assert" ) func TestMain(m *testing.M) { From eb9862657538b4d87441b60478cb79e88ed48106 Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Mon, 2 Sep 2024 12:22:43 +0200 Subject: [PATCH 17/17] address renaming nit-picks --- encoding/codecv1/codecv1.go | 4 ++-- encoding/codecv2/codecv2.go | 4 ++-- encoding/codecv2/codecv2_test.go | 2 +- encoding/codecv4/codecv4.go | 4 ++-- encoding/da.go | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/encoding/codecv1/codecv1.go b/encoding/codecv1/codecv1.go index e0bb749..25c6798 100644 --- a/encoding/codecv1/codecv1.go +++ b/encoding/codecv1/codecv1.go @@ -360,8 +360,8 @@ func DecodeTxsFromBytes(blobBytes []byte, chunks []*DAChunkRawTx, maxNumChunks i // DecodeTxsFromBlob decodes txs from blob bytes and writes to chunks func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*DAChunkRawTx) error { - blobBytes := encoding.BytesFromBlobCanonical(blob) - return DecodeTxsFromBytes(blobBytes[:], chunks, MaxNumChunks) + batchBytes := encoding.BytesFromBlobCanonical(blob) + return DecodeTxsFromBytes(batchBytes[:], chunks, MaxNumChunks) } var errSmallLength error = fmt.Errorf("length of blob bytes is too small") diff --git a/encoding/codecv2/codecv2.go b/encoding/codecv2/codecv2.go index ccfb3e6..dd00dc9 100644 --- a/encoding/codecv2/codecv2.go +++ b/encoding/codecv2/codecv2.go @@ -232,11 +232,11 @@ func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*DAChunkRawTx) error { compressedBytes := encoding.BytesFromBlobCanonical(blob) magics := []byte{0x28, 0xb5, 0x2f, 0xfd} - blobBytes, err := encoding.DecompressScrollBatchBytes(append(magics, compressedBytes[:]...)) + batchBytes, err := encoding.DecompressScrollBlobToBatch(append(magics, compressedBytes[:]...)) if err != nil { return err } - return codecv1.DecodeTxsFromBytes(blobBytes, chunks, MaxNumChunks) + return codecv1.DecodeTxsFromBytes(batchBytes, chunks, MaxNumChunks) } // NewDABatchFromBytes decodes the given byte slice into a DABatch. diff --git a/encoding/codecv2/codecv2_test.go b/encoding/codecv2/codecv2_test.go index 5456db4..69713d5 100644 --- a/encoding/codecv2/codecv2_test.go +++ b/encoding/codecv2/codecv2_test.go @@ -413,7 +413,7 @@ func TestCodecV2CompressDecompress(t *testing.T) { magics := []byte{0x28, 0xb5, 0x2f, 0xfd} compressedBytes = append(magics, compressedBytes...) - decompressedBlobBytes, err := encoding.DecompressScrollBatchBytes(compressedBytes) + decompressedBlobBytes, err := encoding.DecompressScrollBlobToBatch(compressedBytes) assert.NoError(t, err) assert.Equal(t, blobBytes, decompressedBlobBytes) diff --git a/encoding/codecv4/codecv4.go b/encoding/codecv4/codecv4.go index 7ad6f99..d1aa48c 100644 --- a/encoding/codecv4/codecv4.go +++ b/encoding/codecv4/codecv4.go @@ -255,11 +255,11 @@ func DecodeTxsFromBlob(blob *kzg4844.Blob, chunks []*DAChunkRawTx) error { // if first byte is 1 - data compressed, 0 - not compressed if rawBytes[0] == 0x1 { magics := []byte{0x28, 0xb5, 0x2f, 0xfd} - blobBytes, err := encoding.DecompressScrollBatchBytes(append(magics, rawBytes[1:]...)) + batchBytes, err := encoding.DecompressScrollBlobToBatch(append(magics, rawBytes[1:]...)) if err != nil { return err } - return codecv1.DecodeTxsFromBytes(blobBytes, chunks, MaxNumChunks) + return codecv1.DecodeTxsFromBytes(batchBytes, chunks, MaxNumChunks) } else { return codecv1.DecodeTxsFromBytes(rawBytes[1:], chunks, MaxNumChunks) } diff --git a/encoding/da.go b/encoding/da.go index 219e254..8ce6c35 100644 --- a/encoding/da.go +++ b/encoding/da.go @@ -366,8 +366,8 @@ func BytesFromBlobCanonical(blob *kzg4844.Blob) [126976]byte { return blobBytes } -// DecompressScrollBatchBytes decompresses the given bytes into scroll batch bytes -func DecompressScrollBatchBytes(compressedBytes []byte) ([]byte, error) { +// DecompressScrollBlobToBatch decompresses the given blob bytes into scroll batch bytes +func DecompressScrollBlobToBatch(compressedBytes []byte) ([]byte, error) { // decompress data in stream and in batches of bytes, because we don't know actual length of compressed data var res []byte readBatchSize := 131072