|
1 | 1 | ## Assignment Module |
2 | 2 |
|
3 | | -> Warning: this page describes the assignment logic for EigenDA V1. We need to update it with Blazar assignment logic which is very different. |
| 3 | +The assignment module determines how encoded blob chunks are allocated to validators based on the Ethereum chain state, specifically validator stakes and quorum memberships. Given the validator state and blob parameters, it produces a deterministic mapping from validators to chunk indices. The mapping ensures that a sufficient number of signatures and honest validators implies that data is available. |
4 | 4 |
|
5 | | -The assignment module is essentially a rule which takes in the Ethereum chain state and outputs an allocation of chunks to DA operators. This can be generalized to a function that outputs a set of valid allocations. |
6 | | - |
7 | | -A chunk assignment has the following parameters: |
8 | | -1) **Indices**: the chunk indices that will be assigned to each DA node. Some DA nodes receive more than one chunk. |
9 | | -2) **ChunkLength**: the length of each chunk (measured in number of symbols, as defined by the encoding module). We currently require all chunks to be of the same length, so this parameter is a scalar. |
10 | | - |
11 | | -The assignment module is implemented by the `AssignmentCoordinator` interface. |
| 5 | +The assignment module is implemented in `core/v2/assignment.go`. For blobs dispersed to multiple quorums, the algorithm employs overlap optimization to minimize storage requirements while maintaining security guarantees. |
12 | 6 |
|
13 | 7 |  |
14 | 8 |
|
| 9 | +### Chunk Assignment Algorithm within One Quorum |
15 | 10 |
|
16 | | -### Chunk Assignment Scheme Overview |
17 | | - |
18 | | -Our chunk assignment scheme (CAS) assigns encoded chunks to validators proportionally to their stake, ensuring that any coalition of validators with sufficient combined stake can reconstruct the blob. Given: |
| 11 | +The chunk assignment scheme assigns encoded chunks to validators proportionally to their stake, ensuring that any coalition of validators with sufficient combined stake can reconstruct the blob. |
19 | 12 |
|
| 13 | +Given: |
20 | 14 | - A set of $n$ validators with stakes $\eta_1, \eta_2, \ldots, \eta_n$, where $\sum_{i=1}^n \eta_i = 1$ |
21 | 15 | - A set of $c$ chunks to be assigned to the validators |
22 | 16 |
|
23 | | -The assignment algorithm `GetAssignments` works as follows: |
| 17 | +Within a single quorum, the number of chunks assigned to validator $i$ is: |
| 18 | +```math |
| 19 | +c_i = \lceil \eta_i(c - n) \rceil |
| 20 | +``` |
| 21 | +This assignment ensures that the total number of assigned chunks is less than or equal to the total number of chunks $c$, since $\sum_{i=1}^n c_i = \sum_{i=1}^n \lceil \eta_i(c - n) \rceil \le \sum_{i=1}^n [\eta_i(c - n) + 1] = c$. |
24 | 22 |
|
25 | | -1. **Initial allocation:** For each validator $i$, calculate the base number of chunks: |
26 | | - $$ |
27 | | - c'_i = \lceil \eta_i(c - n) \rceil |
28 | | - $$ |
| 23 | +This guarantees that the chunks assigned to validators within a quorum are **non-overlapping**. In other words, each validator in a quorum contributes **distinct chunks** for reconstruction. The proof that any subset of validators with sufficient combined stake can reconstruct the blob is provided in [Security Parameters](./security-parameters.md). |
29 | 24 |
|
30 | | -2. **Total initial assignment:** Compute |
31 | | - $$ |
32 | | - c' = \sum_{i=1}^n c'_i |
33 | | - $$ |
| 25 | +### Chunk Assignment for Multiple Quorums |
34 | 26 |
|
35 | | -3. **Sort validators:** Order validators deterministically and assign index $k_i$ to each validator $i$. |
| 27 | +EigenDA supports blobs dispersed to multiple quorums simultaneously. The security threshold is guaranteed to hold for each quorum independently, as shown in the previous section. The multi-quorum assignment algorithm minimizes storage requirements through overlap optimization while maintaining security guarantees. |
36 | 28 |
|
37 | | -4. **Final assignment:** The number of chunks assigned to validator $i$ is: |
38 | | - $$ |
39 | | - c_i = c'_i + \mathbb{I}_{k_i \leq c - c'} |
40 | | - $$ |
41 | | - where $\mathbb{I}$ is the indicator function that adds one extra chunk to the first $c - c'$ validators to ensure the total number of assigned chunks equals $c$. |
| 29 | +#### Storage Optimization Strategy |
42 | 30 |
|
43 | | -We will prove that any subset of validators with sufficient combined stake can reconstruct the blob in [Security Parameters](./security-parameters.md). |
| 31 | +The assignment algorithm uses two key strategies to minimize storage: |
44 | 32 |
|
45 | | -### Assignment Logic |
| 33 | +1. **Chunk Overlap Maximization:** When a validator participates in multiple quorums for the same blob, the algorithm reuses the same chunk indices across quorums whenever possible. |
46 | 34 |
|
47 | | -The standard assignment coordinator implements a very simple logic for determining the number of chunks per node and the chunk length, which we describe here. |
| 35 | +2. **Reconstruction Capping:** Each validator is assigned at most the number of chunks needed to independently reconstruct the blob. |
48 | 36 |
|
49 | | -**Chunk Length** |
| 37 | +**Example:** Consider a validator with 5% stake in quorum 0 and 15% stake in quorum 1. Without optimization, the validator might receive two non-overlapping sets of chunks (one per quorum), totaling up to 20% of all chunks. With overlap optimization, the validator stores only `max(chunks_quorum_0, chunks_quorum_1)` unique chunks, which is 15% of the total chunks. With reconstruction capping, if the [coding rate](./security-parameters.md#blob-parameters) is $\gamma = 1/8$, the validator only needs to store 1/8 of the total chunks. |
50 | 38 |
|
51 | | -Chunk lengths must be sufficiently small that operators with a small proportion of stake will be able to receive a quantity of data commensurate with their stake share. For each operator $i$, let $S_i$ signify the amount of stake held by that operator. |
| 39 | +#### Algorithm Components |
52 | 40 |
|
53 | | -We require that the chunk size $C$ satisfy |
| 41 | +The multi-quorum assignment algorithm consists of four key functions: |
54 | 42 |
|
55 | | -$$ |
56 | | -C \le \text{NextPowerOf2}\left(\frac{B}{\gamma}\max\left(\frac{\min_jS_j}{\sum_jS_j}, \frac{1}{M_\text{max}} \right) \right) |
57 | | -$$ |
| 43 | +**1. GetAssignmentsForQuorum:** Calculates assignments for a single quorum independently using the stake-proportional algorithm described above. |
58 | 44 |
|
| 45 | +**2. AddAssignmentsForQuorum:** Generates the assignment for a new quorum while maximizing overlap with a baseline quorum assignment through a two-phase process: |
59 | 46 |
|
60 | | -where $\gamma = \beta-\alpha$, with $\alpha$ and $\beta$ the adversary and quorum thresholds as defined in the [Overview](../overview.md). |
| 47 | +- **Phase 1 (Overlap Maximization):** For each validator, reuse as many chunk indices as possible from the baseline quorum assignment, up to the number required for the new quorum. Mark these reused indices as "used." |
61 | 48 |
|
62 | | -This means that as long as an operator has a stake share of at least $1/M_\text{max}$, then the encoded data that they will receive will be within a factor of 2 of their share of stake. Operators with less than $1/M_\text{max}$ of stake will receive no more than a $1/M_\text{max}$ of the encoded data. $M_\text{max}$ represents the maximum number of chunks that the disperser can be required to encode per blob. This limit is included because proving costs scale somewhat super-linearly with the number of chunks. |
| 49 | +- **Phase 2 (Gap Filling):** Distribute the remaining unused chunk indices to validators who need additional chunks beyond what was reused from the baseline, ensuring each validator receives their stake-proportional allocation in the new quorum. |
63 | 50 |
|
64 | | -In the future, additional constraints on chunk length may be added; for instance, the chunk length may be set in order to maintain a fixed number of chunks per blob across all system states. Currently, the protocol does not mandate a specific value for the chunk length, but will accept the range satisfying the above constraint. The `CalculateChunkLength` function is provided as a convenience function that can be used to find a chunk length satisfying the protocol requirements. |
| 51 | +This algorithm guarantees that validators participating in both quorums store only `max(chunks_in_quorum_1, chunks_in_quorum_2)` unique chunks rather than the sum. |
65 | 52 |
|
66 | | -**Index Assignment** |
| 53 | +**3. MergeAssignmentsAndCap:** Merges assignments across all quorums and caps the total at the reconstruction threshold: |
| 54 | +```math |
| 55 | +\text{max\_chunks} = c \cdot \gamma |
| 56 | +``` |
| 57 | + where $c$ is the total number of chunks and $\gamma$ is the [coding rate](./security-parameters.md#blob-parameters). This cap exists because once a validator has enough unique chunks to reconstruct the blob, additional chunks provide no incremental security benefit. Therefore, pruning the extra chunks improves performance and reduces storage and bandwidth requirements without affecting security. |
67 | 58 |
|
68 | | -For each operator $i$, let $S_i$ signify the amount of stake held by that operator. We want for the number of chunks assigned to operator $i$ to satisfy |
| 59 | +**4. GetAssignmentsForBlob:** Coordinates the full multi-quorum assignment process: |
| 60 | +1. Generate the assignment for quorum 0 using `GetAssignmentsForQuorum` |
| 61 | +2. Generate assignments for all other quorums using `AddAssignmentsForQuorum` with quorum 0 as the baseline |
| 62 | +3. Merge all per-quorum assignments using `MergeAssignmentsAndCap` to produce the final assignment for each validator |
69 | 63 |
|
70 | | -$$ |
71 | | -\frac{\gamma m_i C}{B} \ge \frac{S_i}{\sum_j S_j} |
72 | | -$$ |
| 64 | +**Note on Optimality:** The algorithm produces optimal storage assignments for two quorums. For three or more quorums, the assignment is not guaranteed to be globally optimal. Since quorums 0 and 1 are the "default" quorums and are expected to be the larger than custom quorums (i.e. containing the most validators), the algorithm achieves near-optimal storage reduction for the majority of validators. |
73 | 65 |
|
74 | | -Let |
| 66 | +### Code Walkthrough |
| 67 | +Notation note: In the code, we sometimes use the term `operator` to refer to a `validator`, although `validator` is now the preferred term. |
75 | 68 |
|
76 | | -$$ |
77 | | -m_i = \text{ceil}\left(\frac{B S_i}{C\gamma \sum_j S_j}\right)\tag{1} |
78 | | -$$ |
| 69 | +**Location:** `core/v2/assignment.go` |
79 | 70 |
|
80 | | -**Correctness** |
81 | | -Let's show that any sets $U_q$ and $U_a$ satisfying the constraints in the [Consensus Layer Overview](../overview.md#consensus-layer), the data held by the operators $U_q \setminus U_a$ will constitute an entire blob. The amount of data held by these operators is given by |
| 71 | +**Data Structure:** |
| 72 | +```go |
| 73 | +type Assignment struct { |
| 74 | + Indices []uint32 // Explicit list of chunk indices (non-contiguous) |
| 75 | +} |
| 76 | +``` |
82 | 77 |
|
83 | | -$$ |
84 | | -\sum_{i \in U_q \setminus U_a} m_i C |
85 | | -$$ |
| 78 | +**Core Functions:** |
86 | 79 |
|
87 | | -We have from (1) and from the definitions of $U_q$ and $U_a$ that |
| 80 | +**1. GetAssignmentsForQuorum (`core/v2/assignment.go:40-90`)** |
88 | 81 |
|
89 | | -$$ |
90 | | -\sum_{i \in U_q \setminus U_a} m_i C \ge =\frac{B}{\gamma}\sum_{i \in U_q \setminus U_a}\frac{S_i}{\sum_j S_j} = \frac{B}{\gamma}\frac{\sum_{i \in U_q} S_i - \sum_{i \in U_a} S_i}{\sum_jS_j} \ge B \frac{\beta-\alpha}{\gamma} = B \tag{2} |
91 | | -$$ |
| 82 | +Assigns chunks for a single quorum with deterministic ordering: |
92 | 83 |
|
93 | | -Since the unique data held by these operators exceeds the size of a blob, the encoding module ensures that the original blob can be reconstructed from this data. |
| 84 | +```go |
| 85 | +func GetAssignmentsForQuorum( |
| 86 | + state *core.OperatorState, |
| 87 | + blobParams *core.BlobVersionParameters, |
| 88 | + quorum core.QuorumID, |
| 89 | +) (map[core.OperatorID]*Assignment, []core.OperatorID, error) |
| 90 | +``` |
94 | 91 |
|
| 92 | +Algorithm: |
| 93 | +1. Sort operators by hex ID for determinism |
| 94 | +2. Calculate effective chunks: `effectiveNumChunks = NumChunks - MaxNumOperators` |
| 95 | +3. For each operator $i$: `chunksForOperator = ceil((effectiveNumChunks × stake_i) / totalStake)` |
| 96 | +4. Assign contiguous indices starting from offset 0 |
| 97 | +5. Return assignments and ordered operator list |
95 | 98 |
|
96 | | -## Validation Actions |
| 99 | +**2. AddAssignmentsForQuorum (`core/v2/assignment.go:99-161`)** |
97 | 100 |
|
98 | | -Validation with respect to assignments is performed at different layers of the protocol: |
| 101 | +Maximizes overlap with a baseline assignment: |
99 | 102 |
|
100 | | -### DA Nodes |
| 103 | +```go |
| 104 | +func AddAssignmentsForQuorum( |
| 105 | + assignments map[core.OperatorID]*Assignment, // Baseline from first quorum |
| 106 | + state *core.OperatorState, |
| 107 | + blobParams *core.BlobVersionParameters, |
| 108 | + quorum core.QuorumID, |
| 109 | +) (map[core.OperatorID]*Assignment, error) |
| 110 | +``` |
101 | 111 |
|
102 | | -When the DA node receives a `StoreChunks` request, it performs the following validation actions relative to each blob header: |
103 | | -- It uses the `ValidateChunkLength` to validate that the `ChunkLength` for the blob satisfies the above constraints. |
104 | | -- It uses `GetOperatorAssignment` to calculate the chunk indices for which it is responsible, and verifies that each of the chunks that it has received lies on the polynomial at these indices (see [Encoding validation actions](./encoding.md#validation-actions)) |
| 112 | +Two-phase algorithm: |
| 113 | +- **Phase 1 (Lines 115-136):** For each operator, reuse indices from baseline up to their allotted count for this quorum |
| 114 | +- **Phase 2 (Lines 145-158):** Distribute unused indices to operators needing more chunks |
105 | 115 |
|
106 | | -This step ensures that each honest node has received the blobs for which it is accountable. |
| 116 | +**3. MergeAssignmentsAndCap (`core/v2/assignment.go:167-220`)** |
107 | 117 |
|
108 | | -Since the DA nodes will allow a range of `ChunkLength` values, as long as they satisfy the constraints of the protocol, it is necessary for there to be consensus on the `ChunkLength` that is in use for a particular blob and quorum. For this reason, the `ChunkLength` is included in the `BlobQuorumParam` which is hashed to create the merkle root contained in the `BatchHeaderHash` signed by the DA nodes. |
| 118 | +```go |
| 119 | +func MergeAssignmentsAndCap( |
| 120 | + assignments []map[core.OperatorID]*Assignment, |
| 121 | + blobParams *core.BlobVersionParameters, |
| 122 | +) map[core.OperatorID]Assignment |
| 123 | +``` |
109 | 124 |
|
110 | | -### Rollup Smart Contract |
| 125 | +Merges all quorum assignments and caps at `maxChunks = NumChunks / CodingRate` |
111 | 126 |
|
112 | | -When the rollup confirms its blob against the EigenDA batch, it checks that the `ConfirmationThreshold` for the blob is greater than the `AdversaryThreshold`. This means that if the `ChunkLength` determined by the disperser is invalid, the batch cannot be confirmed as a sufficient number of nodes will not sign. |
| 127 | +**4. GetAssignmentsForBlob (`core/v2/assignment.go:227-266`)** |
| 128 | + |
| 129 | +Main entry point coordinating the full multi-quorum assignment: |
| 130 | + |
| 131 | +```go |
| 132 | +func GetAssignmentsForBlob( |
| 133 | + state *core.OperatorState, |
| 134 | + blobParams *core.BlobVersionParameters, |
| 135 | + quorums []core.QuorumID, |
| 136 | +) (map[core.OperatorID]Assignment, error) { |
| 137 | + // Sort quorums for determinism |
| 138 | + sort.Slice(quorums, ...) |
| 139 | + |
| 140 | + // Process first quorum |
| 141 | + assignmentsList[0], _, err = GetAssignmentsForQuorum(state, blobParams, quorums[0]) |
| 142 | + |
| 143 | + // Process remaining quorums with overlap optimization |
| 144 | + for i := 1; i < len(quorums); i++ { |
| 145 | + assignmentsList[i], err = AddAssignmentsForQuorum( |
| 146 | + assignmentsList[0], state, blobParams, quorums[i]) |
| 147 | + } |
| 148 | + |
| 149 | + // Merge and cap |
| 150 | + return MergeAssignmentsAndCap(assignmentsList, blobParams) |
| 151 | +} |
| 152 | +``` |
| 153 | + |
| 154 | +**Usage in Node Chunk Download (`node/node_v2.go:40-105`):** |
| 155 | + |
| 156 | +```go |
| 157 | +func (n *Node) DetermineChunkLocations( |
| 158 | + batch *corev2.Batch, |
| 159 | + operatorState *core.OperatorState, |
| 160 | +) { |
| 161 | + for _, cert := range batch.BlobCertificates { |
| 162 | + // Get assignment for this operator across ALL quorums in the blob |
| 163 | + assgn, err := corev2.GetAssignmentForBlob( |
| 164 | + operatorState, |
| 165 | + blobParams, |
| 166 | + cert.BlobHeader.QuorumNumbers, // Multiple quorums |
| 167 | + n.Config.ID) |
| 168 | + |
| 169 | + // Request specific chunk indices from relay |
| 170 | + req.chunkRequests = append(req.chunkRequests, &relay.ChunkRequestByIndex{ |
| 171 | + BlobKey: blobKey, |
| 172 | + Indices: assgn.Indices, // Explicit indices with overlap optimization |
| 173 | + }) |
| 174 | + } |
| 175 | +} |
| 176 | +``` |
| 177 | + |
| 178 | +**Usage in Validation (`core/v2/validator.go:49-79`):** |
| 179 | + |
| 180 | +```go |
| 181 | +func (v *shardValidator) validateBlobParams( |
| 182 | + blob *BlobShard, |
| 183 | + blobParams *core.BlobVersionParameters, |
| 184 | + operatorState *core.OperatorState, |
| 185 | +) (*Assignment, error) { |
| 186 | + // Get assignment across all quorums for this blob |
| 187 | + assignment, err := GetAssignmentForBlob( |
| 188 | + operatorState, |
| 189 | + blobParams, |
| 190 | + blob.BlobHeader.QuorumNumbers, // All quorums |
| 191 | + v.operatorID) |
| 192 | + |
| 193 | + // Validate chunk count |
| 194 | + if assignment.NumChunks() != uint32(len(blob.Bundle)) { |
| 195 | + return error |
| 196 | + } |
| 197 | + |
| 198 | + // Validate chunk lengths |
| 199 | + for _, chunk := range blob.Bundle { |
| 200 | + if chunk.Length() != expectedChunkLength { |
| 201 | + return error |
| 202 | + } |
| 203 | + } |
| 204 | + |
| 205 | + return &assignment, nil |
| 206 | +} |
| 207 | +``` |
0 commit comments