github.com/filecoin-project/bacalhau@v0.3.23-0.20230228154132-45c989550ace/pkg/verifier/deterministic/verifier.go (about)

     1  package deterministic
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	"github.com/filecoin-project/bacalhau/pkg/model"
     8  	"github.com/filecoin-project/bacalhau/pkg/system"
     9  	"github.com/filecoin-project/bacalhau/pkg/verifier"
    10  	"github.com/filecoin-project/bacalhau/pkg/verifier/results"
    11  	"golang.org/x/mod/sumdb/dirhash"
    12  )
    13  
    14  type DeterministicVerifier struct {
    15  	results   *results.Results
    16  	encrypter verifier.EncrypterFunction
    17  	decrypter verifier.DecrypterFunction
    18  }
    19  
    20  func NewDeterministicVerifier(
    21  	_ context.Context, cm *system.CleanupManager,
    22  	encrypter verifier.EncrypterFunction,
    23  	decrypter verifier.DecrypterFunction,
    24  ) (*DeterministicVerifier, error) {
    25  	results, err := results.NewResults()
    26  	if err != nil {
    27  		return nil, err
    28  	}
    29  
    30  	cm.RegisterCallback(func() error {
    31  		if err := results.Close(); err != nil {
    32  			return fmt.Errorf("unable to remove results folder: %w", err)
    33  		}
    34  		return nil
    35  	})
    36  	return &DeterministicVerifier{
    37  		results:   results,
    38  		encrypter: encrypter,
    39  		decrypter: decrypter,
    40  	}, nil
    41  }
    42  
    43  func (deterministicVerifier *DeterministicVerifier) IsInstalled(context.Context) (bool, error) {
    44  	return true, nil
    45  }
    46  
    47  func (deterministicVerifier *DeterministicVerifier) GetShardResultPath(
    48  	_ context.Context,
    49  	shard model.JobShard,
    50  ) (string, error) {
    51  	return deterministicVerifier.results.EnsureShardResultsDir(shard.Job.Metadata.ID, shard.Index)
    52  }
    53  
    54  func (deterministicVerifier *DeterministicVerifier) GetShardProposal(
    55  	ctx context.Context,
    56  	shard model.JobShard,
    57  	shardResultPath string,
    58  ) ([]byte, error) {
    59  	if len(shard.Job.Metadata.Requester.RequesterPublicKey) == 0 {
    60  		return nil, fmt.Errorf("no RequesterPublicKey found in the job")
    61  	}
    62  	dirHash, err := dirhash.HashDir(shardResultPath, "results", dirhash.Hash1)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	encryptedHash, err := deterministicVerifier.encrypter(ctx, []byte(dirHash), shard.Job.Metadata.Requester.RequesterPublicKey)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	return encryptedHash, nil
    71  }
    72  
    73  func (deterministicVerifier *DeterministicVerifier) getHashGroups(
    74  	ctx context.Context,
    75  	executionStates []model.ExecutionState,
    76  ) map[string][]*verifier.VerifierResult {
    77  	// group the verifier results by their reported hash
    78  	// then pick the largest group and verify all of those
    79  	// caveats:
    80  	//  * if there is only 1 group - there must be > 1 result
    81  	//  * there cannot be a draw between the top 2 groups
    82  	hashGroups := map[string][]*verifier.VerifierResult{}
    83  
    84  	for _, executionState := range executionStates { //nolint:gocritic
    85  		hash := ""
    86  
    87  		if len(executionState.VerificationProposal) > 0 {
    88  			decryptedHash, err := deterministicVerifier.decrypter(ctx, executionState.VerificationProposal)
    89  
    90  			// if there is an error decrypting let's not fail the verification job
    91  			// but just leave the proposed hash at empty string (which won't pass actual verification)
    92  			// this means we can "complete" the verification process by deciding that anyone
    93  			// who couldn't submit a correctly encrypted hash will result in an empty hash
    94  			// rather than a decryption error
    95  			if err == nil {
    96  				hash = string(decryptedHash)
    97  			}
    98  		}
    99  
   100  		existingArray, ok := hashGroups[hash]
   101  		if !ok {
   102  			existingArray = []*verifier.VerifierResult{}
   103  		}
   104  		hashGroups[hash] = append(existingArray, &verifier.VerifierResult{
   105  			Execution: executionState,
   106  			Verified:  false,
   107  		})
   108  	}
   109  
   110  	return hashGroups
   111  }
   112  
   113  func (deterministicVerifier *DeterministicVerifier) VerifyShard(
   114  	ctx context.Context,
   115  	shard model.JobShard,
   116  	executionStates []model.ExecutionState,
   117  ) ([]verifier.VerifierResult, error) {
   118  	_, span := system.NewSpan(ctx, system.GetTracer(), "pkg/verifier.DeterministicVerifier.VerifyShard")
   119  	defer span.End()
   120  
   121  	err := verifier.ValidateExecutions(shard, executionStates)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	confidence := shard.Job.Spec.Deal.Confidence
   126  
   127  	largestGroupHash := ""
   128  	largestGroupSize := 0
   129  	isVoidResult := false
   130  	groupSizeCounts := map[int]int{}
   131  	hashGroups := deterministicVerifier.getHashGroups(ctx, executionStates)
   132  
   133  	for hash, group := range hashGroups {
   134  		if len(group) > largestGroupSize {
   135  			largestGroupSize = len(group)
   136  			largestGroupHash = hash
   137  		}
   138  		groupSizeCounts[len(group)]++
   139  	}
   140  
   141  	// this means there is a draw for the largest group size
   142  	if groupSizeCounts[largestGroupSize] > 1 {
   143  		isVoidResult = true
   144  	}
   145  
   146  	// this means there is only a single result
   147  	if len(hashGroups) == 1 && largestGroupSize == 1 {
   148  		isVoidResult = true
   149  	}
   150  
   151  	// this means that the winning group size does not
   152  	// meet the confidence threshold
   153  	if confidence > 0 && largestGroupSize < confidence {
   154  		isVoidResult = true
   155  	}
   156  
   157  	// the winning hash must not be empty string
   158  	if largestGroupHash == "" {
   159  		isVoidResult = true
   160  	}
   161  
   162  	if !isVoidResult {
   163  		for _, passedVerificationResult := range hashGroups[largestGroupHash] {
   164  			passedVerificationResult.Verified = true
   165  		}
   166  	}
   167  
   168  	var allResults []verifier.VerifierResult
   169  
   170  	for _, verificationResultList := range hashGroups {
   171  		for _, verificationResult := range verificationResultList {
   172  			allResults = append(allResults, *verificationResult)
   173  		}
   174  	}
   175  
   176  	return allResults, nil
   177  }
   178  
   179  // Compile-time check that deterministicVerifier implements the correct interface:
   180  var _ verifier.Verifier = (*DeterministicVerifier)(nil)