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 }