github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/renter/proto/contractset.go (about)

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