github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/chunks/chunk_assigner.go (about) 1 package chunks 2 3 import ( 4 "fmt" 5 6 "github.com/onflow/crypto/hash" 7 "github.com/onflow/crypto/random" 8 9 chunkmodels "github.com/onflow/flow-go/model/chunks" 10 "github.com/onflow/flow-go/model/encoding/json" 11 "github.com/onflow/flow-go/model/flow" 12 "github.com/onflow/flow-go/model/flow/filter" 13 "github.com/onflow/flow-go/module/mempool" 14 "github.com/onflow/flow-go/module/mempool/stdmap" 15 "github.com/onflow/flow-go/state/protocol" 16 "github.com/onflow/flow-go/state/protocol/prg" 17 ) 18 19 // ChunkAssigner implements an instance of the Public Chunk Assignment 20 // algorithm for assigning chunks to verifier nodes in a deterministic but 21 // unpredictable manner. It implements the ChunkAssigner interface. 22 type ChunkAssigner struct { 23 alpha int // used to indicate the number of verifiers that should be assigned to each chunk 24 assignments mempool.Assignments 25 26 protocolState protocol.State 27 } 28 29 // NewChunkAssigner generates and returns an instance of the Public Chunk 30 // Assignment algorithm. Parameter alpha is the number of verifiers that should 31 // be assigned to each chunk. 32 func NewChunkAssigner(alpha uint, protocolState protocol.State) (*ChunkAssigner, error) { 33 // TODO to have limit of assignment mempool as a parameter (2703) 34 assignment, err := stdmap.NewAssignments(1000) 35 if err != nil { 36 return nil, fmt.Errorf("could not create an assignment mempool: %w", err) 37 } 38 return &ChunkAssigner{ 39 alpha: int(alpha), 40 assignments: assignment, 41 protocolState: protocolState, 42 }, nil 43 } 44 45 // Size returns number of assignments 46 func (p *ChunkAssigner) Size() uint { 47 return p.assignments.Size() 48 } 49 50 // Assign generates the assignment 51 // error returns: 52 // - NoValidChildBlockError indicates that no valid child block is known 53 // (which contains the block's source of randomness) 54 // - unexpected errors should be considered symptoms of internal bugs 55 func (p *ChunkAssigner) Assign(result *flow.ExecutionResult, blockID flow.Identifier) (*chunkmodels.Assignment, error) { 56 // computes a fingerprint for blockID||resultID||alpha 57 hash, err := fingerPrint(blockID, result.ID(), p.alpha) 58 if err != nil { 59 return nil, fmt.Errorf("could not compute hash of identifiers: %w", err) 60 } 61 62 // checks cache against this assignment 63 assignmentFingerprint := flow.HashToID(hash) 64 a, exists := p.assignments.ByID(assignmentFingerprint) 65 if exists { 66 return a, nil 67 } 68 69 // Get a list of verifiers at block that is being sealed 70 verifiers, err := p.protocolState.AtBlockID(result.BlockID).Identities( 71 filter.And( 72 filter.HasInitialWeight[flow.Identity](true), 73 filter.HasRole[flow.Identity](flow.RoleVerification), 74 filter.IsValidCurrentEpochParticipant, 75 )) 76 if err != nil { 77 return nil, fmt.Errorf("could not get verifiers: %w", err) 78 } 79 80 // create RNG for assignment 81 rng, err := p.rngByBlockID(p.protocolState.AtBlockID(blockID)) 82 if err != nil { 83 return nil, err 84 } 85 86 // otherwise, it computes the assignment and caches it for future calls 87 a, err = chunkAssignment(verifiers.NodeIDs(), result.Chunks, rng, p.alpha) 88 if err != nil { 89 return nil, fmt.Errorf("could not complete chunk assignment: %w", err) 90 } 91 92 // adds assignment to mempool 93 _ = p.assignments.Add(assignmentFingerprint, a) 94 95 return a, nil 96 } 97 98 func (p *ChunkAssigner) rngByBlockID(stateSnapshot protocol.Snapshot) (random.Rand, error) { 99 // TODO: seed could be cached to optimize performance 100 randomSource, err := stateSnapshot.RandomSource() // potentially returns NoValidChildBlockError 101 if err != nil { 102 return nil, fmt.Errorf("failed to retrieve source of randomness: %w", err) 103 } 104 105 rng, err := prg.New(randomSource, prg.VerificationChunkAssignment, nil) 106 if err != nil { 107 return nil, fmt.Errorf("failed to instantiate random number generator: %w", err) 108 } 109 110 return rng, nil 111 } 112 113 // ChunkAssignment implements the business logic of the Public Chunk Assignment algorithm and returns an 114 // assignment object for the chunks where each chunk is assigned to alpha-many verifier node from ids list 115 func chunkAssignment(ids flow.IdentifierList, chunks flow.ChunkList, rng random.Rand, alpha int) (*chunkmodels.Assignment, error) { 116 if len(ids) < alpha { 117 return nil, fmt.Errorf("not enough verification nodes for chunk assignment: %d, minumum should be %d", len(ids), alpha) 118 } 119 120 // creates an assignment 121 assignment := chunkmodels.NewAssignment() 122 123 // permutes the entire slice 124 err := rng.Shuffle(len(ids), ids.Swap) 125 if err != nil { 126 return nil, fmt.Errorf("shuffling verifiers failed: %w", err) 127 } 128 t := ids 129 130 for i := 0; i < chunks.Len(); i++ { 131 assignees := make([]flow.Identifier, 0, alpha) 132 if len(t) >= alpha { // More verifiers than required for this chunk 133 assignees = append(assignees, t[:alpha]...) 134 t = t[alpha:] 135 } else { // Less verifiers than required for this chunk 136 assignees = append(assignees, t...) // take all remaining elements from t 137 138 // now, we need `still` elements from a new shuffling round: 139 still := alpha - len(assignees) 140 t = ids[:ids.Len()-len(assignees)] // but we exclude the elements we already picked from the population 141 err := rng.Samples(len(t), still, t.Swap) 142 if err != nil { 143 return nil, fmt.Errorf("sampling verifiers failed: %w", err) 144 } 145 146 // by adding `still` elements from new shuffling round: we have alpha assignees for the current chunk 147 assignees = append(assignees, t[:still]...) 148 149 // we have already assigned the first `still` elements in `ids` 150 // note that remaining elements ids[still:] still need shuffling 151 t = ids[still:] 152 err = rng.Shuffle(len(t), t.Swap) 153 if err != nil { 154 return nil, fmt.Errorf("shuffling verifiers failed: %w", err) 155 } 156 } 157 // extracts chunk by index 158 chunk, ok := chunks.ByIndex(uint64(i)) 159 if !ok { 160 return nil, fmt.Errorf("chunk out of range requested: %v", i) 161 } 162 assignment.Add(chunk, assignees) 163 } 164 return assignment, nil 165 } 166 167 func fingerPrint(blockID flow.Identifier, resultID flow.Identifier, alpha int) (hash.Hash, error) { 168 hasher := hash.NewSHA3_256() 169 170 // encodes alpha parameter 171 encAlpha, err := json.NewMarshaler().Marshal(alpha) 172 if err != nil { 173 return nil, fmt.Errorf("could not encode alpha: %w", err) 174 } 175 176 _, err = hasher.Write(blockID[:]) 177 if err != nil { 178 return nil, fmt.Errorf("could not hash blockID: %w", err) 179 } 180 _, err = hasher.Write(resultID[:]) 181 if err != nil { 182 return nil, fmt.Errorf("could not hash result: %w", err) 183 } 184 _, err = hasher.Write(encAlpha) 185 if err != nil { 186 return nil, fmt.Errorf("could not hash alpha: %w", err) 187 } 188 189 return hasher.SumHash(), nil 190 }