From c815321e9c0f0687f563eb0ffc62477f4148c89d Mon Sep 17 00:00:00 2001 From: Robert Raynor <35671663+mooselumph@users.noreply.github.com> Date: Sat, 31 Aug 2024 00:23:03 +0000 Subject: [PATCH] Basic implementation of V2 assignment --- core/assignment_test.go | 16 ----- core/assignment_v2.go | 128 +++++++++++++++++++++++++++++++++++++ core/assignment_v2_test.go | 121 +++++++++++++++++++++++++++++++++++ 3 files changed, 249 insertions(+), 16 deletions(-) create mode 100644 core/assignment_v2.go create mode 100644 core/assignment_v2_test.go diff --git a/core/assignment_test.go b/core/assignment_test.go index 656f03952f..1d45a312d3 100644 --- a/core/assignment_test.go +++ b/core/assignment_test.go @@ -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, diff --git a/core/assignment_v2.go b/core/assignment_v2.go new file mode 100644 index 0000000000..4c5fdc3dad --- /dev/null +++ b/core/assignment_v2.go @@ -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 + +} diff --git a/core/assignment_v2_test.go b/core/assignment_v2_test.go new file mode 100644 index 0000000000..490b5c3368 --- /dev/null +++ b/core/assignment_v2_test.go @@ -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) + + } + + }) + +}