gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/workerjobupdateregistry_test.go (about) 1 package renter 2 3 import ( 4 "context" 5 "reflect" 6 "testing" 7 "time" 8 9 "github.com/opentracing/opentracing-go" 10 "gitlab.com/NebulousLabs/errors" 11 "gitlab.com/NebulousLabs/fastrand" 12 "gitlab.com/SkynetLabs/skyd/siatest/dependencies" 13 "gitlab.com/SkynetLabs/skyd/skymodules" 14 "go.sia.tech/siad/crypto" 15 "go.sia.tech/siad/modules" 16 ) 17 18 // TestUpdateRegistryJob tests the various cases of running an UpdateRegistry 19 // job on a host. 20 func TestUpdateRegistryJob(t *testing.T) { 21 if testing.Short() { 22 t.SkipNow() 23 } 24 t.Parallel() 25 26 deps := dependencies.NewDependencyCorruptMDMOutput() 27 wt, err := newWorkerTesterCustomDependency(t.Name(), skymodules.SkydProdDependencies, deps) 28 if err != nil { 29 t.Fatal(err) 30 } 31 defer func() { 32 if err := wt.Close(); err != nil { 33 t.Fatal(err) 34 } 35 }() 36 37 // Create a registry value. 38 rv, spk, sk := randomRegistryValue() 39 sid := modules.DeriveRegistryEntryID(spk, rv.Tweak) 40 41 // Run the UpdateRegistry job. 42 err = wt.UpdateRegistry(context.Background(), spk, rv) 43 if err != nil { 44 t.Fatal(err) 45 } 46 47 // Manually try to read the entry from the host. 48 span := opentracing.GlobalTracer().StartSpan(t.Name()) 49 lookedUpRV, err := lookupRegistry(wt.worker, sid, &spk, &rv.Tweak) 50 span.Finish() 51 if err != nil { 52 t.Fatal(err) 53 } 54 55 // The entries should match. 56 if !reflect.DeepEqual(lookedUpRV.SignedRegistryValue, rv) { 57 t.Fatal("entries don't match") 58 } 59 60 // Run the UpdateRegistry job again with the same entry. Should succeed. 61 err = wt.UpdateRegistry(context.Background(), spk, rv) 62 if err != nil { 63 t.Fatal(err) 64 } 65 66 // Run it again with the same revision number but more pow. Should succeed. 67 rvMoreWork := rv 68 for !rvMoreWork.HasMoreWork(rv.RegistryValue) { 69 rvMoreWork.Data = fastrand.Bytes(10) 70 rvMoreWork = rvMoreWork.Sign(sk) 71 } 72 err = wt.UpdateRegistry(context.Background(), spk, rvMoreWork) 73 if err != nil { 74 t.Fatal(err) 75 } 76 rv = rvMoreWork 77 78 // Run it again with the same revision number but less pow. Should fail. 79 rvLessWork := rv 80 for !rv.HasMoreWork(rvLessWork.RegistryValue) { 81 rvLessWork.Data = fastrand.Bytes(10) 82 rvLessWork = rvLessWork.Sign(sk) 83 } 84 err = wt.UpdateRegistry(context.Background(), spk, rvLessWork) 85 if !errors.Contains(err, modules.ErrInsufficientWork) { 86 t.Fatal(err) 87 } 88 89 // Make sure there is no recent error or cooldown. 90 wt.staticJobUpdateRegistryQueue.mu.Lock() 91 if wt.staticJobUpdateRegistryQueue.recentErr != nil { 92 t.Fatal("recentErr is set", wt.staticJobUpdateRegistryQueue.recentErr) 93 } 94 if wt.staticJobUpdateRegistryQueue.cooldownUntil != (time.Time{}) { 95 t.Fatal("cooldownUntil is set", wt.staticJobUpdateRegistryQueue.cooldownUntil) 96 } 97 wt.staticJobUpdateRegistryQueue.mu.Unlock() 98 99 // Same thing again but corrupt the output. 100 deps.Fail() 101 err = wt.UpdateRegistry(context.Background(), spk, rv) 102 deps.Disable() 103 if !errors.Contains(err, crypto.ErrInvalidSignature) && !errors.Contains(err, modules.ErrUnknownRegistryEntryType) { 104 t.Fatal(err) 105 } 106 107 // Make sure the recent error is an invalid signature error and reset the 108 // cooldown. 109 wt.staticJobUpdateRegistryQueue.mu.Lock() 110 if !errors.Contains(wt.staticJobUpdateRegistryQueue.recentErr, crypto.ErrInvalidSignature) && 111 !errors.Contains(wt.staticJobUpdateRegistryQueue.recentErr, modules.ErrUnknownRegistryEntryType) { 112 t.Fatal(err) 113 } 114 if wt.staticJobUpdateRegistryQueue.cooldownUntil == (time.Time{}) { 115 t.Fatal("coolDown not set") 116 } 117 wt.staticJobUpdateRegistryQueue.cooldownUntil = time.Time{} 118 wt.staticJobUpdateRegistryQueue.recentErr = nil 119 wt.staticJobUpdateRegistryQueue.mu.Unlock() 120 121 // Run the UpdateRegistry job with a lower revision number. This time it 122 // should fail with an error indicating that the revision number already 123 // exists. 124 rvLowRevNum := rv 125 rvLowRevNum.Revision-- 126 rvLowRevNum = rvLowRevNum.Sign(sk) 127 err = wt.UpdateRegistry(context.Background(), spk, rvLowRevNum) 128 if !errors.Contains(err, modules.ErrLowerRevNum) { 129 t.Fatal(err) 130 } 131 132 // Make sure there is no recent error or cooldown. 133 wt.staticJobUpdateRegistryQueue.mu.Lock() 134 if wt.staticJobUpdateRegistryQueue.recentErr != nil { 135 t.Fatal("recentErr is set", wt.staticJobUpdateRegistryQueue.recentErr) 136 } 137 if wt.staticJobUpdateRegistryQueue.cooldownUntil != (time.Time{}) { 138 t.Fatal("cooldownUntil is set", wt.staticJobUpdateRegistryQueue.cooldownUntil) 139 } 140 wt.staticJobUpdateRegistryQueue.mu.Unlock() 141 142 // Same thing again but corrupt the output. 143 deps.Fail() 144 err = wt.UpdateRegistry(context.Background(), spk, rvLowRevNum) 145 deps.Disable() 146 if !errors.Contains(err, crypto.ErrInvalidSignature) && !errors.Contains(err, modules.ErrUnknownRegistryEntryType) { 147 t.Fatal(err) 148 } 149 if modules.IsRegistryEntryExistErr(err) { 150 t.Fatal("Revision error should have been stripped", err) 151 } 152 153 // Make sure the recent error is an invalid signature error and reset the 154 // cooldown. 155 wt.staticJobUpdateRegistryQueue.mu.Lock() 156 if !errors.Contains(wt.staticJobUpdateRegistryQueue.recentErr, crypto.ErrInvalidSignature) && 157 !errors.Contains(wt.staticJobUpdateRegistryQueue.recentErr, modules.ErrUnknownRegistryEntryType) { 158 t.Fatal(err) 159 } 160 if modules.IsRegistryEntryExistErr(err) { 161 t.Fatal("Revision error should have been stripped", err) 162 } 163 if wt.staticJobUpdateRegistryQueue.cooldownUntil == (time.Time{}) { 164 t.Fatal("coolDown not set") 165 } 166 wt.staticJobUpdateRegistryQueue.cooldownUntil = time.Time{} 167 wt.staticJobUpdateRegistryQueue.recentErr = nil 168 wt.staticJobUpdateRegistryQueue.mu.Unlock() 169 170 // Manually try to read the entry from the host. 171 lookedUpRV, err = lookupRegistry(wt.worker, sid, &spk, &rv.Tweak) 172 if err != nil { 173 t.Fatal(err) 174 } 175 176 // The entries should match. 177 if !reflect.DeepEqual(lookedUpRV.SignedRegistryValue, rv) { 178 t.Fatal("entries don't match") 179 } 180 181 // Increment the revision number and do it one more time. 182 rv.Revision++ 183 rv = rv.Sign(sk) 184 err = wt.UpdateRegistry(context.Background(), spk, rv) 185 if err != nil { 186 t.Fatal(err) 187 } 188 189 // Manually try to read the entry from the host. 190 lookedUpRV, err = lookupRegistry(wt.worker, sid, &spk, &rv.Tweak) 191 if err != nil { 192 t.Fatal(err) 193 } 194 195 // The entries should match. 196 if !reflect.DeepEqual(lookedUpRV.SignedRegistryValue, rv) { 197 t.Fatal("entries don't match") 198 } 199 } 200 201 // TestUpdateRegistryLyingHost tests the edge case where a host returns a valid 202 // registry entry but also returns an error. 203 func TestUpdateRegistryLyingHost(t *testing.T) { 204 if testing.Short() { 205 t.SkipNow() 206 } 207 t.Parallel() 208 209 wt, err := newWorkerTesterCustomDependency(t.Name(), skymodules.SkydProdDependencies, &dependencies.DependencyRegistryUpdateLyingHost{}) 210 if err != nil { 211 t.Fatal(err) 212 } 213 defer func() { 214 if err := wt.Close(); err != nil { 215 t.Fatal(err) 216 } 217 }() 218 219 // Create a registry value. 220 rv, spk, sk := randomRegistryValue() 221 sid := modules.DeriveRegistryEntryID(spk, rv.Tweak) 222 223 // Run the UpdateRegistry job. 224 err = wt.UpdateRegistry(context.Background(), spk, rv) 225 if err != nil { 226 t.Fatal(err) 227 } 228 229 // Manually try to read the entry from the host. 230 lookedUpRV, err := lookupRegistry(wt.worker, sid, &spk, &rv.Tweak) 231 if err != nil { 232 t.Fatal(err) 233 } 234 235 // The entries should match. 236 if !reflect.DeepEqual(lookedUpRV.SignedRegistryValue, rv) { 237 t.Fatal("entries don't match") 238 } 239 240 // Increment the revision number. 241 rv.Revision++ 242 rv = rv.Sign(sk) 243 244 // Run the UpdateRegistry job again. This time the host will respond with an 245 // error and provide a proof which has a valid signature, but an outdated 246 // revision. The worker should detect the cheating host an 247 // errHostInvalidProof error but no revision errors. 248 err = wt.UpdateRegistry(context.Background(), spk, rv) 249 if !errors.Contains(err, errHostOutdatedProof) { 250 t.Fatal("worker should return errHostOutdatedProof") 251 } 252 if modules.IsRegistryEntryExistErr(err) { 253 t.Fatal(err) 254 } 255 } 256 257 // TestUpdateRegistryInvalidCache tests the edge case where a host tries to 258 // prove an invalid revision number with a lower revision number than we have 259 // stored in the cache for this particular host. 260 func TestUpdateRegistryInvalidCached(t *testing.T) { 261 if testing.Short() { 262 t.SkipNow() 263 } 264 t.Parallel() 265 266 deps := dependencies.NewDependencyRegistryUpdateNoOp() 267 deps.Disable() 268 wt, err := newWorkerTesterCustomDependency(t.Name(), skymodules.SkydProdDependencies, deps) 269 if err != nil { 270 t.Fatal(err) 271 } 272 defer func() { 273 if err := wt.Close(); err != nil { 274 t.Fatal(err) 275 } 276 }() 277 278 // Create a registry value. 279 rv, spk, sk := randomRegistryValue() 280 281 // Run the UpdateRegistry job. 282 err = wt.UpdateRegistry(context.Background(), spk, rv) 283 if err != nil { 284 t.Fatal(err) 285 } 286 287 // Run the UpdateRegistry job again. This time it's a no-op. The renter 288 // won't know and increment the revision in the cache. 289 rv.Revision++ 290 rv = rv.Sign(sk) 291 deps.Enable() 292 err = wt.UpdateRegistry(context.Background(), spk, rv) 293 deps.Disable() 294 if err != nil { 295 t.Fatal(err) 296 } 297 298 // Run the UpdateRegistry job again with a lower rev num than the initial 299 // one. Causing a ErrLowerRevNumError. The host will use the latest revision 300 // it knows for the proof which is lower than the one in the worker cache. 301 rv.Revision -= 2 302 rv = rv.Sign(sk) 303 err = wt.UpdateRegistry(context.Background(), spk, rv) 304 if !errors.Contains(err, errHostCheating) { 305 t.Fatal(err) 306 } 307 308 // Make sure there is a recent error and cooldown. 309 wt.staticJobUpdateRegistryQueue.mu.Lock() 310 if !errors.Contains(wt.staticJobUpdateRegistryQueue.recentErr, errHostCheating) { 311 t.Fatal("wrong recent error", wt.staticJobUpdateRegistryQueue.recentErr) 312 } 313 if wt.staticJobUpdateRegistryQueue.cooldownUntil == (time.Time{}) { 314 t.Fatal("cooldownUntil is not set") 315 } 316 wt.staticJobUpdateRegistryQueue.mu.Unlock() 317 }