github.com/johnathanhowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/renter/proto/editor.go (about)

     1  package proto
     2  
     3  import (
     4  	"errors"
     5  	"net"
     6  	"time"
     7  
     8  	"github.com/NebulousLabs/Sia/crypto"
     9  	"github.com/NebulousLabs/Sia/encoding"
    10  	"github.com/NebulousLabs/Sia/modules"
    11  	"github.com/NebulousLabs/Sia/types"
    12  )
    13  
    14  var (
    15  	// sectorHeight is the height of a Merkle tree that covers a single
    16  	// sector. It is log2(modules.SectorSize / crypto.SegmentSize)
    17  	sectorHeight = func() uint64 {
    18  		height := uint64(0)
    19  		for 1<<height < (modules.SectorSize / crypto.SegmentSize) {
    20  			height++
    21  		}
    22  		return height
    23  	}()
    24  )
    25  
    26  // cachedMerkleRoot calculates the root of a set of existing Merkle roots.
    27  func cachedMerkleRoot(roots []crypto.Hash) crypto.Hash {
    28  	tree := crypto.NewCachedTree(sectorHeight) // NOTE: height is not strictly necessary here
    29  	for _, h := range roots {
    30  		tree.Push(h)
    31  	}
    32  	return tree.Root()
    33  }
    34  
    35  // A Editor modifies a Contract by calling the revise RPC on a host. It
    36  // Editors are NOT thread-safe; calls to Upload must happen in serial.
    37  type Editor struct {
    38  	conn net.Conn
    39  	host modules.HostDBEntry
    40  
    41  	height   types.BlockHeight
    42  	contract modules.RenterContract // updated after each revision
    43  
    44  	// metrics
    45  	StorageSpending types.Currency
    46  	UploadSpending  types.Currency
    47  }
    48  
    49  // Close cleanly terminates the revision loop with the host and closes the
    50  // connection.
    51  func (he *Editor) Close() error {
    52  	// don't care about these errors
    53  	_, _ = verifySettings(he.conn, he.host)
    54  	_ = modules.WriteNegotiationStop(he.conn)
    55  	return he.conn.Close()
    56  }
    57  
    58  // runRevisionIteration submits actions and their accompanying revision to the
    59  // host for approval. If negotiation is successful, it updates the underlying
    60  // Contract.
    61  func (he *Editor) runRevisionIteration(actions []modules.RevisionAction, rev types.FileContractRevision, newRoots []crypto.Hash) error {
    62  	// initiate revision
    63  	if err := startRevision(he.conn, he.host); err != nil {
    64  		return err
    65  	}
    66  
    67  	// send actions
    68  	if err := encoding.WriteObject(he.conn, actions); err != nil {
    69  		return err
    70  	}
    71  
    72  	// send revision to host and exchange signatures
    73  	signedTxn, err := negotiateRevision(he.conn, rev, he.contract.SecretKey)
    74  	if err != nil {
    75  		return err
    76  	}
    77  
    78  	// update host contract
    79  	he.contract.LastRevision = rev
    80  	he.contract.LastRevisionTxn = signedTxn
    81  	he.contract.MerkleRoots = newRoots
    82  
    83  	return nil
    84  }
    85  
    86  // Upload negotiates a revision that adds a sector to a file contract.
    87  func (he *Editor) Upload(data []byte) (modules.RenterContract, crypto.Hash, error) {
    88  	// allot 10 minutes for this exchange; sufficient to transfer 4 MB over 50 kbps
    89  	extendDeadline(he.conn, modules.NegotiateFileContractRevisionTime)
    90  	defer extendDeadline(he.conn, time.Hour) // reset deadline
    91  
    92  	// calculate price
    93  	// TODO: height is never updated, so we'll wind up overpaying on long-running uploads
    94  	blockBytes := types.NewCurrency64(modules.SectorSize * uint64(he.contract.FileContract.WindowEnd-he.height))
    95  	sectorStoragePrice := he.host.StoragePrice.Mul(blockBytes)
    96  	sectorBandwidthPrice := he.host.UploadBandwidthPrice.Mul64(modules.SectorSize)
    97  	sectorPrice := sectorStoragePrice.Add(sectorBandwidthPrice)
    98  	if he.contract.LastRevision.NewValidProofOutputs[0].Value.Cmp(sectorPrice) < 0 {
    99  		return modules.RenterContract{}, crypto.Hash{}, errors.New("contract has insufficient funds to support upload")
   100  	}
   101  	sectorCollateral := he.host.Collateral.Mul(blockBytes)
   102  	if he.contract.LastRevision.NewMissedProofOutputs[1].Value.Cmp(sectorCollateral) < 0 {
   103  		return modules.RenterContract{}, crypto.Hash{}, errors.New("contract has insufficient collateral to support upload")
   104  	}
   105  
   106  	// calculate the new Merkle root
   107  	sectorRoot := crypto.MerkleRoot(data)
   108  	newRoots := append(he.contract.MerkleRoots, sectorRoot)
   109  	merkleRoot := cachedMerkleRoot(newRoots)
   110  
   111  	// create the action and revision
   112  	actions := []modules.RevisionAction{{
   113  		Type:        modules.ActionInsert,
   114  		SectorIndex: uint64(len(he.contract.MerkleRoots)),
   115  		Data:        data,
   116  	}}
   117  	rev := newUploadRevision(he.contract.LastRevision, merkleRoot, sectorPrice, sectorCollateral)
   118  
   119  	// run the revision iteration
   120  	if err := he.runRevisionIteration(actions, rev, newRoots); err != nil {
   121  		return modules.RenterContract{}, crypto.Hash{}, err
   122  	}
   123  
   124  	// update metrics
   125  	he.StorageSpending = he.StorageSpending.Add(sectorStoragePrice)
   126  	he.UploadSpending = he.UploadSpending.Add(sectorBandwidthPrice)
   127  
   128  	return he.contract, sectorRoot, nil
   129  }
   130  
   131  // Delete negotiates a revision that removes a sector from a file contract.
   132  func (he *Editor) Delete(root crypto.Hash) (modules.RenterContract, error) {
   133  	// allot 2 minutes for this exchange
   134  	extendDeadline(he.conn, 120*time.Second)
   135  	defer extendDeadline(he.conn, time.Hour) // reset deadline
   136  
   137  	// calculate the new Merkle root
   138  	newRoots := make([]crypto.Hash, 0, len(he.contract.MerkleRoots))
   139  	index := -1
   140  	for i, h := range he.contract.MerkleRoots {
   141  		if h == root {
   142  			index = i
   143  		} else {
   144  			newRoots = append(newRoots, h)
   145  		}
   146  	}
   147  	if index == -1 {
   148  		return modules.RenterContract{}, errors.New("no record of that sector root")
   149  	}
   150  	merkleRoot := cachedMerkleRoot(newRoots)
   151  
   152  	// create the action and accompanying revision
   153  	actions := []modules.RevisionAction{{
   154  		Type:        modules.ActionDelete,
   155  		SectorIndex: uint64(index),
   156  	}}
   157  	rev := newDeleteRevision(he.contract.LastRevision, merkleRoot)
   158  
   159  	// run the revision iteration
   160  	if err := he.runRevisionIteration(actions, rev, newRoots); err != nil {
   161  		return modules.RenterContract{}, err
   162  	}
   163  	return he.contract, nil
   164  }
   165  
   166  // Modify negotiates a revision that edits a sector in a file contract.
   167  func (he *Editor) Modify(oldRoot, newRoot crypto.Hash, offset uint64, newData []byte) (modules.RenterContract, error) {
   168  	// allot 10 minutes for this exchange; sufficient to transfer 4 MB over 50 kbps
   169  	extendDeadline(he.conn, modules.NegotiateFileContractRevisionTime)
   170  	defer extendDeadline(he.conn, time.Hour) // reset deadline
   171  
   172  	// calculate price
   173  	sectorBandwidthPrice := he.host.UploadBandwidthPrice.Mul64(uint64(len(newData)))
   174  	if he.contract.LastRevision.NewValidProofOutputs[0].Value.Cmp(sectorBandwidthPrice) < 0 {
   175  		return modules.RenterContract{}, errors.New("contract has insufficient funds to support modification")
   176  	}
   177  
   178  	// calculate the new Merkle root
   179  	newRoots := make([]crypto.Hash, len(he.contract.MerkleRoots))
   180  	index := -1
   181  	for i, h := range he.contract.MerkleRoots {
   182  		if h == oldRoot {
   183  			index = i
   184  			newRoots[i] = newRoot
   185  		} else {
   186  			newRoots[i] = h
   187  		}
   188  	}
   189  	if index == -1 {
   190  		return modules.RenterContract{}, errors.New("no record of that sector root")
   191  	}
   192  	merkleRoot := cachedMerkleRoot(newRoots)
   193  
   194  	// create the action and revision
   195  	actions := []modules.RevisionAction{{
   196  		Type:        modules.ActionModify,
   197  		SectorIndex: uint64(index),
   198  		Offset:      offset,
   199  		Data:        newData,
   200  	}}
   201  	rev := newModifyRevision(he.contract.LastRevision, merkleRoot, sectorBandwidthPrice)
   202  
   203  	// run the revision iteration
   204  	if err := he.runRevisionIteration(actions, rev, newRoots); err != nil {
   205  		return modules.RenterContract{}, err
   206  	}
   207  
   208  	// update metrics
   209  	he.UploadSpending = he.UploadSpending.Add(sectorBandwidthPrice)
   210  
   211  	return he.contract, nil
   212  }
   213  
   214  // NewEditor initiates the contract revision process with a host, and returns
   215  // an Editor.
   216  func NewEditor(host modules.HostDBEntry, contract modules.RenterContract, currentHeight types.BlockHeight) (*Editor, error) {
   217  	// check that contract has enough value to support an upload
   218  	if len(contract.LastRevision.NewValidProofOutputs) != 2 {
   219  		return nil, errors.New("invalid contract")
   220  	}
   221  	if !host.StoragePrice.IsZero() {
   222  		bytes, errOverflow := contract.LastRevision.NewValidProofOutputs[0].Value.Div(host.StoragePrice).Uint64()
   223  		if errOverflow == nil && bytes < modules.SectorSize {
   224  			return nil, errors.New("contract has insufficient capacity")
   225  		}
   226  	}
   227  
   228  	// initiate revision loop
   229  	conn, err := net.DialTimeout("tcp", string(contract.NetAddress), 15*time.Second)
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  	// allot 2 minutes for RPC request + revision exchange
   234  	extendDeadline(conn, modules.NegotiateRecentRevisionTime)
   235  	defer extendDeadline(conn, time.Hour)
   236  	if err := encoding.WriteObject(conn, modules.RPCReviseContract); err != nil {
   237  		return nil, errors.New("couldn't initiate RPC: " + err.Error())
   238  	}
   239  	if err := verifyRecentRevision(conn, contract); err != nil {
   240  		return nil, errors.New("revision exchange failed: " + err.Error())
   241  	}
   242  
   243  	// the host is now ready to accept revisions
   244  	return &Editor{
   245  		host:     host,
   246  		height:   currentHeight,
   247  		contract: contract,
   248  		conn:     conn,
   249  	}, nil
   250  }