-
Notifications
You must be signed in to change notification settings - Fork 175
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1475cd9
commit d7ab758
Showing
8 changed files
with
944 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
|
||
} |
Oops, something went wrong.