Skip to content

Commit

Permalink
Add core/v2
Browse files Browse the repository at this point in the history
  • Loading branch information
mooselumph committed Oct 21, 2024
1 parent 1475cd9 commit d7ab758
Show file tree
Hide file tree
Showing 8 changed files with 944 additions and 1 deletion.
4 changes: 3 additions & 1 deletion core/mock/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mock

import (
"context"
"encoding/binary"
"fmt"
"math/big"
"sort"
Expand Down Expand Up @@ -36,7 +37,8 @@ type PrivateOperatorState struct {
}

func MakeOperatorId(id int) core.OperatorID {
data := [32]byte{uint8(id)}
var data [32]byte
binary.LittleEndian.PutUint64(data[:8], uint64(id))
return data
}

Expand Down
123 changes: 123 additions & 0 deletions core/v2/assignment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package corev2

import (
"fmt"
"math/big"
"sort"

"github.com/Layr-Labs/eigenda/core"
)

func GetAssignments(state *core.OperatorState, blobVersion byte, quorum uint8) (map[core.OperatorID]Assignment, error) {

params, ok := ParametersMap[blobVersion]
if !ok {
return nil, fmt.Errorf("blob version %d not found", blobVersion)
}

ops, ok := state.Operators[quorum]
if !ok {
return nil, fmt.Errorf("no operators found for quorum %d", quorum)
}

if len(ops) > int(params.MaxNumOperators()) {
return nil, fmt.Errorf("too many operators for blob version %d", blobVersion)
}

n := big.NewInt(int64(len(ops)))
m := big.NewInt(int64(params.NumChunks))

type assignment struct {
id core.OperatorID
index uint32
chunks uint32
stake *big.Int
}

chunkAssignments := make([]assignment, 0, len(ops))
for ID, r := range state.Operators[quorum] {

num := new(big.Int).Mul(r.Stake, new(big.Int).Sub(m, n))
denom := state.Totals[quorum].Stake

chunks := RoundUpDivideBig(num, denom)

chunkAssignments = append(chunkAssignments, assignment{id: ID, index: uint32(r.Index), chunks: uint32(chunks.Uint64()), stake: r.Stake})
}

// Sort chunk decreasing by stake or operator ID in case of a tie
sort.Slice(chunkAssignments, func(i, j int) bool {
if chunkAssignments[i].stake.Cmp(chunkAssignments[j].stake) == 0 {
return chunkAssignments[i].index < chunkAssignments[j].index
}
return chunkAssignments[i].stake.Cmp(chunkAssignments[j].stake) == 1
})

mp := 0
for _, a := range chunkAssignments {
mp += int(a.chunks)
}

delta := int(params.NumChunks) - mp
if delta < 0 {
return nil, fmt.Errorf("total chunks %d exceeds maximum %d", mp, params.NumChunks)
}

assignments := make(map[core.OperatorID]Assignment, len(chunkAssignments))
index := uint32(0)
for i, a := range chunkAssignments {
if i < delta {
a.chunks++
}

assignment := Assignment{
StartIndex: index,
NumChunks: a.chunks,
}

assignments[a.id] = assignment
index += a.chunks
}

return assignments, nil

}

func GetAssignment(state *core.OperatorState, blobVersion byte, quorum core.QuorumID, id core.OperatorID) (Assignment, error) {

assignments, err := GetAssignments(state, blobVersion, quorum)
if err != nil {
return Assignment{}, err
}

assignment, ok := assignments[id]
if !ok {
return Assignment{}, ErrNotFound
}

return assignment, nil
}

func GetChunkLength(blobVersion byte, blobLength uint32) (uint32, error) {

if blobLength == 0 {
return 0, fmt.Errorf("blob length must be greater than 0")
}

// Check that the blob length is a power of 2
if blobLength&(blobLength-1) != 0 {
return 0, fmt.Errorf("blob length %d is not a power of 2", blobLength)
}

if _, ok := ParametersMap[blobVersion]; !ok {
return 0, fmt.Errorf("blob version %d not found", blobVersion)
}

chunkLength := blobLength * ParametersMap[blobVersion].CodingRate / ParametersMap[blobVersion].NumChunks
if chunkLength == 0 {
chunkLength = 1
}

return chunkLength, nil

}
220 changes: 220 additions & 0 deletions core/v2/assignment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
package corev2_test

import (
"context"
"math/rand"
"testing"

"github.com/Layr-Labs/eigenda/core"
"github.com/Layr-Labs/eigenda/core/mock"
corev2 "github.com/Layr-Labs/eigenda/core/v2"
"github.com/stretchr/testify/assert"
)

const (
maxNumOperators = 3537
)

func TestOperatorAssignmentsV2(t *testing.T) {

state := dat.GetTotalOperatorState(context.Background(), 0)
operatorState := state.OperatorState

blobVersion := byte(0)

assignments, err := corev2.GetAssignments(operatorState, blobVersion, 0)
assert.NoError(t, err)
expectedAssignments := map[core.OperatorID]corev2.Assignment{
mock.MakeOperatorId(0): {
StartIndex: 7802,
NumChunks: 390,
},
mock.MakeOperatorId(1): {
StartIndex: 7022,
NumChunks: 780,
},
mock.MakeOperatorId(2): {
StartIndex: 5852,
NumChunks: 1170,
},
mock.MakeOperatorId(3): {
StartIndex: 4291,
NumChunks: 1561,
},
mock.MakeOperatorId(4): {
StartIndex: 2340,
NumChunks: 1951,
},
mock.MakeOperatorId(5): {
StartIndex: 0,
NumChunks: 2340,
},
}

for operatorID, assignment := range assignments {

assert.Equal(t, assignment, expectedAssignments[operatorID])

assignment, err := corev2.GetAssignment(operatorState, blobVersion, 0, operatorID)
assert.NoError(t, err)

assert.Equal(t, assignment, expectedAssignments[operatorID])

}

}

func TestMaxNumOperators(t *testing.T) {

assert.Equal(t, corev2.ParametersMap[0].MaxNumOperators(), uint32(maxNumOperators))

}

func TestAssignmentWithTooManyOperators(t *testing.T) {

numOperators := maxNumOperators + 1

stakes := map[core.QuorumID]map[core.OperatorID]int{
0: {},
}
for i := 0; i < numOperators; i++ {
stakes[0][mock.MakeOperatorId(i)] = rand.Intn(100) + 1
}

dat, err := mock.NewChainDataMock(stakes)
if err != nil {
t.Fatal(err)
}

state := dat.GetTotalOperatorState(context.Background(), 0)

assert.Equal(t, len(state.Operators[0]), numOperators)

blobVersion := byte(0)

_, err = corev2.GetAssignments(state.OperatorState, blobVersion, 0)
assert.Error(t, err)

}

func FuzzOperatorAssignmentsV2(f *testing.F) {

// Add distributions to fuzz

for i := 1; i < 100; i++ {
f.Add(i)
}

for i := 0; i < 100; i++ {
f.Add(rand.Intn(2048) + 100)
}

for i := 0; i < 5; i++ {
f.Add(maxNumOperators)
}

f.Fuzz(func(t *testing.T, numOperators int) {

// Generate a random slice of integers of length n

stakes := map[core.QuorumID]map[core.OperatorID]int{
0: {},
}
for i := 0; i < numOperators; i++ {
stakes[0][mock.MakeOperatorId(i)] = rand.Intn(100) + 1
}

dat, err := mock.NewChainDataMock(stakes)
if err != nil {
t.Fatal(err)
}

state := dat.GetTotalOperatorState(context.Background(), 0)

blobVersion := byte(0)

assignments, err := corev2.GetAssignments(state.OperatorState, blobVersion, 0)
assert.NoError(t, err)

// Check that the total number of chunks is correct
totalChunks := uint32(0)
for _, assignment := range assignments {
totalChunks += assignment.NumChunks
}
assert.Equal(t, totalChunks, corev2.ParametersMap[blobVersion].NumChunks)

// Check that each operator's assignment satisfies the security requirement
for operatorID, assignment := range assignments {

totalStake := uint32(state.Totals[0].Stake.Uint64())
myStake := uint32(state.Operators[0][operatorID].Stake.Uint64())

LHS := assignment.NumChunks * totalStake * corev2.ParametersMap[blobVersion].CodingRate * uint32(corev2.ParametersMap[blobVersion].ReconstructionThreshold*100)
RHS := 100 * myStake * corev2.ParametersMap[blobVersion].NumChunks

assert.GreaterOrEqual(t, LHS, RHS)

}

})

}

func TestChunkLength(t *testing.T) {

blobVersion := byte(0)

pairs := []struct {
blobLength uint32
chunkLength uint32
}{
{512, 1},
{1024, 1},
{2048, 2},
{4096, 4},
{8192, 8},
}

for _, pair := range pairs {

chunkLength, err := corev2.GetChunkLength(blobVersion, pair.blobLength)

assert.NoError(t, err)

assert.Equal(t, pair.chunkLength, chunkLength)
}

}

func TestInvalidChunkLength(t *testing.T) {

blobVersion := byte(0)

invalidLengths := []uint32{
0,
3,
5,
6,
7,
9,
10,
11,
12,
13,
14,
15,
31,
63,
127,
255,
511,
1023,
}

for _, length := range invalidLengths {

_, err := corev2.GetChunkLength(blobVersion, length)
assert.Error(t, err)
}

}
Loading

0 comments on commit d7ab758

Please sign in to comment.