github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/renter/contractor/formcontract.go (about) 1 package contractor 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 8 "github.com/NebulousLabs/Sia/modules" 9 "github.com/NebulousLabs/Sia/modules/renter/proto" 10 "github.com/NebulousLabs/Sia/types" 11 ) 12 13 var ( 14 // the contractor will not form contracts above this price 15 maxStoragePrice = types.SiacoinPrecision.Mul64(500e3).Mul(modules.BlockBytesPerMonthTerabyte) // 500k SC / TB / Month 16 // the contractor will not download data above this price (3x the maximum monthly storage price) 17 maxDownloadPrice = maxStoragePrice.Mul64(3 * 4320) 18 19 errInsufficientAllowance = errors.New("allowance is not large enough to perform contract creation") 20 errTooExpensive = errors.New("host price was too high") 21 ) 22 23 // managedNewContract negotiates an initial file contract with the specified 24 // host, saves it, and returns it. 25 func (c *Contractor) managedNewContract(host modules.HostDBEntry, filesize uint64, endHeight types.BlockHeight) (modules.RenterContract, error) { 26 // reject hosts that are too expensive 27 if host.StoragePrice.Cmp(maxStoragePrice) > 0 { 28 return modules.RenterContract{}, errTooExpensive 29 } 30 31 // get an address to use for negotiation 32 uc, err := c.wallet.NextAddress() 33 if err != nil { 34 return modules.RenterContract{}, err 35 } 36 37 // create contract params 38 c.mu.RLock() 39 params := proto.ContractParams{ 40 Host: host, 41 Filesize: filesize, 42 StartHeight: c.blockHeight, 43 EndHeight: endHeight, 44 RefundAddress: uc.UnlockHash(), 45 } 46 c.mu.RUnlock() 47 48 // create transaction builder 49 txnBuilder := c.wallet.StartTransaction() 50 51 contract, err := proto.FormContract(params, txnBuilder, c.tpool) 52 if err != nil { 53 txnBuilder.Drop() 54 return modules.RenterContract{}, err 55 } 56 57 c.mu.Lock() 58 c.contracts[contract.ID] = contract 59 c.saveSync() 60 c.mu.Unlock() 61 62 contractValue := contract.LastRevision.NewValidProofOutputs[0].Value 63 c.log.Printf("Formed contract with %v for %v SC", host.NetAddress, contractValue.Div(types.SiacoinPrecision)) 64 65 return contract, nil 66 } 67 68 // formContracts forms contracts with hosts using the allowance parameters. 69 func (c *Contractor) formContracts(a modules.Allowance) error { 70 // Sample at least 10 hosts. 71 nRandomHosts := 2 * int(a.Hosts) 72 if nRandomHosts < 10 { 73 nRandomHosts = 10 74 } 75 hosts := c.hdb.RandomHosts(nRandomHosts, nil) 76 if uint64(len(hosts)) < a.Hosts/2 { // TODO: /2 is temporary until more hosts are online 77 return errors.New("not enough hosts") 78 } 79 // Calculate average host price. 80 var sum types.Currency 81 for _, h := range hosts { 82 sum = sum.Add(h.StoragePrice) 83 } 84 avgPrice := sum.Div64(uint64(len(hosts))) 85 86 // Check that allowance is sufficient to store at least one sector per 87 // host for the specified duration. 88 costPerSector := avgPrice.Mul64(a.Hosts).Mul64(modules.SectorSize).Mul64(uint64(a.Period)) 89 if a.Funds.Cmp(costPerSector) < 0 { 90 return errInsufficientAllowance 91 } 92 93 // Calculate the filesize of the contracts by using the average host price 94 // and rounding down to the nearest sector. 95 numSectors, err := a.Funds.Div(costPerSector).Uint64() 96 if err != nil { 97 // if there was an overflow, something is definitely wrong 98 return errors.New("allowance resulted in unexpectedly large contract size") 99 } 100 filesize := numSectors * modules.SectorSize 101 102 c.mu.RLock() 103 endHeight := c.blockHeight + a.Period 104 c.mu.RUnlock() 105 106 // Form contracts with each host. 107 var numContracts uint64 108 var errs []string 109 for _, h := range hosts { 110 _, err := c.managedNewContract(h, filesize, endHeight) 111 if err != nil { 112 errs = append(errs, fmt.Sprintf("\t%v: %v", h.NetAddress, err)) 113 continue 114 } 115 if numContracts++; numContracts >= a.Hosts { 116 break 117 } 118 } 119 // If we couldn't form any contracts, return an error. Otherwise, just log 120 // the failures. 121 // TODO: is there a better way to handle failure here? Should we prefer an 122 // all-or-nothing approach? We can't pick new hosts to negotiate with 123 // because they'll probably be more expensive than we can afford. 124 if numContracts == 0 { 125 return errors.New("could not form any contracts:\n" + strings.Join(errs, "\n")) 126 } else if numContracts < a.Hosts { 127 c.log.Printf("WARN: failed to form desired number of contracts (wanted %v, got %v):\n%v", a.Hosts, numContracts, strings.Join(errs, "\n")) 128 } 129 c.mu.Lock() 130 c.renewHeight = endHeight 131 c.mu.Unlock() 132 return nil 133 }