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  }