github.com/decred/dcrlnd@v0.7.6/contractcourt/nursery_store_test.go (about)

     1  package contractcourt
     2  
     3  import (
     4  	"reflect"
     5  	"testing"
     6  
     7  	"github.com/decred/dcrd/chaincfg/chainhash"
     8  	"github.com/decred/dcrd/wire"
     9  	"github.com/decred/dcrlnd/channeldb"
    10  )
    11  
    12  type incubateTest struct {
    13  	nOutputs    int
    14  	chanPoint   *wire.OutPoint
    15  	commOutput  *kidOutput
    16  	htlcOutputs []babyOutput
    17  	err         error
    18  }
    19  
    20  // incubateTests holds the test vectors used to test the state transitions of
    21  // outputs stored in the nursery store.
    22  var incubateTests []incubateTest
    23  
    24  var chainHash chainhash.Hash
    25  
    26  // initIncubateTests instantiates the test vectors during package init, which
    27  // properly captures the sign descriptors and public keys.
    28  func initIncubateTests() {
    29  	incubateTests = []incubateTest{
    30  		{
    31  			nOutputs:  0,
    32  			chanPoint: &outPoints[3],
    33  		},
    34  		{
    35  			nOutputs:   1,
    36  			chanPoint:  &outPoints[0],
    37  			commOutput: &kidOutputs[0],
    38  		},
    39  		{
    40  			nOutputs:    4,
    41  			chanPoint:   &outPoints[0],
    42  			commOutput:  &kidOutputs[0],
    43  			htlcOutputs: babyOutputs,
    44  		},
    45  	}
    46  
    47  }
    48  
    49  // TestNurseryStoreInit verifies basic properties of the nursery store before
    50  // any modifying calls are made.
    51  func TestNurseryStoreInit(t *testing.T) {
    52  	cdb, cleanUp, err := channeldb.MakeTestDB()
    53  	if err != nil {
    54  		t.Fatalf("unable to open channel db: %v", err)
    55  	}
    56  	defer cleanUp()
    57  
    58  	ns, err := NewNurseryStore(&chainHash, cdb)
    59  	if err != nil {
    60  		t.Fatalf("unable to open nursery store: %v", err)
    61  	}
    62  
    63  	assertNumChannels(t, ns, 0)
    64  	assertNumPreschools(t, ns, 0)
    65  }
    66  
    67  // TestNurseryStoreIncubate tests the primary state transitions taken by outputs
    68  // in the nursery store. The test is designed to walk both commitment or htlc
    69  // outputs through the nursery store, verifying the properties of the
    70  // intermediate states.
    71  func TestNurseryStoreIncubate(t *testing.T) {
    72  	cdb, cleanUp, err := channeldb.MakeTestDB()
    73  	if err != nil {
    74  		t.Fatalf("unable to open channel db: %v", err)
    75  	}
    76  	defer cleanUp()
    77  
    78  	ns, err := NewNurseryStore(&chainHash, cdb)
    79  	if err != nil {
    80  		t.Fatalf("unable to open nursery store: %v", err)
    81  	}
    82  
    83  	for i, test := range incubateTests {
    84  		// At the beginning of each test, we do not expect to the
    85  		// nursery store to be tracking any outputs for this channel
    86  		// point.
    87  		assertNumChanOutputs(t, ns, test.chanPoint, 0)
    88  
    89  		// Nursery store should be completely empty.
    90  		assertNumChannels(t, ns, 0)
    91  		assertNumPreschools(t, ns, 0)
    92  
    93  		// Begin incubating all of the outputs provided in this test
    94  		// vector.
    95  		var kids []kidOutput
    96  		if test.commOutput != nil {
    97  			kids = append(kids, *test.commOutput)
    98  		}
    99  		err = ns.Incubate(kids, test.htlcOutputs)
   100  		if err != nil {
   101  			t.Fatalf("unable to incubate outputs"+
   102  				"on test #%d: %v", i, err)
   103  		}
   104  		// Now that the outputs have been inserted, the nursery store
   105  		// should see exactly that many outputs under this channel
   106  		// point.
   107  		// NOTE: This property should remain intact after every state
   108  		// change until the channel has been completely removed.
   109  		assertNumChanOutputs(t, ns, test.chanPoint, test.nOutputs)
   110  
   111  		// If there were no inputs to be incubated, just check that the
   112  		// no trace of the channel was left.
   113  		if test.nOutputs == 0 {
   114  			assertNumChannels(t, ns, 0)
   115  			continue
   116  		}
   117  
   118  		// The test vector has a non-zero number of outputs, we will
   119  		// expect to only see the one channel from this test case.
   120  		assertNumChannels(t, ns, 1)
   121  
   122  		// The channel should be shown as immature, since none of the
   123  		// outputs should be graduated directly after being inserted.
   124  		// It should also be impossible to remove the channel, if it is
   125  		// also immature.
   126  		// NOTE: These two tests properties should hold between every
   127  		// state change until all outputs have been fully graduated.
   128  		assertChannelMaturity(t, ns, test.chanPoint, false)
   129  		assertCanRemoveChannel(t, ns, test.chanPoint, false)
   130  
   131  		// Verify that the htlc outputs, if any, reside in the height
   132  		// index at their first stage CLTV's expiry height.
   133  		for _, htlcOutput := range test.htlcOutputs {
   134  			assertCribAtExpiryHeight(t, ns, &htlcOutput)
   135  		}
   136  
   137  		// If the commitment output was not dust, we will move it from
   138  		// the preschool bucket to the kindergarten bucket.
   139  		if test.commOutput != nil {
   140  			// If the commitment output was not considered dust, we
   141  			// should see exactly one preschool output in the
   142  			// nursery store.
   143  			assertNumPreschools(t, ns, 1)
   144  
   145  			// Now, move the commitment output to the kindergarten
   146  			// bucket.
   147  			err = ns.PreschoolToKinder(test.commOutput, 0)
   148  			if err != test.err {
   149  				t.Fatalf("unable to move commitment output from "+
   150  					"pscl to kndr: %v", err)
   151  			}
   152  
   153  			// The total number of outputs for this channel should
   154  			// not have changed, and the kindergarten output should
   155  			// reside at its maturity height.
   156  			assertNumChanOutputs(t, ns, test.chanPoint, test.nOutputs)
   157  			assertKndrAtMaturityHeight(t, ns, test.commOutput)
   158  
   159  			// The total number of channels should not have changed.
   160  			assertNumChannels(t, ns, 1)
   161  
   162  			// Channel maturity and removal should reflect that the
   163  			// channel still has non-graduated outputs.
   164  			assertChannelMaturity(t, ns, test.chanPoint, false)
   165  			assertCanRemoveChannel(t, ns, test.chanPoint, false)
   166  
   167  			// Moving the preschool output should have no effect on
   168  			// the placement of crib outputs in the height index.
   169  			for _, htlcOutput := range test.htlcOutputs {
   170  				assertCribAtExpiryHeight(t, ns, &htlcOutput)
   171  			}
   172  		}
   173  
   174  		// At this point, we should see no more preschool outputs in the
   175  		// nursery store. Either it was moved to the kindergarten
   176  		// bucket, or never inserted.
   177  		assertNumPreschools(t, ns, 0)
   178  
   179  		// If the commitment output is not-dust, we will graduate the
   180  		// class at its maturity height.
   181  		if test.commOutput != nil {
   182  			// Compute the commitment output's maturity height, and
   183  			// move proceed to graduate that class.
   184  			maturityHeight := test.commOutput.ConfHeight() +
   185  				test.commOutput.BlocksToMaturity()
   186  
   187  			err = ns.GraduateKinder(maturityHeight, test.commOutput)
   188  			if err != nil {
   189  				t.Fatalf("unable to graduate kindergarten class at "+
   190  					"height %d: %v", maturityHeight, err)
   191  			}
   192  
   193  			// The total number of outputs for this channel should
   194  			// not have changed, but the kindergarten output should
   195  			// have been removed from its maturity height.
   196  			assertNumChanOutputs(t, ns, test.chanPoint, test.nOutputs)
   197  			assertKndrNotAtMaturityHeight(t, ns, test.commOutput)
   198  
   199  			// The total number of channels should not have changed.
   200  			assertNumChannels(t, ns, 1)
   201  
   202  			// Moving the preschool output should have no effect on
   203  			// the placement of crib outputs in the height index.
   204  			for _, htlcOutput := range test.htlcOutputs {
   205  				assertCribAtExpiryHeight(t, ns, &htlcOutput)
   206  			}
   207  		}
   208  
   209  		// If there are any htlc outputs to incubate, we will walk them
   210  		// through their two-stage incubation process.
   211  		if len(test.htlcOutputs) > 0 {
   212  			for i, htlcOutput := range test.htlcOutputs {
   213  				// Begin by moving each htlc output from the
   214  				// crib to kindergarten state.
   215  				err = ns.CribToKinder(&htlcOutput)
   216  				if err != nil {
   217  					t.Fatalf("unable to move htlc output from "+
   218  						"crib to kndr: %v", err)
   219  				}
   220  				// Number of outputs for this channel should
   221  				// remain unchanged.
   222  				assertNumChanOutputs(t, ns, test.chanPoint,
   223  					test.nOutputs)
   224  
   225  				// If the output hasn't moved to kndr, it should
   226  				// be at its crib expiry height, otherwise is
   227  				// should have been removed.
   228  				for j := range test.htlcOutputs {
   229  					if j > i {
   230  						assertCribAtExpiryHeight(t, ns,
   231  							&test.htlcOutputs[j])
   232  						assertKndrNotAtMaturityHeight(t,
   233  							ns, &test.htlcOutputs[j].kidOutput)
   234  					} else {
   235  						assertCribNotAtExpiryHeight(t, ns,
   236  							&test.htlcOutputs[j])
   237  						assertKndrAtMaturityHeight(t,
   238  							ns, &test.htlcOutputs[j].kidOutput)
   239  					}
   240  				}
   241  			}
   242  
   243  			// Total number of channels in the nursery store should
   244  			// be the same, no outputs should be marked as
   245  			// preschool.
   246  			assertNumChannels(t, ns, 1)
   247  			assertNumPreschools(t, ns, 0)
   248  
   249  			// Channel should also not be mature, as it we should
   250  			// still have outputs in kindergarten.
   251  			assertChannelMaturity(t, ns, test.chanPoint, false)
   252  			assertCanRemoveChannel(t, ns, test.chanPoint, false)
   253  
   254  			// Now, graduate each htlc kindergarten output,
   255  			// asserting the invariant number of outputs being
   256  			// tracked in this channel
   257  			for _, htlcOutput := range test.htlcOutputs {
   258  				maturityHeight := htlcOutput.ConfHeight() +
   259  					htlcOutput.BlocksToMaturity()
   260  
   261  				err = ns.GraduateKinder(maturityHeight,
   262  					&htlcOutput.kidOutput)
   263  				if err != nil {
   264  					t.Fatalf("unable to graduate htlc output "+
   265  						"from kndr to grad: %v", err)
   266  				}
   267  				assertNumChanOutputs(t, ns, test.chanPoint,
   268  					test.nOutputs)
   269  			}
   270  		}
   271  
   272  		// All outputs have been advanced through the nursery store, but
   273  		// no attempt has been made to clean up this channel. We expect
   274  		// to see the same channel remaining, and no kindergarten
   275  		// outputs.
   276  		assertNumChannels(t, ns, 1)
   277  		assertNumPreschools(t, ns, 0)
   278  
   279  		// Since all outputs have now been graduated, the nursery store
   280  		// should recognize that the channel is mature, and attempting
   281  		// to remove it should succeed.
   282  		assertChannelMaturity(t, ns, test.chanPoint, true)
   283  		assertCanRemoveChannel(t, ns, test.chanPoint, true)
   284  
   285  		// Now that the channel has been removed, the nursery store
   286  		// should be no channels in the nursery store, and no outputs
   287  		// being tracked for this channel point.
   288  		assertNumChannels(t, ns, 0)
   289  		assertNumChanOutputs(t, ns, test.chanPoint, 0)
   290  
   291  		// If we had a commitment output, ensure it was removed from the
   292  		// height index.
   293  		if test.commOutput != nil {
   294  			assertKndrNotAtMaturityHeight(t, ns, test.commOutput)
   295  		}
   296  
   297  		// Check that all htlc outputs are no longer stored in their
   298  		// crib or kindergarten height buckets.
   299  		for _, htlcOutput := range test.htlcOutputs {
   300  			assertCribNotAtExpiryHeight(t, ns, &htlcOutput)
   301  			assertKndrNotAtMaturityHeight(t, ns, &htlcOutput.kidOutput)
   302  		}
   303  
   304  		// Lastly, there should be no lingering preschool outputs.
   305  		assertNumPreschools(t, ns, 0)
   306  	}
   307  }
   308  
   309  // TestNurseryStoreGraduate verifies that the nursery store properly removes
   310  // populated entries from the height index as it is purged, and that the last
   311  // purged height is set appropriately.
   312  func TestNurseryStoreGraduate(t *testing.T) {
   313  	cdb, cleanUp, err := channeldb.MakeTestDB()
   314  	if err != nil {
   315  		t.Fatalf("unable to open channel db: %v", err)
   316  	}
   317  	defer cleanUp()
   318  
   319  	ns, err := NewNurseryStore(&chainHash, cdb)
   320  	if err != nil {
   321  		t.Fatalf("unable to open nursery store: %v", err)
   322  	}
   323  
   324  	kid := &kidOutputs[3]
   325  
   326  	// Compute the height at which this output will be inserted in the
   327  	// height index.
   328  	maturityHeight := kid.ConfHeight() + kid.BlocksToMaturity()
   329  
   330  	// First, add a commitment output to the nursery store, which is
   331  	// initially inserted in the preschool bucket.
   332  	err = ns.Incubate([]kidOutput{*kid}, nil)
   333  	if err != nil {
   334  		t.Fatalf("unable to incubate commitment output: %v", err)
   335  	}
   336  
   337  	// Then, move the commitment output to the kindergarten bucket, such
   338  	// that it resides in the height index at its maturity height.
   339  	err = ns.PreschoolToKinder(kid, 0)
   340  	if err != nil {
   341  		t.Fatalf("unable to move pscl output to kndr: %v", err)
   342  	}
   343  
   344  	// Now, iteratively purge all height below the target maturity height,
   345  	// checking that each class is now empty, and that the last purged
   346  	// height is set correctly.
   347  	for i := 0; i < int(maturityHeight); i++ {
   348  		assertHeightIsPurged(t, ns, uint32(i))
   349  	}
   350  
   351  	// Check that the commitment output currently exists at its maturity
   352  	// height.
   353  	assertKndrAtMaturityHeight(t, ns, kid)
   354  
   355  	err = ns.GraduateKinder(maturityHeight, kid)
   356  	if err != nil {
   357  		t.Fatalf("unable to graduate kindergarten outputs at height=%d: "+
   358  			"%v", maturityHeight, err)
   359  	}
   360  
   361  	assertHeightIsPurged(t, ns, maturityHeight)
   362  }
   363  
   364  // assertNumChanOutputs checks that the channel bucket has the expected number
   365  // of outputs.
   366  func assertNumChanOutputs(t *testing.T, ns NurseryStorer,
   367  	chanPoint *wire.OutPoint, expectedNum int) {
   368  
   369  	var count int
   370  	err := ns.ForChanOutputs(chanPoint, func([]byte, []byte) error {
   371  		count++
   372  		return nil
   373  	}, func() {
   374  		count = 0
   375  	})
   376  
   377  	if count == 0 && err == ErrContractNotFound {
   378  		return
   379  	} else if err != nil {
   380  		t.Fatalf("unable to count num outputs for channel %v: %v",
   381  			chanPoint, err)
   382  	}
   383  
   384  	if count != expectedNum {
   385  		t.Fatalf("nursery store should have %d outputs, found %d",
   386  			expectedNum, count)
   387  	}
   388  }
   389  
   390  // assertNumPreschools loads all preschool outputs and verifies their count
   391  // matches the expected number.
   392  func assertNumPreschools(t *testing.T, ns NurseryStorer, expected int) {
   393  	psclOutputs, err := ns.FetchPreschools()
   394  	if err != nil {
   395  		t.Fatalf("unable to retrieve preschool outputs: %v", err)
   396  	}
   397  
   398  	if len(psclOutputs) != expected {
   399  		t.Fatalf("expected number of pscl outputs to be %d, got %v",
   400  			expected, len(psclOutputs))
   401  	}
   402  }
   403  
   404  // assertNumChannels checks that the nursery has a given number of active
   405  // channels.
   406  func assertNumChannels(t *testing.T, ns NurseryStorer, expected int) {
   407  	channels, err := ns.ListChannels()
   408  	if err != nil {
   409  		t.Fatalf("unable to fetch channels from nursery store: %v",
   410  			err)
   411  	}
   412  
   413  	if len(channels) != expected {
   414  		t.Fatalf("expected number of active channels to be %d, got %d",
   415  			expected, len(channels))
   416  	}
   417  }
   418  
   419  // assertHeightIsPurged checks that the finalized transaction, kindergarten, and
   420  // htlc outputs at a particular height are all nil.
   421  func assertHeightIsPurged(t *testing.T, ns NurseryStorer,
   422  	height uint32) {
   423  
   424  	kndrOutputs, cribOutputs, err := ns.FetchClass(height)
   425  	if err != nil {
   426  		t.Fatalf("unable to retrieve class at height=%d: %v",
   427  			height, err)
   428  	}
   429  
   430  	if kndrOutputs != nil {
   431  		t.Fatalf("height=%d not purged, kndr outputs should be nil", height)
   432  	}
   433  
   434  	if cribOutputs != nil {
   435  		t.Fatalf("height=%d not purged, crib outputs should be nil", height)
   436  	}
   437  }
   438  
   439  // assertCribAtExpiryHeight loads the class at the given height, and verifies
   440  // that the given htlc output is one of the crib outputs.
   441  func assertCribAtExpiryHeight(t *testing.T, ns NurseryStorer,
   442  	htlcOutput *babyOutput) {
   443  
   444  	expiryHeight := htlcOutput.expiry
   445  	_, cribOutputs, err := ns.FetchClass(expiryHeight)
   446  	if err != nil {
   447  		t.Fatalf("unable to retrieve class at height=%d: %v",
   448  			expiryHeight, err)
   449  	}
   450  
   451  	for _, crib := range cribOutputs {
   452  		if reflect.DeepEqual(&crib, htlcOutput) {
   453  			return
   454  		}
   455  	}
   456  
   457  	t.Fatalf("could not find crib output %v at height %d",
   458  		htlcOutput.OutPoint(), expiryHeight)
   459  }
   460  
   461  // assertCribNotAtExpiryHeight loads the class at the given height, and verifies
   462  // that the given htlc output is not one of the crib outputs.
   463  func assertCribNotAtExpiryHeight(t *testing.T, ns NurseryStorer,
   464  	htlcOutput *babyOutput) {
   465  
   466  	expiryHeight := htlcOutput.expiry
   467  	_, cribOutputs, err := ns.FetchClass(expiryHeight)
   468  	if err != nil {
   469  		t.Fatalf("unable to retrieve class at height %d: %v",
   470  			expiryHeight, err)
   471  	}
   472  
   473  	for _, crib := range cribOutputs {
   474  		if reflect.DeepEqual(&crib, htlcOutput) {
   475  			t.Fatalf("found find crib output %v at height %d",
   476  				htlcOutput.OutPoint(), expiryHeight)
   477  		}
   478  	}
   479  }
   480  
   481  // assertKndrAtMaturityHeight loads the class at the provided height and
   482  // verifies that the provided kid output is one of the kindergarten outputs
   483  // returned.
   484  func assertKndrAtMaturityHeight(t *testing.T, ns NurseryStorer,
   485  	kndrOutput *kidOutput) {
   486  
   487  	maturityHeight := kndrOutput.ConfHeight() +
   488  		kndrOutput.BlocksToMaturity()
   489  	kndrOutputs, _, err := ns.FetchClass(maturityHeight)
   490  	if err != nil {
   491  		t.Fatalf("unable to retrieve class at height %d: %v",
   492  			maturityHeight, err)
   493  	}
   494  
   495  	for _, kndr := range kndrOutputs {
   496  		if reflect.DeepEqual(&kndr, kndrOutput) {
   497  			return
   498  		}
   499  	}
   500  
   501  	t.Fatalf("could not find kndr output %v at height %d",
   502  		kndrOutput.OutPoint(), maturityHeight)
   503  }
   504  
   505  // assertKndrNotAtMaturityHeight loads the class at the provided height and
   506  // verifies that the provided kid output is not one of the kindergarten outputs
   507  // returned.
   508  func assertKndrNotAtMaturityHeight(t *testing.T, ns NurseryStorer,
   509  	kndrOutput *kidOutput) {
   510  
   511  	maturityHeight := kndrOutput.ConfHeight() +
   512  		kndrOutput.BlocksToMaturity()
   513  
   514  	kndrOutputs, _, err := ns.FetchClass(maturityHeight)
   515  	if err != nil {
   516  		t.Fatalf("unable to retrieve class at height %d: %v",
   517  			maturityHeight, err)
   518  	}
   519  
   520  	for _, kndr := range kndrOutputs {
   521  		if reflect.DeepEqual(&kndr, kndrOutput) {
   522  			t.Fatalf("found find kndr output %v at height %d",
   523  				kndrOutput.OutPoint(), maturityHeight)
   524  		}
   525  	}
   526  }
   527  
   528  // assertChannelMaturity queries the nursery store for the maturity of the given
   529  // channel, failing if the result does not match the expectedMaturity.
   530  func assertChannelMaturity(t *testing.T, ns NurseryStorer,
   531  	chanPoint *wire.OutPoint, expectedMaturity bool) {
   532  
   533  	isMature, err := ns.IsMatureChannel(chanPoint)
   534  	if err != nil {
   535  		t.Fatalf("unable to fetch channel maturity: %v", err)
   536  	}
   537  
   538  	if isMature != expectedMaturity {
   539  		t.Fatalf("expected channel maturity: %v, actual: %v",
   540  			expectedMaturity, isMature)
   541  	}
   542  }
   543  
   544  // assertCanRemoveChannel tries to remove a channel from the nursery store,
   545  // failing if the result does match expected canRemove.
   546  func assertCanRemoveChannel(t *testing.T, ns NurseryStorer,
   547  	chanPoint *wire.OutPoint, canRemove bool) {
   548  
   549  	err := ns.RemoveChannel(chanPoint)
   550  	if canRemove && err != nil {
   551  		t.Fatalf("expected nil when removing active channel, got: %v",
   552  			err)
   553  	} else if !canRemove && err != ErrImmatureChannel {
   554  		t.Fatalf("expected ErrImmatureChannel when removing "+
   555  			"active channel: %v", err)
   556  	}
   557  }