gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/contractor/contractor_test.go (about) 1 package contractor 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "testing" 8 "time" 9 10 "gitlab.com/SiaPrime/SiaPrime/build" 11 "gitlab.com/SiaPrime/SiaPrime/modules" 12 "gitlab.com/SiaPrime/SiaPrime/types" 13 ) 14 15 // newStub is used to test the New function. It implements all of the contractor's 16 // dependencies. 17 type newStub struct{} 18 19 // consensus set stubs 20 func (newStub) ConsensusSetSubscribe(modules.ConsensusSetSubscriber, modules.ConsensusChangeID, <-chan struct{}) error { 21 return nil 22 } 23 func (newStub) Synced() bool { return true } 24 func (newStub) Unsubscribe(modules.ConsensusSetSubscriber) { return } 25 26 // wallet stubs 27 func (newStub) NextAddress() (uc types.UnlockConditions, err error) { return } 28 func (newStub) PrimarySeed() (modules.Seed, uint64, error) { return modules.Seed{}, 0, nil } 29 func (newStub) StartTransaction() (tb modules.TransactionBuilder, err error) { return } 30 func (newStub) Unlocked() (bool, error) { return true, nil } 31 32 // transaction pool stubs 33 func (newStub) AcceptTransactionSet([]types.Transaction) error { return nil } 34 func (newStub) FeeEstimation() (a types.Currency, b types.Currency) { return } 35 36 // hdb stubs 37 func (newStub) AllHosts() []modules.HostDBEntry { return nil } 38 func (newStub) ActiveHosts() []modules.HostDBEntry { return nil } 39 func (newStub) CheckForIPViolations([]types.SiaPublicKey) []types.SiaPublicKey { return nil } 40 func (newStub) Filter() (modules.FilterMode, map[string]types.SiaPublicKey) { 41 return 0, make(map[string]types.SiaPublicKey) 42 } 43 func (newStub) SetFilterMode(fm modules.FilterMode, hosts []types.SiaPublicKey) error { return nil } 44 func (newStub) Host(types.SiaPublicKey) (settings modules.HostDBEntry, ok bool) { return } 45 func (newStub) IncrementSuccessfulInteractions(key types.SiaPublicKey) { return } 46 func (newStub) IncrementFailedInteractions(key types.SiaPublicKey) { return } 47 func (newStub) RandomHosts(int, []types.SiaPublicKey, []types.SiaPublicKey) ([]modules.HostDBEntry, error) { 48 return nil, nil 49 } 50 func (newStub) ScoreBreakdown(modules.HostDBEntry) (modules.HostScoreBreakdown, error) { 51 return modules.HostScoreBreakdown{}, nil 52 } 53 func (newStub) SetAllowance(allowance modules.Allowance) error { return nil } 54 func (newStub) UpdateContracts([]modules.RenterContract) error { return nil } 55 func (newStub) SetIPViolationCheck(enabled bool) {} 56 func (newStub) IPViolationsCheck() bool { 57 return false 58 //hdb.hostTree.FilterByIPEnabled() 59 } 60 61 // TestNew tests the New function. 62 func TestNew(t *testing.T) { 63 if testing.Short() { 64 t.SkipNow() 65 } 66 // Using a stub implementation of the dependencies is fine, as long as its 67 // non-nil. 68 var stub newStub 69 dir := build.TempDir("contractor", t.Name()) 70 71 // Sane values. 72 _, err := New(stub, stub, stub, stub, dir) 73 if err != nil { 74 t.Fatalf("expected nil, got %v", err) 75 } 76 77 // Nil consensus set. 78 _, err = New(nil, stub, stub, stub, dir) 79 if err != errNilCS { 80 t.Fatalf("expected %v, got %v", errNilCS, err) 81 } 82 83 // Nil wallet. 84 _, err = New(stub, nil, stub, stub, dir) 85 if err != errNilWallet { 86 t.Fatalf("expected %v, got %v", errNilWallet, err) 87 } 88 89 // Nil transaction pool. 90 _, err = New(stub, stub, nil, stub, dir) 91 if err != errNilTpool { 92 t.Fatalf("expected %v, got %v", errNilTpool, err) 93 } 94 95 // Bad persistDir. 96 _, err = New(stub, stub, stub, stub, "") 97 if !os.IsNotExist(err) { 98 t.Fatalf("expected invalid directory, got %v", err) 99 } 100 } 101 102 // TestAllowance tests the Allowance method. 103 func TestAllowance(t *testing.T) { 104 c := &Contractor{ 105 allowance: modules.Allowance{ 106 Funds: types.NewCurrency64(1), 107 Period: 2, 108 Hosts: 3, 109 }, 110 } 111 a := c.Allowance() 112 if a.Funds.Cmp(c.allowance.Funds) != 0 || 113 a.Period != c.allowance.Period || 114 a.Hosts != c.allowance.Hosts { 115 t.Fatal("Allowance did not return correct allowance:", a, c.allowance) 116 } 117 } 118 119 // stubHostDB mocks the hostDB dependency using zero-valued implementations of 120 // its methods. 121 type stubHostDB struct{} 122 123 func (stubHostDB) AllHosts() (hs []modules.HostDBEntry) { return } 124 func (stubHostDB) ActiveHosts() (hs []modules.HostDBEntry) { return } 125 func (stubHostDB) CheckForIPViolations([]types.SiaPublicKey) []types.SiaPublicKey { return nil } 126 func (stubHostDB) Filter() (modules.FilterMode, map[string]types.SiaPublicKey) { 127 return 0, make(map[string]types.SiaPublicKey) 128 } 129 func (stubHostDB) SetFilterMode(fm modules.FilterMode, hosts []types.SiaPublicKey) error { return nil } 130 func (stubHostDB) Host(types.SiaPublicKey) (h modules.HostDBEntry, ok bool) { return } 131 func (stubHostDB) IncrementSuccessfulInteractions(key types.SiaPublicKey) { return } 132 func (stubHostDB) IncrementFailedInteractions(key types.SiaPublicKey) { return } 133 func (stubHostDB) PublicKey() (spk types.SiaPublicKey) { return } 134 func (stubHostDB) RandomHosts(int, []types.SiaPublicKey, []types.SiaPublicKey) (hs []modules.HostDBEntry, _ error) { 135 return 136 } 137 func (stubHostDB) ScoreBreakdown(modules.HostDBEntry) (modules.HostScoreBreakdown, error) { 138 return modules.HostScoreBreakdown{}, nil 139 } 140 func (stubHostDB) SetAllowance(allowance modules.Allowance) error { return nil } 141 func (stubHostDB) UpdateContracts([]modules.RenterContract) error { return nil } 142 143 func (stubHostDB) SetIPViolationCheck(enabled bool) {} 144 func (stubHostDB) IPViolationsCheck() bool { 145 return false 146 //hdb.hostTree.FilterByIPEnabled() 147 } 148 149 // TestAllowanceSpending verifies that the contractor will not spend more or 150 // less than the allowance if uploading causes repeated early renewal, and that 151 // correct spending metrics are returned, even across renewals. 152 func TestAllowanceSpending(t *testing.T) { 153 if testing.Short() { 154 t.SkipNow() 155 } 156 t.Parallel() 157 158 // create testing trio 159 h, c, m, err := newTestingTrio(t.Name()) 160 if err != nil { 161 t.Fatal(err) 162 } 163 164 // make the host's upload price very high so this test requires less 165 // computation 166 settings := h.InternalSettings() 167 settings.MinUploadBandwidthPrice = types.SiacoinPrecision.Div64(10) 168 err = h.SetInternalSettings(settings) 169 if err != nil { 170 t.Fatal(err) 171 } 172 err = h.Announce() 173 if err != nil { 174 t.Fatal(err) 175 } 176 _, err = m.AddBlock() 177 if err != nil { 178 t.Fatal(err) 179 } 180 err = build.Retry(50, 100*time.Millisecond, func() error { 181 hosts, err := c.hdb.RandomHosts(1, nil, nil) 182 if err != nil { 183 return err 184 } 185 if len(hosts) == 0 { 186 return errors.New("host has not been scanned yet") 187 } 188 return nil 189 }) 190 if err != nil { 191 t.Fatal(err) 192 } 193 194 // set an allowance 195 testAllowance := modules.Allowance{ 196 Funds: types.SiacoinPrecision.Mul64(6000), 197 RenewWindow: 100, 198 Hosts: 1, 199 Period: 200, 200 ExpectedStorage: modules.DefaultAllowance.ExpectedStorage, 201 ExpectedUpload: modules.DefaultAllowance.ExpectedUpload, 202 ExpectedDownload: modules.DefaultAllowance.ExpectedDownload, 203 ExpectedRedundancy: modules.DefaultAllowance.ExpectedRedundancy, 204 } 205 err = c.SetAllowance(testAllowance) 206 if err != nil { 207 t.Fatal(err) 208 } 209 err = build.Retry(50, 100*time.Millisecond, func() error { 210 if len(c.Contracts()) != 1 { 211 return errors.New("allowance forming seems to have failed") 212 } 213 return nil 214 }) 215 if err != nil { 216 t.Error(err) 217 } 218 219 // exhaust a contract and add a block several times. Despite repeatedly 220 // running out of funds, the contractor should not spend more than the 221 // allowance. 222 for i := 0; i < 15; i++ { 223 for _, contract := range c.Contracts() { 224 ed, err := c.Editor(contract.HostPublicKey, nil) 225 if err != nil { 226 continue 227 } 228 229 // upload 10 sectors to the contract 230 for sec := 0; sec < 10; sec++ { 231 ed.Upload(make([]byte, modules.SectorSize)) 232 } 233 err = ed.Close() 234 if err != nil { 235 t.Fatal(err) 236 } 237 } 238 _, err := m.AddBlock() 239 if err != nil { 240 t.Fatal(err) 241 } 242 } 243 244 var minerRewards types.Currency 245 w := c.wallet.(*WalletBridge).W.(modules.Wallet) 246 txns, err := w.Transactions(0, 1000) 247 if err != nil { 248 t.Fatal(err) 249 } 250 for _, txn := range txns { 251 for _, so := range txn.Outputs { 252 if so.FundType == types.SpecifierMinerPayout && so.RelatedAddress != types.DevFundUnlockHash { 253 minerRewards = minerRewards.Add(so.Value) 254 } 255 } 256 } 257 balance, _, _, err := w.ConfirmedBalance() 258 if err != nil { 259 t.Fatal(err) 260 } 261 spent := minerRewards.Sub(balance) 262 if spent.Cmp(testAllowance.Funds) > 0 { 263 t.Fatal("contractor spent too much money: spent", spent.HumanString(), "allowance funds:", testAllowance.Funds.HumanString()) 264 } 265 266 // we should have spent at least the allowance minus the cost of one more refresh 267 refreshCost := c.Contracts()[0].TotalCost.Mul64(2) 268 expectedMinSpending := testAllowance.Funds.Sub(refreshCost) 269 if spent.Cmp(expectedMinSpending) < 0 { 270 t.Fatal("contractor spent to little money: spent", spent.HumanString(), "expected at least:", expectedMinSpending.HumanString()) 271 } 272 273 // PeriodSpending should reflect the amount of spending accurately 274 reportedSpending := c.PeriodSpending() 275 if reportedSpending.TotalAllocated.Cmp(spent) != 0 { 276 t.Fatal("reported incorrect spending for this billing cycle: got", reportedSpending.TotalAllocated.HumanString(), "wanted", spent.HumanString()) 277 } 278 // COMPATv132 totalallocated should equal contractspending field. 279 if reportedSpending.ContractSpendingDeprecated.Cmp(reportedSpending.TotalAllocated) != 0 { 280 t.Fatal("TotalAllocated should be equal to ContractSpending for compatibility") 281 } 282 283 var expectedFees types.Currency 284 for _, contract := range c.Contracts() { 285 expectedFees = expectedFees.Add(contract.TxnFee) 286 expectedFees = expectedFees.Add(contract.SiafundFee) 287 expectedFees = expectedFees.Add(contract.ContractFee) 288 } 289 if expectedFees.Cmp(reportedSpending.ContractFees) != 0 { 290 t.Fatalf("expected %v reported fees but was %v", 291 expectedFees.HumanString(), reportedSpending.ContractFees.HumanString()) 292 } 293 } 294 295 // TestIntegrationSetAllowance tests the SetAllowance method. 296 func TestIntegrationSetAllowance(t *testing.T) { 297 if testing.Short() { 298 t.SkipNow() 299 } 300 // create testing trio 301 _, c, m, err := newTestingTrio(t.Name()) 302 if err != nil { 303 t.Fatal(err) 304 } 305 306 // this test requires two hosts: create another one 307 h, err := newTestingHost(build.TempDir("hostdata", ""), c.cs.(modules.ConsensusSet), c.tpool.(modules.TransactionPool)) 308 if err != nil { 309 t.Fatal(err) 310 } 311 312 // announce the extra host 313 err = h.Announce() 314 if err != nil { 315 t.Fatal(err) 316 } 317 318 // mine a block, processing the announcement 319 _, err = m.AddBlock() 320 if err != nil { 321 t.Fatal(err) 322 } 323 324 // wait for hostdb to scan 325 hosts, err := c.hdb.RandomHosts(1, nil, nil) 326 if err != nil { 327 t.Fatal("failed to get hosts", err) 328 } 329 for i := 0; i < 100 && len(hosts) == 0; i++ { 330 time.Sleep(time.Millisecond * 50) 331 } 332 333 // cancel allowance 334 var a modules.Allowance 335 err = c.SetAllowance(a) 336 if err != nil { 337 t.Fatal(err) 338 } 339 340 // bad args 341 a.Hosts = 1 342 err = c.SetAllowance(a) 343 if err != errAllowanceZeroPeriod { 344 t.Errorf("expected %q, got %q", errAllowanceZeroPeriod, err) 345 } 346 a.Period = 20 347 err = c.SetAllowance(a) 348 if err != ErrAllowanceZeroWindow { 349 t.Errorf("expected %q, got %q", ErrAllowanceZeroWindow, err) 350 } 351 a.RenewWindow = 20 352 err = c.SetAllowance(a) 353 if err != errAllowanceWindowSize { 354 t.Errorf("expected %q, got %q", errAllowanceWindowSize, err) 355 } 356 a.RenewWindow = 10 357 err = c.SetAllowance(a) 358 if err != errAllowanceZeroExpectedStorage { 359 t.Errorf("expected %q, got %q", errAllowanceZeroExpectedStorage, err) 360 } 361 a.ExpectedStorage = modules.DefaultAllowance.ExpectedStorage 362 err = c.SetAllowance(a) 363 if err != errAllowanceZeroExpectedUpload { 364 t.Errorf("expected %q, got %q", errAllowanceZeroExpectedUpload, err) 365 } 366 a.ExpectedUpload = modules.DefaultAllowance.ExpectedUpload 367 err = c.SetAllowance(a) 368 if err != errAllowanceZeroExpectedDownload { 369 t.Errorf("expected %q, got %q", errAllowanceZeroExpectedDownload, err) 370 } 371 a.ExpectedDownload = modules.DefaultAllowance.ExpectedDownload 372 err = c.SetAllowance(a) 373 if err != errAllowanceZeroExpectedRedundancy { 374 t.Errorf("expected %q, got %q", errAllowanceZeroExpectedRedundancy, err) 375 } 376 a.ExpectedRedundancy = modules.DefaultAllowance.ExpectedRedundancy 377 378 // reasonable values; should succeed 379 a.Funds = types.SiacoinPrecision.Mul64(100) 380 err = c.SetAllowance(a) 381 if err != nil { 382 t.Fatal(err) 383 } 384 err = build.Retry(50, 100*time.Millisecond, func() error { 385 if len(c.Contracts()) != 1 { 386 return errors.New("allowance forming seems to have failed") 387 } 388 return nil 389 }) 390 if err != nil { 391 t.Error(err) 392 } 393 394 // set same allowance; should no-op 395 err = c.SetAllowance(a) 396 if err != nil { 397 t.Fatal(err) 398 } 399 clen := c.staticContracts.Len() 400 if clen != 1 { 401 t.Fatal("expected 1 contract, got", clen) 402 } 403 404 _, err = m.AddBlock() 405 if err != nil { 406 t.Fatal(err) 407 } 408 409 // set allowance with Hosts = 2; should only form one new contract 410 a.Hosts = 2 411 err = c.SetAllowance(a) 412 if err != nil { 413 t.Fatal(err) 414 } 415 err = build.Retry(50, 100*time.Millisecond, func() error { 416 if len(c.Contracts()) != 2 { 417 return errors.New("allowance forming seems to have failed") 418 } 419 return nil 420 }) 421 if err != nil { 422 t.Fatal(err) 423 } 424 425 // set allowance with Funds*2; should trigger renewal of both contracts 426 a.Funds = a.Funds.Mul64(2) 427 err = c.SetAllowance(a) 428 if err != nil { 429 t.Fatal(err) 430 } 431 err = build.Retry(50, 100*time.Millisecond, func() error { 432 if len(c.Contracts()) != 2 { 433 return errors.New("allowance forming seems to have failed") 434 } 435 return nil 436 }) 437 if err != nil { 438 t.Error(err) 439 } 440 441 // delete one of the contracts and set allowance with Funds*2; should 442 // trigger 1 renewal and 1 new contract 443 c.mu.Lock() 444 ids := c.staticContracts.IDs() 445 contract, _ := c.staticContracts.Acquire(ids[0]) 446 c.staticContracts.Delete(contract) 447 c.mu.Unlock() 448 a.Funds = a.Funds.Mul64(2) 449 err = c.SetAllowance(a) 450 if err != nil { 451 t.Fatal(err) 452 } 453 err = build.Retry(50, 100*time.Millisecond, func() error { 454 if len(c.Contracts()) != 2 { 455 return errors.New("allowance forming seems to have failed") 456 } 457 return nil 458 }) 459 if err != nil { 460 t.Fatal(err) 461 } 462 } 463 464 // TestHostMaxDuration tests that a host will not be used if their max duration 465 // is not sufficient when renewing contracts 466 func TestHostMaxDuration(t *testing.T) { 467 if testing.Short() { 468 t.SkipNow() 469 } 470 t.Parallel() 471 472 // create testing trio 473 h, c, m, err := newTestingTrio(t.Name()) 474 if err != nil { 475 t.Fatal(err) 476 } 477 478 // Set host's MaxDuration to 5 to test if host will be skipped when contract 479 // is formed 480 settings := h.InternalSettings() 481 settings.MaxDuration = types.BlockHeight(5) 482 if err := h.SetInternalSettings(settings); err != nil { 483 t.Fatal(err) 484 } 485 // Let host settings permeate 486 err = build.Retry(50, 100*time.Millisecond, func() error { 487 host, _ := c.hdb.Host(h.PublicKey()) 488 if settings.MaxDuration != host.MaxDuration { 489 return fmt.Errorf("host max duration not set, expected %v, got %v", settings.MaxDuration, host.MaxDuration) 490 } 491 return nil 492 }) 493 if err != nil { 494 t.Fatal(err) 495 } 496 497 // Create allowance 498 a := modules.Allowance{ 499 Funds: types.SiacoinPrecision.Mul64(100), 500 Hosts: 1, 501 Period: 30, 502 RenewWindow: 20, 503 ExpectedStorage: modules.DefaultAllowance.ExpectedStorage, 504 ExpectedUpload: modules.DefaultAllowance.ExpectedUpload, 505 ExpectedDownload: modules.DefaultAllowance.ExpectedDownload, 506 ExpectedRedundancy: modules.DefaultAllowance.ExpectedRedundancy, 507 } 508 err = c.SetAllowance(a) 509 if err != nil { 510 t.Fatal(err) 511 } 512 513 // Wait for and confirm no Contract creation 514 err = build.Retry(50, 100*time.Millisecond, func() error { 515 if len(c.Contracts()) == 0 { 516 return errors.New("no contract created") 517 } 518 return nil 519 }) 520 if err == nil { 521 t.Fatal("Contract should not have been created") 522 } 523 524 // Set host's MaxDuration to 50 to test if host will now form contract 525 settings = h.InternalSettings() 526 settings.MaxDuration = types.BlockHeight(50) 527 if err := h.SetInternalSettings(settings); err != nil { 528 t.Fatal(err) 529 } 530 // Let host settings permeate 531 err = build.Retry(50, 100*time.Millisecond, func() error { 532 host, _ := c.hdb.Host(h.PublicKey()) 533 if settings.MaxDuration != host.MaxDuration { 534 return fmt.Errorf("host max duration not set, expected %v, got %v", settings.MaxDuration, host.MaxDuration) 535 } 536 return nil 537 }) 538 if err != nil { 539 t.Fatal(err) 540 } 541 _, err = m.AddBlock() 542 if err != nil { 543 t.Fatal(err) 544 } 545 546 // Wait for Contract creation 547 err = build.Retry(600, 100*time.Millisecond, func() error { 548 if len(c.Contracts()) != 1 { 549 return errors.New("no contract created") 550 } 551 return nil 552 }) 553 if err != nil { 554 t.Error(err) 555 } 556 557 // Set host's MaxDuration to 5 to test if host will be skipped when contract 558 // is renewed 559 settings = h.InternalSettings() 560 settings.MaxDuration = types.BlockHeight(5) 561 if err := h.SetInternalSettings(settings); err != nil { 562 t.Fatal(err) 563 } 564 // Let host settings permeate 565 err = build.Retry(50, 100*time.Millisecond, func() error { 566 host, _ := c.hdb.Host(h.PublicKey()) 567 if settings.MaxDuration != host.MaxDuration { 568 return fmt.Errorf("host max duration not set, expected %v, got %v", settings.MaxDuration, host.MaxDuration) 569 } 570 return nil 571 }) 572 if err != nil { 573 t.Fatal(err) 574 } 575 576 // Mine blocks to renew contract 577 for i := types.BlockHeight(0); i <= c.allowance.Period-c.allowance.RenewWindow; i++ { 578 _, err = m.AddBlock() 579 if err != nil { 580 t.Fatal(err) 581 } 582 } 583 584 // Confirm Contract is not renewed 585 err = build.Retry(50, 100*time.Millisecond, func() error { 586 if len(c.OldContracts()) == 0 { 587 return errors.New("no contract renewed") 588 } 589 return nil 590 }) 591 if err == nil { 592 t.Fatal("Contract should not have been renewed") 593 } 594 } 595 596 // TestLinkedContracts tests that the contractors maps are updated correctly 597 // when renewing contracts 598 func TestLinkedContracts(t *testing.T) { 599 if testing.Short() { 600 t.SkipNow() 601 } 602 t.Parallel() 603 604 // create testing trio 605 h, c, m, err := newTestingTrio(t.Name()) 606 if err != nil { 607 t.Fatal(err) 608 } 609 610 // Create allowance 611 a := modules.Allowance{ 612 Funds: types.SiacoinPrecision.Mul64(100), 613 Hosts: 1, 614 Period: 20, 615 RenewWindow: 10, 616 ExpectedStorage: modules.DefaultAllowance.ExpectedStorage, 617 ExpectedUpload: modules.DefaultAllowance.ExpectedUpload, 618 ExpectedDownload: modules.DefaultAllowance.ExpectedDownload, 619 ExpectedRedundancy: modules.DefaultAllowance.ExpectedRedundancy, 620 } 621 err = c.SetAllowance(a) 622 if err != nil { 623 t.Fatal(err) 624 } 625 626 // Wait for Contract creation 627 err = build.Retry(200, 100*time.Millisecond, func() error { 628 if len(c.Contracts()) != 1 { 629 return errors.New("no contract created") 630 } 631 return nil 632 }) 633 if err != nil { 634 t.Error(err) 635 } 636 637 // Confirm that maps are empty 638 if len(c.renewedFrom) != 0 { 639 t.Fatal("renewedFrom map should be empty") 640 } 641 if len(c.renewedTo) != 0 { 642 t.Fatal("renewedTo map should be empty") 643 } 644 645 // Set host's uploadbandwidthprice to zero to test divide by zero check when 646 // contracts are renewed 647 settings := h.InternalSettings() 648 settings.MinUploadBandwidthPrice = types.ZeroCurrency 649 if err := h.SetInternalSettings(settings); err != nil { 650 t.Fatal(err) 651 } 652 653 // Mine blocks to renew contract 654 for i := types.BlockHeight(0); i < c.allowance.Period-c.allowance.RenewWindow; i++ { 655 _, err = m.AddBlock() 656 if err != nil { 657 t.Fatal(err) 658 } 659 } 660 661 // Confirm Contracts got renewed 662 err = build.Retry(200, 100*time.Millisecond, func() error { 663 if len(c.Contracts()) != 1 { 664 return errors.New("no contract") 665 } 666 if len(c.OldContracts()) != 1 { 667 return errors.New("no old contract") 668 } 669 return nil 670 }) 671 if err != nil { 672 t.Error(err) 673 } 674 675 // Confirm maps are updated as expected 676 if len(c.renewedFrom) != 1 { 677 t.Fatalf("renewedFrom map should have 1 entry but has %v", len(c.renewedFrom)) 678 } 679 if len(c.renewedTo) != 1 { 680 t.Fatalf("renewedTo map should have 1 entry but has %v", len(c.renewedTo)) 681 } 682 if c.renewedFrom[c.Contracts()[0].ID] != c.OldContracts()[0].ID { 683 t.Fatalf(`Map assignment incorrect, 684 expected: 685 map[%v:%v] 686 got: 687 %v`, c.Contracts()[0].ID, c.OldContracts()[0].ID, c.renewedFrom) 688 } 689 if c.renewedTo[c.OldContracts()[0].ID] != c.Contracts()[0].ID { 690 t.Fatalf(`Map assignment incorrect, 691 expected: 692 map[%v:%v] 693 got: 694 %v`, c.OldContracts()[0].ID, c.Contracts()[0].ID, c.renewedTo) 695 } 696 } 697 698 // testWalletShim is used to test the walletBridge type. 699 type testWalletShim struct { 700 nextAddressCalled bool 701 startTxnCalled bool 702 } 703 704 // These stub implementations for the walletShim interface set their respective 705 // booleans to true, allowing tests to verify that they have been called. 706 func (ws *testWalletShim) NextAddress() (types.UnlockConditions, error) { 707 ws.nextAddressCalled = true 708 return types.UnlockConditions{}, nil 709 } 710 func (ws *testWalletShim) PrimarySeed() (modules.Seed, uint64, error) { 711 return modules.Seed{}, 0, nil 712 } 713 func (ws *testWalletShim) StartTransaction() (modules.TransactionBuilder, error) { 714 ws.startTxnCalled = true 715 return nil, nil 716 } 717 func (ws *testWalletShim) Unlocked() (bool, error) { return true, nil } 718 719 // TestWalletBridge tests the walletBridge type. 720 func TestWalletBridge(t *testing.T) { 721 shim := new(testWalletShim) 722 bridge := WalletBridge{shim} 723 bridge.NextAddress() 724 if !shim.nextAddressCalled { 725 t.Error("NextAddress was not called on the shim") 726 } 727 bridge.StartTransaction() 728 if !shim.startTxnCalled { 729 t.Error("StartTransaction was not called on the shim") 730 } 731 }