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  }