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

     1  package contractor
     2  
     3  import (
     4  	"errors"
     5  	"sync"
     6  
     7  	"SiaPrime/crypto"
     8  	"SiaPrime/modules"
     9  	"SiaPrime/modules/renter/proto"
    10  	"SiaPrime/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  	he.contractor.mu.Unlock()
    68  }
    69  
    70  // Address returns the NetAddress of the host.
    71  func (he *hostEditor) Address() modules.NetAddress { return he.netAddress }
    72  
    73  // ContractID returns the id of the contract being revised.
    74  func (he *hostEditor) ContractID() types.FileContractID { return he.id }
    75  
    76  // EndHeight returns the height at which the host is no longer obligated to
    77  // store the file.
    78  func (he *hostEditor) EndHeight() types.BlockHeight { return he.endHeight }
    79  
    80  // Close cleanly terminates the revision loop with the host and closes the
    81  // connection.
    82  func (he *hostEditor) Close() error {
    83  	he.mu.Lock()
    84  	defer he.mu.Unlock()
    85  	he.clients--
    86  	// Close is a no-op if invalidate has been called, or if there are other
    87  	// clients still using the hostEditor.
    88  	if he.invalid || he.clients > 0 {
    89  		return nil
    90  	}
    91  	he.invalid = true
    92  	he.contractor.mu.Lock()
    93  	delete(he.contractor.editors, he.id)
    94  	he.contractor.mu.Unlock()
    95  	return he.editor.Close()
    96  }
    97  
    98  // Upload negotiates a revision that adds a sector to a file contract.
    99  func (he *hostEditor) Upload(data []byte) (_ crypto.Hash, err error) {
   100  	he.mu.Lock()
   101  	defer he.mu.Unlock()
   102  	if he.invalid {
   103  		return crypto.Hash{}, errInvalidEditor
   104  	}
   105  
   106  	// Perform the upload.
   107  	_, sectorRoot, err := he.editor.Upload(data)
   108  	if err != nil {
   109  		return crypto.Hash{}, err
   110  	}
   111  	return sectorRoot, nil
   112  }
   113  
   114  // Editor returns a Editor object that can be used to upload, modify, and
   115  // delete sectors on a host.
   116  func (c *Contractor) Editor(pk types.SiaPublicKey, cancel <-chan struct{}) (_ Editor, err error) {
   117  	c.mu.RLock()
   118  	id, gotID := c.pubKeysToContractID[string(pk.Key)]
   119  	cachedEditor, haveEditor := c.editors[id]
   120  	height := c.blockHeight
   121  	renewing := c.renewing[id]
   122  	c.mu.RUnlock()
   123  	if !gotID {
   124  		return nil, errors.New("failed to get filecontract id from key")
   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.staticContracts.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  	// Create the editor.
   156  	e, err := c.staticContracts.NewEditor(host, contract.ID, height, c.hdb, cancel)
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	// cache editor
   162  	he := &hostEditor{
   163  		clients:    1,
   164  		contractor: c,
   165  		editor:     e,
   166  		endHeight:  contract.EndHeight,
   167  		id:         id,
   168  		netAddress: host.NetAddress,
   169  	}
   170  	c.mu.Lock()
   171  	c.editors[contract.ID] = he
   172  	c.mu.Unlock()
   173  
   174  	return he, nil
   175  }