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

     1  package proto
     2  
     3  import (
     4  	"errors"
     5  	"net"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/NebulousLabs/Sia/build"
    10  	"github.com/NebulousLabs/Sia/crypto"
    11  	"github.com/NebulousLabs/Sia/encoding"
    12  	"github.com/NebulousLabs/Sia/modules"
    13  	"github.com/NebulousLabs/Sia/types"
    14  	"github.com/NebulousLabs/ratelimit"
    15  )
    16  
    17  // cachedMerkleRoot calculates the root of a set of existing Merkle roots.
    18  func cachedMerkleRoot(roots []crypto.Hash) crypto.Hash {
    19  	tree := crypto.NewCachedTree(sectorHeight) // NOTE: height is not strictly necessary here
    20  	for _, h := range roots {
    21  		tree.Push(h)
    22  	}
    23  	return tree.Root()
    24  }
    25  
    26  // A Editor modifies a Contract by calling the revise RPC on a host. It
    27  // Editors are NOT thread-safe; calls to Upload must happen in serial.
    28  type Editor struct {
    29  	contractID  types.FileContractID
    30  	contractSet *ContractSet
    31  	conn        net.Conn
    32  	closeChan   chan struct{}
    33  	once        sync.Once
    34  	host        modules.HostDBEntry
    35  	hdb         hostDB
    36  
    37  	height types.BlockHeight
    38  }
    39  
    40  // shutdown terminates the revision loop and signals the goroutine spawned in
    41  // NewEditor to return.
    42  func (he *Editor) shutdown() {
    43  	extendDeadline(he.conn, modules.NegotiateSettingsTime)
    44  	// don't care about these errors
    45  	_, _ = verifySettings(he.conn, he.host)
    46  	_ = modules.WriteNegotiationStop(he.conn)
    47  	close(he.closeChan)
    48  }
    49  
    50  // Close cleanly terminates the revision loop with the host and closes the
    51  // connection.
    52  func (he *Editor) Close() error {
    53  	// using once ensures that Close is idempotent
    54  	he.once.Do(he.shutdown)
    55  	return he.conn.Close()
    56  }
    57  
    58  // Upload negotiates a revision that adds a sector to a file contract.
    59  func (he *Editor) Upload(data []byte) (_ modules.RenterContract, _ crypto.Hash, err error) {
    60  	// Acquire the contract.
    61  	sc, haveContract := he.contractSet.Acquire(he.contractID)
    62  	if !haveContract {
    63  		return modules.RenterContract{}, crypto.Hash{}, errors.New("contract not present in contract set")
    64  	}
    65  	defer he.contractSet.Return(sc)
    66  	contract := sc.header // for convenience
    67  
    68  	// calculate price
    69  	// TODO: height is never updated, so we'll wind up overpaying on long-running uploads
    70  	blockBytes := types.NewCurrency64(modules.SectorSize * uint64(contract.LastRevision().NewWindowEnd-he.height))
    71  	sectorStoragePrice := he.host.StoragePrice.Mul(blockBytes)
    72  	sectorBandwidthPrice := he.host.UploadBandwidthPrice.Mul64(modules.SectorSize)
    73  	sectorCollateral := he.host.Collateral.Mul(blockBytes)
    74  
    75  	// to mitigate small errors (e.g. differing block heights), fudge the
    76  	// price and collateral by 0.2%. This is only applied to hosts above
    77  	// v1.0.1; older hosts use stricter math.
    78  	if build.VersionCmp(he.host.Version, "1.0.1") > 0 {
    79  		sectorStoragePrice = sectorStoragePrice.MulFloat(1 + hostPriceLeeway)
    80  		sectorBandwidthPrice = sectorBandwidthPrice.MulFloat(1 + hostPriceLeeway)
    81  		sectorCollateral = sectorCollateral.MulFloat(1 - hostPriceLeeway)
    82  	}
    83  
    84  	sectorPrice := sectorStoragePrice.Add(sectorBandwidthPrice)
    85  	if contract.RenterFunds().Cmp(sectorPrice) < 0 {
    86  		return modules.RenterContract{}, crypto.Hash{}, errors.New("contract has insufficient funds to support upload")
    87  	}
    88  	if contract.LastRevision().NewMissedProofOutputs[1].Value.Cmp(sectorCollateral) < 0 {
    89  		return modules.RenterContract{}, crypto.Hash{}, errors.New("contract has insufficient collateral to support upload")
    90  	}
    91  
    92  	// calculate the new Merkle root
    93  	sectorRoot := crypto.MerkleRoot(data)
    94  	newRoots := append(sc.merkleRoots, sectorRoot)
    95  	merkleRoot := cachedMerkleRoot(newRoots)
    96  
    97  	// create the action and revision
    98  	actions := []modules.RevisionAction{{
    99  		Type:        modules.ActionInsert,
   100  		SectorIndex: uint64(len(sc.merkleRoots)),
   101  		Data:        data,
   102  	}}
   103  	rev := newUploadRevision(contract.LastRevision(), merkleRoot, sectorPrice, sectorCollateral)
   104  
   105  	// run the revision iteration
   106  	defer func() {
   107  		// Increase Successful/Failed interactions accordingly
   108  		if err != nil {
   109  			he.hdb.IncrementFailedInteractions(he.host.PublicKey)
   110  		} else {
   111  			he.hdb.IncrementSuccessfulInteractions(he.host.PublicKey)
   112  		}
   113  
   114  		// reset deadline
   115  		extendDeadline(he.conn, time.Hour)
   116  	}()
   117  
   118  	// initiate revision
   119  	extendDeadline(he.conn, modules.NegotiateSettingsTime)
   120  	if err := startRevision(he.conn, he.host); err != nil {
   121  		return modules.RenterContract{}, crypto.Hash{}, err
   122  	}
   123  
   124  	// record the change we are about to make to the contract. If we lose power
   125  	// mid-revision, this allows us to restore either the pre-revision or
   126  	// post-revision contract.
   127  	walTxn, err := sc.recordUploadIntent(rev, sectorRoot, sectorStoragePrice, sectorBandwidthPrice)
   128  	if err != nil {
   129  		return modules.RenterContract{}, crypto.Hash{}, err
   130  	}
   131  
   132  	// send actions
   133  	extendDeadline(he.conn, modules.NegotiateFileContractRevisionTime)
   134  	if err := encoding.WriteObject(he.conn, actions); err != nil {
   135  		return modules.RenterContract{}, crypto.Hash{}, err
   136  	}
   137  
   138  	// send revision to host and exchange signatures
   139  	extendDeadline(he.conn, 2*time.Minute)
   140  	signedTxn, err := negotiateRevision(he.conn, rev, contract.SecretKey)
   141  	if err == modules.ErrStopResponse {
   142  		// if host gracefully closed, close our connection as well; this will
   143  		// cause the next operation to fail
   144  		he.conn.Close()
   145  	} else if err != nil {
   146  		return modules.RenterContract{}, crypto.Hash{}, err
   147  	}
   148  
   149  	// update contract
   150  	err = sc.commitUpload(walTxn, signedTxn, sectorRoot, sectorStoragePrice, sectorBandwidthPrice)
   151  	if err != nil {
   152  		return modules.RenterContract{}, crypto.Hash{}, err
   153  	}
   154  
   155  	return sc.Metadata(), sectorRoot, nil
   156  }
   157  
   158  // NewEditor initiates the contract revision process with a host, and returns
   159  // an Editor.
   160  func (cs *ContractSet) NewEditor(host modules.HostDBEntry, id types.FileContractID, currentHeight types.BlockHeight, hdb hostDB, cancel <-chan struct{}) (_ *Editor, err error) {
   161  	sc, ok := cs.Acquire(id)
   162  	if !ok {
   163  		return nil, errors.New("invalid contract")
   164  	}
   165  	defer cs.Return(sc)
   166  	contract := sc.header
   167  
   168  	// Increase Successful/Failed interactions accordingly
   169  	defer func() {
   170  		// a revision mismatch is not necessarily the host's fault
   171  		if err != nil && !IsRevisionMismatch(err) {
   172  			hdb.IncrementFailedInteractions(contract.HostPublicKey())
   173  		} else if err == nil {
   174  			hdb.IncrementSuccessfulInteractions(contract.HostPublicKey())
   175  		}
   176  	}()
   177  
   178  	conn, closeChan, err := initiateRevisionLoop(host, contract, modules.RPCReviseContract, cancel)
   179  	if IsRevisionMismatch(err) && len(sc.unappliedTxns) > 0 {
   180  		// we have desynced from the host. If we have unapplied updates from the
   181  		// WAL, try applying them.
   182  		conn, closeChan, err = initiateRevisionLoop(host, sc.unappliedHeader(), modules.RPCReviseContract, cancel)
   183  		if err != nil {
   184  			return nil, err
   185  		}
   186  		// applying the updates was successful; commit them to disk
   187  		if err := sc.commitTxns(); err != nil {
   188  			return nil, err
   189  		}
   190  	} else if err != nil {
   191  		return nil, err
   192  	}
   193  	// if we succeeded, we can safely discard the unappliedTxns
   194  	for _, txn := range sc.unappliedTxns {
   195  		txn.SignalUpdatesApplied()
   196  	}
   197  	sc.unappliedTxns = nil
   198  
   199  	// the host is now ready to accept revisions
   200  	return &Editor{
   201  		host:        host,
   202  		hdb:         hdb,
   203  		height:      currentHeight,
   204  		contractID:  id,
   205  		contractSet: cs,
   206  		conn:        conn,
   207  		closeChan:   closeChan,
   208  	}, nil
   209  }
   210  
   211  // initiateRevisionLoop initiates either the editor or downloader loop with
   212  // host, depending on which rpc was passed.
   213  func initiateRevisionLoop(host modules.HostDBEntry, contract contractHeader, rpc types.Specifier, cancel <-chan struct{}) (net.Conn, chan struct{}, error) {
   214  	c, err := (&net.Dialer{
   215  		Cancel:  cancel,
   216  		Timeout: 45 * time.Second, // TODO: Constant
   217  	}).Dial("tcp", string(host.NetAddress))
   218  	if err != nil {
   219  		return nil, nil, err
   220  	}
   221  	conn := ratelimit.NewRLConn(c, cancel)
   222  
   223  	closeChan := make(chan struct{})
   224  	go func() {
   225  		select {
   226  		case <-cancel:
   227  			conn.Close()
   228  		case <-closeChan:
   229  		}
   230  	}()
   231  
   232  	// allot 2 minutes for RPC request + revision exchange
   233  	extendDeadline(conn, modules.NegotiateRecentRevisionTime)
   234  	defer extendDeadline(conn, time.Hour)
   235  	if err := encoding.WriteObject(conn, rpc); err != nil {
   236  		conn.Close()
   237  		close(closeChan)
   238  		return nil, closeChan, errors.New("couldn't initiate RPC: " + err.Error())
   239  	}
   240  	if err := verifyRecentRevision(conn, contract, host.Version); err != nil {
   241  		conn.Close() // TODO: close gracefully if host has entered revision loop
   242  		close(closeChan)
   243  		return nil, closeChan, err
   244  	}
   245  	return conn, closeChan, nil
   246  }