Skip to content

Commit

Permalink
Basic implementation of V2 assignment
Browse files Browse the repository at this point in the history
  • Loading branch information
mooselumph committed Aug 31, 2024
1 parent 206e951 commit c815321
Show file tree
Hide file tree
Showing 3 changed files with 249 additions and 16 deletions.
16 changes: 0 additions & 16 deletions core/assignment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,6 @@ func TestOperatorAssignments(t *testing.T) {
StartIndex: 15,
NumChunks: 6,
},
mock.MakeOperatorId(6): {
StartIndex: 21,
NumChunks: 3,
},
mock.MakeOperatorId(7): {
StartIndex: 14,
NumChunks: 3,
},
mock.MakeOperatorId(8): {
StartIndex: 17,
NumChunks: 4,
},
mock.MakeOperatorId(9): {
StartIndex: 21,
NumChunks: 4,
},
}
expectedInfo := core.AssignmentInfo{
TotalChunks: 21,
Expand Down
128 changes: 128 additions & 0 deletions core/assignment_v2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package core

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

type BlobParameters struct {
CodingRate uint
ReconstructionThreshold float64
NumChunks uint
}

var (
ParametersMap = map[byte]BlobParameters{
0: {CodingRate: 8, ReconstructionThreshold: 0.22, NumChunks: 8192},
}
)

// Implementation

// AssignmentCoordinator is responsible for taking the current OperatorState and the security requirements represented by a
// given QuorumResults and determining or validating system parameters that will satisfy these security requirements given the
// OperatorStates. There are two classes of parameters that must be determined or validated: 1) the chunk indices that will be
// assigned to each DA node, and 2) the length of each chunk.
type AssignmentCoordinatorV2 interface {

// GetAssignments calculates the full set of node assignments.
GetAssignments(state *OperatorState, blobVersion byte, quorum QuorumID) (map[OperatorID]Assignment, error)

// GetOperatorAssignment calculates the assignment for a specific DA node
GetAssignment(state *OperatorState, blobVersion byte, quorum QuorumID, id OperatorID) (Assignment, error)

// ValidateChunkLength validates that the chunk length for the given quorum satisfies all protocol constraints
GetChunkLength(blobVersion byte, blobLength uint) (uint, error)
}

type StdAssignmentCoordinatorV2 struct {
}

var _ AssignmentCoordinatorV2 = (*StdAssignmentCoordinatorV2)(nil)

func (c *StdAssignmentCoordinatorV2) GetAssignments(state *OperatorState, blobVersion byte, quorum QuorumID) (map[OperatorID]Assignment, error) {

params := ParametersMap[blobVersion]

n := big.NewInt(int64(len(state.Operators[quorum])))
m := big.NewInt(int64(params.NumChunks))

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

chunkAssignments := make([]assignment, 0, len(state.Operators[quorum]))
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)

// delta := new(big.Int).Sub(new(big.Int).Mul(r.Stake, m), new(big.Int).Mul(denom, chunks))

chunkAssignments = append(chunkAssignments, assignment{id: ID, index: r.Index, chunks: uint(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[OperatorID]Assignment, len(chunkAssignments))
index := uint(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 (c *StdAssignmentCoordinatorV2) GetAssignment(state *OperatorState, blobVersion byte, quorum QuorumID, id OperatorID) (Assignment, error) {

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

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

return assignment, nil
}

func (c *StdAssignmentCoordinatorV2) GetChunkLength(blobVersion byte, blobLength uint) (uint, error) {

return blobLength * ParametersMap[blobVersion].CodingRate / ParametersMap[blobVersion].NumChunks, nil

}
121 changes: 121 additions & 0 deletions core/assignment_v2_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package core_test

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

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

func TestOperatorAssignmentsV2(t *testing.T) {

state := dat.GetTotalOperatorState(context.Background(), 0)
operatorState := state.OperatorState
coordinator := &core.StdAssignmentCoordinatorV2{}

blobVersion := byte(0)

assignments, err := coordinator.GetAssignments(operatorState, blobVersion, 0)
assert.NoError(t, err)
expectedAssignments := map[core.OperatorID]core.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 := coordinator.GetAssignment(operatorState, blobVersion, 0, operatorID)
assert.NoError(t, err)

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

}

}

func FuzzOperatorAssignmentsV2(f *testing.F) {

// Add distributions to fuzz
asn := &core.StdAssignmentCoordinatorV2{}

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

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

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 := asn.GetAssignments(state.OperatorState, blobVersion, 0)
assert.NoError(t, err)

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

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

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

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

assert.GreaterOrEqual(t, LHS, RHS)

}

})

}

0 comments on commit c815321

Please sign in to comment.