gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/contractor/persist.go (about) 1 package contractor 2 3 import ( 4 "os" 5 "path/filepath" 6 "reflect" 7 8 "gitlab.com/NebulousLabs/errors" 9 "gitlab.com/NebulousLabs/ratelimit" 10 11 "gitlab.com/SkynetLabs/skyd/skymodules" 12 "gitlab.com/SkynetLabs/skyd/skymodules/renter/proto" 13 "go.sia.tech/siad/modules" 14 "go.sia.tech/siad/persist" 15 "go.sia.tech/siad/types" 16 ) 17 18 var ( 19 persistMeta = persist.Metadata{ 20 Header: "Contractor Persistence", 21 Version: "1.3.1", 22 } 23 24 // PersistFilename is the filename to be used when persisting contractor 25 // information to a JSON file 26 PersistFilename = "contractor.json" 27 ) 28 29 // contractorPersist defines what Contractor data persists across sessions. 30 type contractorPersist struct { 31 Allowance skymodules.Allowance `json:"allowance"` 32 BlockHeight types.BlockHeight `json:"blockheight"` 33 CurrentPeriod types.BlockHeight `json:"currentperiod"` 34 LastChange modules.ConsensusChangeID `json:"lastchange"` 35 RecentRecoveryChange modules.ConsensusChangeID `json:"recentrecoverychange"` 36 OldContracts []skymodules.RenterContract `json:"oldcontracts"` 37 DoubleSpentContracts map[string]types.BlockHeight `json:"doublespentcontracts"` 38 PreferredHosts []string `json:"preferredhosts"` 39 RecoverableContracts []skymodules.RecoverableContract `json:"recoverablecontracts"` 40 RenewedFrom map[string]types.FileContractID `json:"renewedfrom"` 41 RenewedTo map[string]types.FileContractID `json:"renewedto"` 42 Synced bool `json:"synced"` 43 44 // Subsystem persistence: 45 ChurnLimiter churnLimiterPersist `json:"churnlimiter"` 46 WatchdogData watchdogPersist `json:"watchdogdata"` 47 } 48 49 // persistData returns the data in the Contractor that will be saved to disk. 50 func (c *Contractor) persistData() contractorPersist { 51 synced := false 52 select { 53 case <-c.synced: 54 synced = true 55 default: 56 } 57 data := contractorPersist{ 58 Allowance: c.allowance, 59 BlockHeight: c.blockHeight, 60 CurrentPeriod: c.currentPeriod, 61 LastChange: c.lastChange, 62 RecentRecoveryChange: c.recentRecoveryChange, 63 RenewedFrom: make(map[string]types.FileContractID), 64 RenewedTo: make(map[string]types.FileContractID), 65 DoubleSpentContracts: make(map[string]types.BlockHeight), 66 PreferredHosts: make([]string, 0, len(c.preferredHosts)), 67 Synced: synced, 68 } 69 for k, v := range c.renewedFrom { 70 data.RenewedFrom[k.String()] = v 71 } 72 for k, v := range c.renewedTo { 73 data.RenewedTo[k.String()] = v 74 } 75 for _, contract := range c.oldContracts { 76 data.OldContracts = append(data.OldContracts, contract) 77 } 78 for fcID, height := range c.doubleSpentContracts { 79 data.DoubleSpentContracts[fcID.String()] = height 80 } 81 for _, contract := range c.recoverableContracts { 82 data.RecoverableContracts = append(data.RecoverableContracts, contract) 83 } 84 for host := range c.preferredHosts { 85 data.PreferredHosts = append(data.PreferredHosts, host) 86 } 87 data.ChurnLimiter = c.staticChurnLimiter.callPersistData() 88 data.WatchdogData = c.staticWatchdog.callPersistData() 89 return data 90 } 91 92 // load loads the Contractor persistence data from disk. 93 func (c *Contractor) load() error { 94 var data contractorPersist 95 err := persist.LoadJSON(persistMeta, &data, filepath.Join(c.persistDir, PersistFilename)) 96 if err != nil { 97 return err 98 } 99 100 // Compatibility code for allowance definition changes. 101 if !reflect.DeepEqual(data.Allowance, skymodules.Allowance{}) { 102 // COMPATv136 if the allowance is not the empty allowance and "Expected" 103 // fields are not set, set them to the default values. 104 if data.Allowance.ExpectedStorage == 0 && data.Allowance.ExpectedUpload == 0 && 105 data.Allowance.ExpectedDownload == 0 && data.Allowance.ExpectedRedundancy == 0 && 106 data.Allowance.MaxPeriodChurn == 0 { 107 // Set the fields to the defaults. 108 data.Allowance.ExpectedStorage = skymodules.DefaultAllowance.ExpectedStorage 109 data.Allowance.ExpectedUpload = skymodules.DefaultAllowance.ExpectedUpload 110 data.Allowance.ExpectedDownload = skymodules.DefaultAllowance.ExpectedDownload 111 data.Allowance.ExpectedRedundancy = skymodules.DefaultAllowance.ExpectedRedundancy 112 data.Allowance.MaxPeriodChurn = skymodules.DefaultAllowance.MaxPeriodChurn 113 } 114 115 // COMPATv1412 if the allowance is not the empty allowance and 116 // MaxPeriodChurn is 0, set it to the default value. 117 if data.Allowance.MaxPeriodChurn == 0 { 118 data.Allowance.MaxPeriodChurn = skymodules.DefaultAllowance.MaxPeriodChurn 119 } 120 } 121 122 c.allowance = data.Allowance 123 c.blockHeight = data.BlockHeight 124 c.currentPeriod = data.CurrentPeriod 125 c.lastChange = data.LastChange 126 c.synced = make(chan struct{}) 127 if data.Synced { 128 close(c.synced) 129 } 130 c.recentRecoveryChange = data.RecentRecoveryChange 131 var fcid types.FileContractID 132 for k, v := range data.RenewedFrom { 133 if err := fcid.LoadString(k); err != nil { 134 return err 135 } 136 c.renewedFrom[fcid] = v 137 } 138 for k, v := range data.RenewedTo { 139 if err := fcid.LoadString(k); err != nil { 140 return err 141 } 142 c.renewedTo[fcid] = v 143 } 144 for _, contract := range data.OldContracts { 145 c.oldContracts[contract.ID] = contract 146 } 147 for fcIDString, height := range data.DoubleSpentContracts { 148 if err := fcid.LoadString(fcIDString); err != nil { 149 return err 150 } 151 c.doubleSpentContracts[fcid] = height 152 } 153 for _, contract := range data.RecoverableContracts { 154 c.recoverableContracts[contract.ID] = contract 155 } 156 for _, host := range data.PreferredHosts { 157 c.preferredHosts[host] = struct{}{} 158 } 159 160 c.staticChurnLimiter = newChurnLimiterFromPersist(c, data.ChurnLimiter) 161 162 c.staticWatchdog, err = newWatchdogFromPersist(c, data.WatchdogData) 163 if err != nil { 164 return err 165 } 166 c.staticWatchdog.renewWindow = data.Allowance.RenewWindow 167 c.staticWatchdog.blockHeight = data.BlockHeight 168 return nil 169 } 170 171 // save saves the Contractor persistence data to disk. 172 func (c *Contractor) save() error { 173 // c.persistData is broken out because stack traces will not include the 174 // function call otherwise. 175 persistData := c.persistData() 176 filename := filepath.Join(c.persistDir, PersistFilename) 177 return persist.SaveJSON(persistMeta, persistData, filename) 178 } 179 180 // convertPersist converts the pre-v1.3.1 contractor persist formats to the new 181 // formats. 182 func convertPersist(dir string, rl *ratelimit.RateLimit) (err error) { 183 // Try loading v1.3.1 persist. If it has the correct version number, no 184 // further action is necessary. 185 persistPath := filepath.Join(dir, PersistFilename) 186 err = persist.LoadJSON(persistMeta, nil, persistPath) 187 if err == nil { 188 return nil 189 } 190 191 // Try loading v1.3.0 persist (journal). 192 journalPath := filepath.Join(dir, "contractor.journal") 193 if _, err := os.Stat(journalPath); os.IsNotExist(err) { 194 // no journal file found; assume this is a fresh install 195 return nil 196 } 197 var p journalPersist 198 j, err := openJournal(journalPath, &p) 199 if err != nil { 200 return err 201 } 202 j.Close() 203 // convert to v1.3.1 format and save 204 data := contractorPersist{ 205 Allowance: p.Allowance, 206 BlockHeight: p.BlockHeight, 207 CurrentPeriod: p.CurrentPeriod, 208 LastChange: p.LastChange, 209 } 210 for _, c := range p.OldContracts { 211 data.OldContracts = append(data.OldContracts, skymodules.RenterContract{ 212 ID: c.ID, 213 HostPublicKey: c.HostPublicKey, 214 StartHeight: c.StartHeight, 215 EndHeight: c.EndHeight(), 216 RenterFunds: c.RenterFunds(), 217 DownloadSpending: c.DownloadSpending, 218 StorageSpending: c.StorageSpending, 219 UploadSpending: c.UploadSpending, 220 TotalCost: c.TotalCost, 221 ContractFee: c.ContractFee, 222 TxnFee: c.TxnFee, 223 SiafundFee: c.SiafundFee, 224 }) 225 } 226 err = persist.SaveJSON(persistMeta, data, persistPath) 227 if err != nil { 228 return err 229 } 230 231 // create the contracts directory if it does not yet exist 232 cs, err := proto.NewContractSet(filepath.Join(dir, "contracts"), rl, modules.ProdDependencies) 233 if err != nil { 234 return err 235 } 236 defer func() { 237 err = errors.Compose(err, cs.Close()) 238 }() 239 240 // convert contracts to contract files 241 for _, c := range p.Contracts { 242 cachedRev := p.CachedRevisions[c.ID.String()] 243 if err := cs.ConvertV130Contract(c, cachedRev); err != nil { 244 return err 245 } 246 } 247 248 // delete the journal file 249 return errors.AddContext(os.Remove(journalPath), "failed to remove journal file") 250 }