gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/proto/contractset.go (about)

     1  package proto
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"sync"
     8  
     9  	"gitlab.com/NebulousLabs/ratelimit"
    10  	"gitlab.com/SiaPrime/SiaPrime/build"
    11  	"gitlab.com/SiaPrime/SiaPrime/crypto"
    12  	"gitlab.com/SiaPrime/SiaPrime/modules"
    13  	"gitlab.com/SiaPrime/SiaPrime/types"
    14  	"gitlab.com/SiaPrime/writeaheadlog"
    15  
    16  	"gitlab.com/NebulousLabs/errors"
    17  )
    18  
    19  // A ContractSet provides safe concurrent access to a set of contracts. Its
    20  // purpose is to serialize modifications to individual contracts, as well as
    21  // to provide operations on the set as a whole.
    22  type ContractSet struct {
    23  	contracts map[types.FileContractID]*SafeContract
    24  	pubKeys   map[string]types.FileContractID
    25  	deps      modules.Dependencies
    26  	dir       string
    27  	mu        sync.Mutex
    28  	rl        *ratelimit.RateLimit
    29  	globalRL  *ratelimit.RateLimit
    30  	wal       *writeaheadlog.WAL
    31  }
    32  
    33  // Acquire looks up the contract for the specified host key and locks it before
    34  // returning it. If the contract is not present in the set, Acquire returns
    35  // false and a zero-valued RenterContract.
    36  func (cs *ContractSet) Acquire(id types.FileContractID) (*SafeContract, bool) {
    37  	cs.mu.Lock()
    38  	safeContract, ok := cs.contracts[id]
    39  	cs.mu.Unlock()
    40  	if !ok {
    41  		return nil, false
    42  	}
    43  	safeContract.revisionMu.Lock()
    44  	// We need to check if the contract is still in the map or if it has been
    45  	// deleted in the meantime.
    46  	cs.mu.Lock()
    47  	_, ok = cs.contracts[id]
    48  	cs.mu.Unlock()
    49  	if !ok {
    50  		safeContract.revisionMu.Unlock()
    51  		return nil, false
    52  	}
    53  	return safeContract, true
    54  }
    55  
    56  // Delete removes a contract from the set. The contract must have been
    57  // previously acquired by Acquire. If the contract is not present in the set,
    58  // Delete is a no-op.
    59  func (cs *ContractSet) Delete(c *SafeContract) {
    60  	cs.mu.Lock()
    61  	_, ok := cs.contracts[c.header.ID()]
    62  	if !ok {
    63  		cs.mu.Unlock()
    64  		build.Critical("Delete called on already deleted contract")
    65  		return
    66  	}
    67  	delete(cs.contracts, c.header.ID())
    68  	delete(cs.pubKeys, c.header.HostPublicKey().String())
    69  	cs.mu.Unlock()
    70  	c.revisionMu.Unlock()
    71  	// delete contract file
    72  	path := filepath.Join(cs.dir, c.header.ID().String()+contractExtension)
    73  	err := errors.Compose(c.headerFile.Close(), os.Remove(path))
    74  	if err != nil {
    75  		build.Critical("Failed to delete SafeContract from disk:", err)
    76  	}
    77  }
    78  
    79  // IDs returns the fcid of each contract with in the set. The contracts are not
    80  // locked.
    81  func (cs *ContractSet) IDs() []types.FileContractID {
    82  	cs.mu.Lock()
    83  	defer cs.mu.Unlock()
    84  	pks := make([]types.FileContractID, 0, len(cs.contracts))
    85  	for fcid := range cs.contracts {
    86  		pks = append(pks, fcid)
    87  	}
    88  	return pks
    89  }
    90  
    91  // InsertContract inserts an existing contract into the set.
    92  func (cs *ContractSet) InsertContract(rc modules.RecoverableContract, revTxn types.Transaction, roots []crypto.Hash, sk crypto.SecretKey) (modules.RenterContract, error) {
    93  	return cs.managedInsertContract(contractHeader{
    94  		Transaction:      revTxn,
    95  		SecretKey:        sk,
    96  		StartHeight:      rc.StartHeight,
    97  		DownloadSpending: types.NewCurrency64(1), // TODO set this
    98  		StorageSpending:  types.NewCurrency64(1), // TODO set this
    99  		UploadSpending:   types.NewCurrency64(1), // TODO set this
   100  		TotalCost:        types.NewCurrency64(1), // TODO set this
   101  		ContractFee:      types.NewCurrency64(1), // TODO set this
   102  		TxnFee:           rc.TxnFee,
   103  		SiafundFee:       types.Tax(rc.StartHeight, rc.Payout),
   104  	}, roots)
   105  }
   106  
   107  // Len returns the number of contracts in the set.
   108  func (cs *ContractSet) Len() int {
   109  	cs.mu.Lock()
   110  	defer cs.mu.Unlock()
   111  	return len(cs.contracts)
   112  }
   113  
   114  // Return returns a locked contract to the set and unlocks it. The contract
   115  // must have been previously acquired by Acquire. If the contract is not
   116  // present in the set, Return panics.
   117  func (cs *ContractSet) Return(c *SafeContract) {
   118  	cs.mu.Lock()
   119  	_, ok := cs.contracts[c.header.ID()]
   120  	if !ok {
   121  		cs.mu.Unlock()
   122  		build.Critical("no contract with that key")
   123  	}
   124  	cs.mu.Unlock()
   125  	c.revisionMu.Unlock()
   126  }
   127  
   128  // RateLimits sets the bandwidth limits for connections created by the
   129  // contractSet.
   130  func (cs *ContractSet) RateLimits() (readBPS int64, writeBPS int64, packetSize uint64) {
   131  	return cs.rl.Limits()
   132  }
   133  
   134  // SetRateLimits sets the bandwidth limits for connections created by the
   135  // contractSet.
   136  func (cs *ContractSet) SetRateLimits(readBPS int64, writeBPS int64, packetSize uint64) {
   137  	cs.rl.SetLimits(readBPS, writeBPS, packetSize)
   138  }
   139  
   140  // View returns a copy of the contract with the specified host key. The
   141  // contracts is not locked. Certain fields, including the MerkleRoots, are set
   142  // to nil for safety reasons. If the contract is not present in the set, View
   143  // returns false and a zero-valued RenterContract.
   144  func (cs *ContractSet) View(id types.FileContractID) (modules.RenterContract, bool) {
   145  	cs.mu.Lock()
   146  	defer cs.mu.Unlock()
   147  	safeContract, ok := cs.contracts[id]
   148  	if !ok {
   149  		return modules.RenterContract{}, false
   150  	}
   151  	return safeContract.Metadata(), true
   152  }
   153  
   154  // ViewAll returns the metadata of each contract in the set. The contracts are
   155  // not locked.
   156  func (cs *ContractSet) ViewAll() []modules.RenterContract {
   157  	cs.mu.Lock()
   158  	defer cs.mu.Unlock()
   159  	contracts := make([]modules.RenterContract, 0, len(cs.contracts))
   160  	for _, safeContract := range cs.contracts {
   161  		contracts = append(contracts, safeContract.Metadata())
   162  	}
   163  	return contracts
   164  }
   165  
   166  // Close closes all contracts in a contract set, this means rendering it unusable for I/O
   167  func (cs *ContractSet) Close() error {
   168  	for _, c := range cs.contracts {
   169  		c.headerFile.Close()
   170  	}
   171  	_, err := cs.wal.CloseIncomplete()
   172  	return err
   173  }
   174  
   175  // NewContractSet returns a ContractSet storing its contracts in the specified
   176  // dir.
   177  func NewContractSet(dir string, deps modules.Dependencies) (*ContractSet, error) {
   178  	if err := os.MkdirAll(dir, 0700); err != nil {
   179  		return nil, err
   180  	}
   181  	d, err := os.Open(dir)
   182  	if err != nil {
   183  		return nil, err
   184  	} else if stat, err := d.Stat(); err != nil {
   185  		return nil, err
   186  	} else if !stat.IsDir() {
   187  		return nil, errors.New("not a directory")
   188  	}
   189  	defer d.Close()
   190  
   191  	// Load the WAL. Any recovered updates will be applied after loading
   192  	// contracts.
   193  	// COMPATv1.3.1RC2 Rename old wals to have the 'wal' extension if new file
   194  	// doesn't exist.
   195  	if err := v131RC2RenameWAL(dir); err != nil {
   196  		return nil, err
   197  	}
   198  	walTxns, wal, err := writeaheadlog.New(filepath.Join(dir, "contractset.wal"))
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	cs := &ContractSet{
   204  		contracts: make(map[types.FileContractID]*SafeContract),
   205  		pubKeys:   make(map[string]types.FileContractID),
   206  
   207  		deps: deps,
   208  		dir:  dir,
   209  		wal:  wal,
   210  	}
   211  	// Set the initial rate limit to 'unlimited' bandwidth with 4kib packets.
   212  	cs.rl = ratelimit.NewRateLimit(0, 0, 0)
   213  
   214  	// Load the contract files.
   215  	dirNames, err := d.Readdirnames(-1)
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	for _, filename := range dirNames {
   221  		if filepath.Ext(filename) != contractExtension {
   222  			continue
   223  		}
   224  		path := filepath.Join(dir, filename)
   225  		if err := cs.loadSafeContract(path, walTxns); err != nil {
   226  			extErr := fmt.Errorf("failed to load safecontract %v", path)
   227  			return nil, errors.Compose(extErr, err)
   228  		}
   229  	}
   230  
   231  	return cs, nil
   232  }
   233  
   234  // v131RC2RenameWAL renames an existing old wal file from contractset.log to
   235  // contractset.wal
   236  func v131RC2RenameWAL(dir string) error {
   237  	oldPath := filepath.Join(dir, "contractset.log")
   238  	newPath := filepath.Join(dir, "contractset.wal")
   239  	_, errOld := os.Stat(oldPath)
   240  	_, errNew := os.Stat(newPath)
   241  	if !os.IsNotExist(errOld) && os.IsNotExist(errNew) {
   242  		return build.ExtendErr("failed to rename contractset.log to contractset.wal",
   243  			os.Rename(oldPath, newPath))
   244  	}
   245  	return nil
   246  }