Skip to content

Commit

Permalink
[4/N] Chunk encoding optimization: Add bundle encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
jianoaix committed Jul 9, 2024
1 parent 52f1293 commit f43f6ae
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 0 deletions.
65 changes: 65 additions & 0 deletions core/data.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package core

import (
"encoding/binary"
"errors"
"fmt"

"github.com/Layr-Labs/eigenda/common"
"github.com/Layr-Labs/eigenda/encoding"
"github.com/consensys/gnark-crypto/ecc/bn254"
)

type AccountID = string
Expand Down Expand Up @@ -33,6 +35,8 @@ const (
// which means the max ID can not be larger than 254 (from 0 to 254, there are 255
// different IDs).
MaxQuorumID = 254

GnarkBundleEncodingFormat = 1
)

func (s *SecurityParam) String() string {
Expand Down Expand Up @@ -165,6 +169,67 @@ func (b Bundle) Size() uint64 {
return size
}

func (b Bundle) Serialize() ([]byte, error) {
if len(b) == 0 {
return []byte{}, nil
}
if len(b[0].Coeffs) == 0 {
return nil, errors.New("invalid bundle: the coeffs length is zero")
}
size := 0
for _, f := range b {
if len(f.Coeffs) != len(b[0].Coeffs) {
return nil, errors.New("invalid bundle: all chunks should have the same length")
}
size += bn254.SizeOfG1AffineCompressed + encoding.BYTES_PER_SYMBOL*len(f.Coeffs)
}
result := make([]byte, size+8)
buf := result
metadata := uint64(GnarkBundleEncodingFormat) | (uint64(len(b[0].Coeffs)) << 8)
binary.LittleEndian.PutUint64(buf, metadata)
buf = buf[8:]
for _, f := range b {
chunk, err := f.SerializeGnark()
if err != nil {
return nil, err
}
copy(buf, chunk)
buf = buf[len(chunk):]
}
return result, nil
}

func (b Bundle) Deserialize(data []byte) (Bundle, error) {
if len(data) < 8 {
return nil, errors.New("bundle data must have at least 8 bytes")
}
// Parse metadata
meta := binary.LittleEndian.Uint64(data)
if (meta & 0xFF) != GnarkBundleEncodingFormat {
return nil, errors.New("invalid bundle data encoding format")
}
chunkLen := meta >> 8
chunkSize := bn254.SizeOfG1AffineCompressed + encoding.BYTES_PER_SYMBOL*int(chunkLen)
if (len(data)-8)%chunkSize != 0 {
return nil, errors.New("bundle data is invalid")
}
// Decode
bundle := make([]*encoding.Frame, 0, (len(data)-8)/chunkSize)
buf := data[8:]
for len(buf) > 0 {
if len(buf) < chunkSize {
return nil, errors.New("bundle data is invalid")
}
f, err := new(encoding.Frame).DeserializeGnark(buf[:chunkSize])
if err != nil {
return nil, err
}
bundle = append(bundle, f)
buf = buf[chunkSize:]
}
return bundle, nil
}

// Serialize encodes a batch of chunks into a byte array
func (cb Bundles) Serialize() (map[uint32][][]byte, error) {
data := make(map[uint32][][]byte, len(cb))
Expand Down
89 changes: 89 additions & 0 deletions core/data_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package core_test

import (
"math/rand"
"testing"

"github.com/Layr-Labs/eigenda/core"
"github.com/Layr-Labs/eigenda/encoding"
"github.com/consensys/gnark-crypto/ecc/bn254/fp"
"github.com/consensys/gnark-crypto/ecc/bn254/fr"
"github.com/stretchr/testify/assert"
)

func createBundle(t *testing.T, numFrames, numCoeffs, seed int) core.Bundle {
var XCoord, YCoord fp.Element
_, err := XCoord.SetString("21661178944771197726808973281966770251114553549453983978976194544185382599016")
assert.NoError(t, err)
_, err = YCoord.SetString("9207254729396071334325696286939045899948985698134704137261649190717970615186")
assert.NoError(t, err)
r := rand.New(rand.NewSource(int64(seed)))
frames := make([]*encoding.Frame, numFrames)
for n := 0; n < numFrames; n++ {
frames[n] = new(encoding.Frame)
frames[n].Proof = encoding.Proof{
X: XCoord,
Y: YCoord,
}
for i := 0; i < numCoeffs; i++ {
frames[n].Coeffs = append(frames[n].Coeffs, fr.NewElement(r.Uint64()))
}
}
return frames
}

func TestInvalidBundleSer(t *testing.T) {
b1 := createBundle(t, 1, 0, 0)
_, err := b1.Serialize()
assert.EqualError(t, err, "invalid bundle: the coeffs length is zero")

b2 := createBundle(t, 1, 1, 0)
b3 := createBundle(t, 1, 2, 0)
b3 = append(b3, b2...)
_, err = b3.Serialize()
assert.EqualError(t, err, "invalid bundle: all chunks should have the same length")
}

func TestInvalidBundleDeser(t *testing.T) {
tooSmallBytes := []byte{byte(0b01000000)}
_, err := new(core.Bundle).Deserialize(tooSmallBytes)
assert.EqualError(t, err, "bundle data must have at least 8 bytes")

invalidBundle := make([]byte, 0, 8)
for i := 0; i < 7; i++ {
invalidBundle = append(invalidBundle, byte(0))
}
invalidBundle = append(invalidBundle, byte(0b01000000))
_, err = new(core.Bundle).Deserialize(invalidBundle)
assert.EqualError(t, err, "invalid bundle data encoding format")

data := make([]byte, 0, 9)
data = append(data, byte(1))
for i := 0; i < 6; i++ {
data = append(data, byte(0))
}
data = append(data, byte(0b00100000))
data = append(data, byte(5))
data = append(data, byte(0b01000000))
_, err = new(core.Bundle).Deserialize(data)
assert.EqualError(t, err, "bundle data is invalid")
}

func TestBundleEncoding(t *testing.T) {
numTrials := 16
for i := 0; i < numTrials; i++ {
bundle := createBundle(t, 64, 64, i)
bytes, err := bundle.Serialize()
assert.Nil(t, err)
decoded, err := new(core.Bundle).Deserialize(bytes)
assert.Nil(t, err)
assert.Equal(t, len(bundle), len(decoded))
for i := 0; i < len(bundle); i++ {
assert.True(t, bundle[i].Proof.Equal(&decoded[i].Proof))
assert.Equal(t, len(bundle[i].Coeffs), len(decoded[i].Coeffs))
for j := 0; j < len(bundle[i].Coeffs); j++ {
assert.True(t, bundle[i].Coeffs[j].Equal(&decoded[i].Coeffs[j]))
}
}
}
}

0 comments on commit f43f6ae

Please sign in to comment.