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  }