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)