github.com/koko1123/flow-go-1@v0.29.6/module/chunks/chunk_assigner.go (about)

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