gitlab.com/jokerrs1/Sia@v1.3.2/modules/renter/contractor/editor.go (about)

     1  package contractor
     2  
     3  import (
     4  	"errors"
     5  	"sync"
     6  
     7  	"github.com/NebulousLabs/Sia/crypto"
     8  	"github.com/NebulousLabs/Sia/modules"
     9  	"github.com/NebulousLabs/Sia/modules/renter/proto"
    10  	"github.com/NebulousLabs/Sia/types"
    11  )
    12  
    13  var errInvalidEditor = errors.New("editor has been invalidated because its contract is being renewed")
    14  
    15  // the contractor will cap host's MaxCollateral setting to this value
    16  var maxUploadCollateral = types.SiacoinPrecision.Mul64(1e3).Div(modules.BlockBytesPerMonthTerabyte) // 1k SC / TB / Month
    17  
    18  // An Editor modifies a Contract by communicating with a host. It uses the
    19  // contract revision protocol to send modification requests to the host.
    20  // Editors are the means by which the renter uploads data to hosts.
    21  type Editor interface {
    22  	// Upload revises the underlying contract to store the new data. It
    23  	// returns the Merkle root of the data.
    24  	Upload(data []byte) (root crypto.Hash, err error)
    25  
    26  	// Address returns the address of the host.
    27  	Address() modules.NetAddress
    28  
    29  	// ContractID returns the FileContractID of the contract.
    30  	ContractID() types.FileContractID
    31  
    32  	// EndHeight returns the height at which the contract ends.
    33  	EndHeight() types.BlockHeight
    34  
    35  	// Close terminates the connection to the host.
    36  	Close() error
    37  }
    38  
    39  // A hostEditor modifies a Contract by calling the revise RPC on a host. It
    40  // implements the Editor interface. hostEditors are safe for use by
    41  // multiple goroutines.
    42  type hostEditor struct {
    43  	clients    int // safe to Close when 0
    44  	contractor *Contractor
    45  	editor     *proto.Editor
    46  	endHeight  types.BlockHeight
    47  	id         types.FileContractID
    48  	invalid    bool // true if invalidate has been called
    49  	netAddress modules.NetAddress
    50  
    51  	mu sync.Mutex
    52  }
    53  
    54  // invalidate sets the invalid flag and closes the underlying proto.Editor.
    55  // Once invalidate returns, the hostEditor is guaranteed to not further revise
    56  // its contract. This is used during contract renewal to prevent an Editor
    57  // from revising a contract mid-renewal.
    58  func (he *hostEditor) invalidate() {
    59  	he.mu.Lock()
    60  	defer he.mu.Unlock()
    61  	if !he.invalid {
    62  		he.editor.Close()
    63  		he.invalid = true
    64  	}
    65  	he.contractor.mu.Lock()
    66  	delete(he.contractor.editors, he.id)
    67  	delete(he.contractor.revising, he.id)
    68  	he.contractor.mu.Unlock()
    69  }
    70  
    71  // Address returns the NetAddress of the host.
    72  func (he *hostEditor) Address() modules.NetAddress { return he.netAddress }
    73  
    74  // ContractID returns the ID of the contract being revised.
    75  func (he *hostEditor) ContractID() types.FileContractID { return he.id }
    76  
    77  // EndHeight returns the height at which the host is no longer obligated to
    78  // store the file.
    79  func (he *hostEditor) EndHeight() types.BlockHeight { return he.endHeight }
    80  
    81  // Close cleanly terminates the revision loop with the host and closes the
    82  // connection.
    83  func (he *hostEditor) Close() error {
    84  	he.mu.Lock()
    85  	defer he.mu.Unlock()
    86  	he.clients--
    87  	// Close is a no-op if invalidate has been called, or if there are other
    88  	// clients still using the hostEditor.
    89  	if he.invalid || he.clients > 0 {
    90  		return nil
    91  	}
    92  	he.invalid = true
    93  	he.contractor.mu.Lock()
    94  	delete(he.contractor.editors, he.id)
    95  	delete(he.contractor.revising, he.id)
    96  	he.contractor.mu.Unlock()
    97  	return he.editor.Close()
    98  }
    99  
   100  // Upload negotiates a revision that adds a sector to a file contract.
   101  func (he *hostEditor) Upload(data []byte) (_ crypto.Hash, err error) {
   102  	he.mu.Lock()
   103  	defer he.mu.Unlock()
   104  	if he.invalid {
   105  		return crypto.Hash{}, errInvalidEditor
   106  	}
   107  
   108  	// Perform the upload.
   109  	_, sectorRoot, err := he.editor.Upload(data)
   110  	if err != nil {
   111  		return crypto.Hash{}, err
   112  	}
   113  	return sectorRoot, nil
   114  }
   115  
   116  // Editor returns a Editor object that can be used to upload, modify, and
   117  // delete sectors on a host.
   118  func (c *Contractor) Editor(id types.FileContractID, cancel <-chan struct{}) (_ Editor, err error) {
   119  	id = c.ResolveID(id)
   120  	c.mu.RLock()
   121  	cachedEditor, haveEditor := c.editors[id]
   122  	height := c.blockHeight
   123  	renewing := c.renewing[id]
   124  	c.mu.RUnlock()
   125  
   126  	if renewing {
   127  		// Cannot use the editor if the contract is being renewed.
   128  		return nil, errors.New("currently renewing that contract")
   129  	} else if haveEditor {
   130  		// This editor already exists. Mark that there are now two routines
   131  		// using the editor, and then return the editor that already exists.
   132  		cachedEditor.mu.Lock()
   133  		cachedEditor.clients++
   134  		cachedEditor.mu.Unlock()
   135  		return cachedEditor, nil
   136  	}
   137  
   138  	// Check that the contract and host are both available, and run some brief
   139  	// sanity checks to see that the host is not swindling us.
   140  	contract, haveContract := c.contracts.View(id)
   141  	if !haveContract {
   142  		return nil, errors.New("no record of that contract")
   143  	}
   144  	host, haveHost := c.hdb.Host(contract.HostPublicKey)
   145  	if height > contract.EndHeight {
   146  		return nil, errors.New("contract has already ended")
   147  	} else if !haveHost {
   148  		return nil, errors.New("no record of that host")
   149  	} else if host.StoragePrice.Cmp(maxStoragePrice) > 0 {
   150  		return nil, errTooExpensive
   151  	} else if host.UploadBandwidthPrice.Cmp(maxUploadPrice) > 0 {
   152  		return nil, errTooExpensive
   153  	}
   154  
   155  	// Acquire the revising lock.
   156  	c.mu.Lock()
   157  	alreadyRevising := c.revising[contract.ID]
   158  	if alreadyRevising {
   159  		c.mu.Unlock()
   160  		return nil, errors.New("already revising that contract")
   161  	}
   162  	c.revising[contract.ID] = true
   163  	c.mu.Unlock()
   164  	// Release the revising lock early in the event of an error.
   165  	defer func() {
   166  		if err != nil {
   167  			c.mu.Lock()
   168  			delete(c.revising, contract.ID)
   169  			c.mu.Unlock()
   170  		}
   171  	}()
   172  
   173  	// Create the editor.
   174  	e, err := c.contracts.NewEditor(host, contract.ID, height, c.hdb, cancel)
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  
   179  	// cache editor
   180  	he := &hostEditor{
   181  		clients:    1,
   182  		contractor: c,
   183  		editor:     e,
   184  		endHeight:  contract.EndHeight,
   185  		id:         contract.ID,
   186  		netAddress: host.NetAddress,
   187  	}
   188  	c.mu.Lock()
   189  	c.editors[contract.ID] = he
   190  	c.mu.Unlock()
   191  
   192  	return he, nil
   193  }