gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/proto/session.go (about)

     1  package proto
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/cipher"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"math/bits"
    10  	"net"
    11  	"sort"
    12  	"sync"
    13  	"time"
    14  
    15  	"gitlab.com/NebulousLabs/errors"
    16  	"gitlab.com/NebulousLabs/log"
    17  	"gitlab.com/NebulousLabs/ratelimit"
    18  
    19  	"gitlab.com/SkynetLabs/skyd/build"
    20  	"gitlab.com/SkynetLabs/skyd/skymodules"
    21  	"go.sia.tech/siad/crypto"
    22  	"go.sia.tech/siad/modules"
    23  	"go.sia.tech/siad/types"
    24  )
    25  
    26  // sessionDialTimeout determines how long a Session will try to dial a host
    27  // before aborting.
    28  var sessionDialTimeout = build.Select(build.Var{
    29  	Testing:  5 * time.Second,
    30  	Dev:      20 * time.Second,
    31  	Standard: 45 * time.Second,
    32  }).(time.Duration)
    33  
    34  // A Session is an ongoing exchange of RPCs via the renter-host protocol.
    35  //
    36  // TODO: The session type needs access to a logger. Probably the renter logger.
    37  type Session struct {
    38  	aead        cipher.AEAD
    39  	challenge   [16]byte
    40  	closeChan   chan struct{}
    41  	conn        net.Conn
    42  	contractID  types.FileContractID
    43  	contractSet *ContractSet
    44  	deps        modules.Dependencies
    45  	hdb         hostDB
    46  	height      types.BlockHeight
    47  	host        skymodules.HostDBEntry
    48  	once        sync.Once
    49  }
    50  
    51  // writeRequest sends an encrypted RPC request to the host.
    52  func (s *Session) writeRequest(rpcID types.Specifier, req interface{}) error {
    53  	return modules.WriteRPCRequest(s.conn, s.aead, rpcID, req)
    54  }
    55  
    56  // writeResponse writes an encrypted RPC response to the host.
    57  func (s *Session) writeResponse(resp interface{}, err error) error {
    58  	return modules.WriteRPCResponse(s.conn, s.aead, resp, err)
    59  }
    60  
    61  // readResponse reads an encrypted RPC response from the host.
    62  func (s *Session) readResponse(resp interface{}, maxLen uint64) error {
    63  	return modules.ReadRPCResponse(s.conn, s.aead, resp, maxLen)
    64  }
    65  
    66  // call is a helper method that calls writeRequest followed by readResponse.
    67  func (s *Session) call(rpcID types.Specifier, req, resp interface{}, maxLen uint64) error {
    68  	if err := s.writeRequest(rpcID, req); err != nil {
    69  		return err
    70  	}
    71  	return s.readResponse(resp, maxLen)
    72  }
    73  
    74  // Lock calls the Lock RPC, locking the supplied contract and returning its
    75  // most recent revision.
    76  func (s *Session) Lock(id types.FileContractID, secretKey crypto.SecretKey) (types.FileContractRevision, []types.TransactionSignature, error) {
    77  	sig := crypto.SignHash(crypto.HashAll(modules.RPCChallengePrefix, s.challenge), secretKey)
    78  	req := modules.LoopLockRequest{
    79  		ContractID: id,
    80  		Signature:  sig[:],
    81  		Timeout:    defaultContractLockTimeout,
    82  	}
    83  
    84  	timeoutDur := time.Duration(defaultContractLockTimeout) * time.Millisecond
    85  	extendDeadline(s.conn, modules.NegotiateSettingsTime+timeoutDur)
    86  	var resp modules.LoopLockResponse
    87  	if err := s.call(modules.RPCLoopLock, req, &resp, modules.RPCMinLen); err != nil {
    88  		return types.FileContractRevision{}, nil, errors.AddContext(err, "lock request on host session has failed")
    89  	}
    90  	// Unconditionally update the challenge.
    91  	s.challenge = resp.NewChallenge
    92  
    93  	if !resp.Acquired {
    94  		return resp.Revision, resp.Signatures, errors.New("contract is locked by another party")
    95  	}
    96  	// Set the new Session contract.
    97  	s.contractID = id
    98  	// Verify the public keys in the claimed revision.
    99  	expectedUnlockConditions := types.UnlockConditions{
   100  		PublicKeys: []types.SiaPublicKey{
   101  			types.Ed25519PublicKey(secretKey.PublicKey()),
   102  			s.host.PublicKey,
   103  		},
   104  		SignaturesRequired: 2,
   105  	}
   106  	if resp.Revision.UnlockConditions.UnlockHash() != expectedUnlockConditions.UnlockHash() {
   107  		return resp.Revision, resp.Signatures, errors.New("host's claimed revision has wrong unlock conditions")
   108  	}
   109  	// Verify the claimed signatures.
   110  	if err := modules.VerifyFileContractRevisionTransactionSignatures(resp.Revision, resp.Signatures, s.height); err != nil {
   111  		return resp.Revision, resp.Signatures, errors.AddContext(err, "unable to verify signatures on contract revision")
   112  	}
   113  	return resp.Revision, resp.Signatures, nil
   114  }
   115  
   116  // Unlock calls the Unlock RPC, unlocking the currently-locked contract.
   117  func (s *Session) Unlock() error {
   118  	if s.contractID == (types.FileContractID{}) {
   119  		return errors.New("no contract locked")
   120  	}
   121  	extendDeadline(s.conn, modules.NegotiateSettingsTime)
   122  	return s.writeRequest(modules.RPCLoopUnlock, nil)
   123  }
   124  
   125  // HostSettings returns the currently active host settings of the session.
   126  func (s *Session) HostSettings() modules.HostExternalSettings {
   127  	return s.host.HostExternalSettings
   128  }
   129  
   130  // Settings calls the Settings RPC, returning the host's reported settings.
   131  func (s *Session) Settings() (modules.HostExternalSettings, error) {
   132  	extendDeadline(s.conn, modules.NegotiateSettingsTime)
   133  	var resp modules.LoopSettingsResponse
   134  	if err := s.call(modules.RPCLoopSettings, nil, &resp, modules.RPCMinLen); err != nil {
   135  		return modules.HostExternalSettings{}, err
   136  	}
   137  	var hes modules.HostExternalSettings
   138  	if err := json.Unmarshal(resp.Settings, &hes); err != nil {
   139  		return modules.HostExternalSettings{}, err
   140  	}
   141  	s.host.HostExternalSettings = hes
   142  	return s.host.HostExternalSettings, nil
   143  }
   144  
   145  // Append calls the Write RPC with a single Append action, returning the
   146  // updated contract and the Merkle root of the appended sector.
   147  func (s *Session) Append(data []byte) (_ skymodules.RenterContract, _ crypto.Hash, err error) {
   148  	rc, err := s.Write([]modules.LoopWriteAction{{Type: modules.WriteActionAppend, Data: data}})
   149  	return rc, crypto.MerkleRoot(data), err
   150  }
   151  
   152  // Replace calls the Write RPC with a series of actions that replace the sector
   153  // at the specified index with data, returning the updated contract and the
   154  // Merkle root of the new sector.
   155  func (s *Session) Replace(data []byte, sectorIndex uint64, trim bool) (_ skymodules.RenterContract, _ crypto.Hash, err error) {
   156  	sc, haveContract := s.contractSet.Acquire(s.contractID)
   157  	if !haveContract {
   158  		return skymodules.RenterContract{}, crypto.Hash{}, errors.New("contract not present in contract set")
   159  	}
   160  	defer s.contractSet.Return(sc)
   161  	// get current number of sectors
   162  	numSectors := sc.header.LastRevision().NewFileSize / modules.SectorSize
   163  	actions := []modules.LoopWriteAction{
   164  		// append the new sector
   165  		{Type: modules.WriteActionAppend, Data: data},
   166  		// swap the new sector with the old sector
   167  		{Type: modules.WriteActionSwap, A: 0, B: numSectors},
   168  	}
   169  	if trim {
   170  		// delete the old sector
   171  		actions = append(actions, modules.LoopWriteAction{Type: modules.WriteActionTrim, A: 1})
   172  	}
   173  
   174  	rc, err := s.write(sc, actions)
   175  	return rc, crypto.MerkleRoot(data), errors.AddContext(err, "write to host failed")
   176  }
   177  
   178  // Write implements the Write RPC, except for ActionUpdate. A Merkle proof is
   179  // always requested.
   180  func (s *Session) Write(actions []modules.LoopWriteAction) (_ skymodules.RenterContract, err error) {
   181  	sc, haveContract := s.contractSet.Acquire(s.contractID)
   182  	if !haveContract {
   183  		return skymodules.RenterContract{}, errors.New("contract not present in contract set")
   184  	}
   185  	defer s.contractSet.Return(sc)
   186  	return s.write(sc, actions)
   187  }
   188  
   189  func (s *Session) write(sc *SafeContract, actions []modules.LoopWriteAction) (_ skymodules.RenterContract, err error) {
   190  	contract := sc.header // for convenience
   191  
   192  	// calculate price per sector
   193  	blockBytes := types.NewCurrency64(modules.SectorSize * uint64(contract.LastRevision().NewWindowEnd-s.height))
   194  	sectorBandwidthPrice := s.host.UploadBandwidthPrice.Mul64(modules.SectorSize)
   195  	sectorStoragePrice := s.host.StoragePrice.Mul(blockBytes)
   196  	sectorCollateral := s.host.Collateral.Mul(blockBytes)
   197  
   198  	// calculate the new Merkle root set and total cost/collateral
   199  	var bandwidthPrice, storagePrice, collateral types.Currency
   200  	newFileSize := contract.LastRevision().NewFileSize
   201  	rootUpdates := make(map[uint64]rootUpdate)
   202  	for _, action := range actions {
   203  		switch action.Type {
   204  		case modules.WriteActionAppend:
   205  			bandwidthPrice = bandwidthPrice.Add(sectorBandwidthPrice)
   206  			ru, exists := rootUpdates[newFileSize/modules.SectorSize]
   207  			if !exists {
   208  				ru = newRootUpdateAppendRoot(crypto.MerkleRoot(action.Data))
   209  			} else {
   210  				ru.root = crypto.MerkleRoot(action.Data)
   211  				ru.trim = false
   212  			}
   213  			rootUpdates[newFileSize/modules.SectorSize] = ru
   214  			newFileSize += modules.SectorSize
   215  
   216  		case modules.WriteActionTrim:
   217  			newFileSize -= modules.SectorSize * action.A
   218  			ru, exists := rootUpdates[newFileSize/modules.SectorSize]
   219  			if !exists {
   220  				ru = newRootUpdateTrimRoot()
   221  			}
   222  			ru.trim = true
   223  			rootUpdates[newFileSize/modules.SectorSize] = ru
   224  
   225  		case modules.WriteActionSwap:
   226  			ruA, existsA := rootUpdates[action.A]
   227  			if !existsA {
   228  				rootA, err := sc.merkleRoots.merkleRoot(int(action.A))
   229  				if err != nil {
   230  					return skymodules.RenterContract{}, err
   231  				}
   232  				ruA = newRootUpdateUpdateRoot(rootA)
   233  			}
   234  			ruB, existsB := rootUpdates[action.B]
   235  			if !existsB {
   236  				rootB, err := sc.merkleRoots.merkleRoot(int(action.B))
   237  				if err != nil {
   238  					return skymodules.RenterContract{}, err
   239  				}
   240  				ruB = newRootUpdateUpdateRoot(rootB)
   241  			}
   242  			ruA.root, ruB.root = ruB.root, ruA.root
   243  			rootUpdates[action.A] = ruA
   244  			rootUpdates[action.B] = ruB
   245  
   246  		case modules.WriteActionUpdate:
   247  			return skymodules.RenterContract{}, errors.New("update not supported")
   248  
   249  		default:
   250  			build.Critical("unknown action type", action.Type)
   251  		}
   252  	}
   253  
   254  	if newFileSize > contract.LastRevision().NewFileSize {
   255  		addedSectors := (newFileSize - contract.LastRevision().NewFileSize) / modules.SectorSize
   256  		storagePrice = sectorStoragePrice.Mul64(addedSectors)
   257  		collateral = sectorCollateral.Mul64(addedSectors)
   258  	}
   259  
   260  	// estimate cost of Merkle proof
   261  	proofSize := crypto.HashSize * (128 + len(actions))
   262  	bandwidthPrice = bandwidthPrice.Add(s.host.DownloadBandwidthPrice.Mul64(uint64(proofSize)))
   263  
   264  	// to mitigate small errors (e.g. differing block heights), fudge the
   265  	// price and collateral by hostPriceLeeway.
   266  	cost := s.host.BaseRPCPrice.Add(bandwidthPrice).Add(storagePrice).MulFloat(1 + hostPriceLeeway)
   267  	collateral = collateral.MulFloat(1 - hostPriceLeeway)
   268  
   269  	// check that enough funds are available
   270  	if contract.RenterFunds().Cmp(cost) < 0 {
   271  		return skymodules.RenterContract{}, errors.New("contract has insufficient funds to support upload")
   272  	}
   273  	if contract.LastRevision().MissedHostOutput().Value.Cmp(collateral) < 0 {
   274  		// The contract doesn't have enough value in it to supply the
   275  		// collateral. Instead of giving up, have the host put up everything
   276  		// that remains, even if that is zero. The renter was aware when the
   277  		// contract was formed that there may not be enough collateral in the
   278  		// contract to cover the full storage needs for the renter, yet the
   279  		// renter formed the contract anyway. Therefore the renter must think
   280  		// that it's sufficient.
   281  		//
   282  		// TODO: log.Debugln here to indicate that the host is having issues
   283  		// supplying collateral.
   284  		//
   285  		// TODO: We may in the future want to have the renter perform a renewal
   286  		// on this contract so that the host can refill the collateral. That's a
   287  		// concern at the renter level though, not at the session level. The
   288  		// session should still be assuming that if the renter is willing to use
   289  		// this contract, the renter is aware that there isn't enough collateral
   290  		// remaining and is happy to use the contract anyway. Therefore this
   291  		// TODO should be moved to a different part of the codebase.
   292  		collateral = contract.LastRevision().MissedHostOutput().Value
   293  	}
   294  
   295  	// create the revision; we will update the Merkle root later
   296  	rev, err := contract.LastRevision().PaymentRevision(cost)
   297  	if err != nil {
   298  		return skymodules.RenterContract{}, errors.AddContext(err, "Error creating new write revision")
   299  	}
   300  
   301  	rev.SetMissedHostPayout(rev.MissedHostOutput().Value.Sub(collateral))
   302  	voidOutput, err := rev.MissedVoidOutput()
   303  	rev.SetMissedVoidPayout(voidOutput.Value.Add(collateral))
   304  	rev.NewFileSize = newFileSize
   305  
   306  	// create the request
   307  	req := modules.LoopWriteRequest{
   308  		Actions:           actions,
   309  		MerkleProof:       true,
   310  		NewRevisionNumber: rev.NewRevisionNumber,
   311  	}
   312  	req.NewValidProofValues = make([]types.Currency, len(rev.NewValidProofOutputs))
   313  	for i, o := range rev.NewValidProofOutputs {
   314  		req.NewValidProofValues[i] = o.Value
   315  	}
   316  	req.NewMissedProofValues = make([]types.Currency, len(rev.NewMissedProofOutputs))
   317  	for i, o := range rev.NewMissedProofOutputs {
   318  		req.NewMissedProofValues[i] = o.Value
   319  	}
   320  
   321  	defer func() {
   322  		// Increase Successful/Failed interactions accordingly
   323  		if err != nil {
   324  			s.hdb.IncrementFailedInteractions(s.host.PublicKey)
   325  		} else {
   326  			s.hdb.IncrementSuccessfulInteractions(s.host.PublicKey)
   327  		}
   328  
   329  		// reset deadline
   330  		extendDeadline(s.conn, time.Hour)
   331  	}()
   332  
   333  	// Disrupt here before sending the signed revision to the host.
   334  	if s.deps.Disrupt("InterruptUploadBeforeSendingRevision") {
   335  		return skymodules.RenterContract{}, errors.New("InterruptUploadBeforeSendingRevision disrupt")
   336  	}
   337  
   338  	// send Write RPC request
   339  	extendDeadline(s.conn, modules.NegotiateFileContractRevisionTime)
   340  	if err := s.writeRequest(modules.RPCLoopWrite, req); err != nil {
   341  		return skymodules.RenterContract{}, err
   342  	}
   343  
   344  	// read Merkle proof from host
   345  	var merkleResp modules.LoopWriteMerkleProof
   346  	if err := s.readResponse(&merkleResp, modules.RPCMinLen); err != nil {
   347  		return skymodules.RenterContract{}, err
   348  	}
   349  	// verify the proof, first by verifying the old Merkle root...
   350  	numSectors := contract.LastRevision().NewFileSize / modules.SectorSize
   351  	proofRanges := calculateProofRanges(actions, numSectors)
   352  	proofHashes := merkleResp.OldSubtreeHashes
   353  	leafHashes := merkleResp.OldLeafHashes
   354  	oldRoot, newRoot := contract.LastRevision().NewFileMerkleRoot, merkleResp.NewMerkleRoot
   355  	if !crypto.VerifyDiffProof(proofRanges, numSectors, proofHashes, leafHashes, oldRoot) {
   356  		return skymodules.RenterContract{}, errors.New("invalid Merkle proof for old root")
   357  	}
   358  	// ...then by modifying the leaves and verifying the new Merkle root
   359  	leafHashes = modifyLeaves(leafHashes, actions, numSectors)
   360  	proofRanges = modifyProofRanges(proofRanges, actions, numSectors)
   361  	if !crypto.VerifyDiffProof(proofRanges, numSectors, proofHashes, leafHashes, newRoot) {
   362  		return skymodules.RenterContract{}, errors.New("invalid Merkle proof for new root")
   363  	}
   364  
   365  	// If the proof was correct, update the revision's root.
   366  	rev.NewFileMerkleRoot = newRoot
   367  
   368  	// record the change we are about to make to the contract. If we lose power
   369  	// mid-revision, this allows us to restore either the pre-revision or
   370  	// post-revision contract.
   371  	//
   372  	walTxn, err := sc.managedRecordRootUpdates(rev, rootUpdates, storagePrice, bandwidthPrice)
   373  	if err != nil {
   374  		return skymodules.RenterContract{}, err
   375  	}
   376  
   377  	// , sign it, and send it
   378  	txn := types.Transaction{
   379  		FileContractRevisions: []types.FileContractRevision{rev},
   380  		TransactionSignatures: []types.TransactionSignature{
   381  			{
   382  				ParentID:       crypto.Hash(rev.ParentID),
   383  				CoveredFields:  types.CoveredFields{FileContractRevisions: []uint64{0}},
   384  				PublicKeyIndex: 0, // renter key is always first -- see formContract
   385  			},
   386  			{
   387  				ParentID:       crypto.Hash(rev.ParentID),
   388  				PublicKeyIndex: 1,
   389  				CoveredFields:  types.CoveredFields{FileContractRevisions: []uint64{0}},
   390  				Signature:      nil, // to be provided by host
   391  			},
   392  		},
   393  	}
   394  	sig := crypto.SignHash(txn.SigHash(0, s.height), contract.SecretKey)
   395  	txn.TransactionSignatures[0].Signature = sig[:]
   396  	renterSig := modules.LoopWriteResponse{
   397  		Signature: sig[:],
   398  	}
   399  	if err := s.writeResponse(renterSig, nil); err != nil {
   400  		return skymodules.RenterContract{}, err
   401  	}
   402  
   403  	// read the host's signature
   404  	var hostSig modules.LoopWriteResponse
   405  	if err := s.readResponse(&hostSig, modules.RPCMinLen); err != nil {
   406  		// If the host was OOS, we update the contract utility.
   407  		if modules.IsOOSErr(err) {
   408  			u := sc.Utility()
   409  			u.GoodForUpload = false // Stop uploading to such a host immediately.
   410  			u.LastOOSErr = s.height
   411  			err = errors.Compose(err, sc.UpdateUtility(u))
   412  			err = errors.AddContext(err, "marking host as !gfu becaue it ran out of storage")
   413  		}
   414  		return skymodules.RenterContract{}, errors.AddContext(err, "failed to read host's signature")
   415  	}
   416  	txn.TransactionSignatures[1].Signature = hostSig.Signature
   417  
   418  	// Disrupt here before updating the contract.
   419  	if s.deps.Disrupt("InterruptUploadAfterSendingRevision") {
   420  		return skymodules.RenterContract{}, errors.New("InterruptUploadAfterSendingRevision disrupt")
   421  	}
   422  
   423  	// update contract
   424  	//
   425  	err = sc.managedCommitAppend(walTxn, txn, storagePrice, bandwidthPrice)
   426  	if err != nil {
   427  		return skymodules.RenterContract{}, err
   428  	}
   429  
   430  	// Sanity check: Make sure the contract on disk has the right root.
   431  	if build.Release == "testing" {
   432  		// Check cached root first.
   433  		if sc.merkleRoots.root() != newRoot {
   434  			build.Critical("write: cached root mismatch")
   435  		}
   436  		// Check on-disk root.
   437  		roots, err := sc.merkleRoots.merkleRoots()
   438  		if err != nil {
   439  			build.Critical("failed to fetch roots for sanity check")
   440  		}
   441  		if cachedMerkleRoot(roots) != newRoot {
   442  			build.Critical("write: root mismatch")
   443  		}
   444  	}
   445  	return sc.Metadata(), nil
   446  }
   447  
   448  // Read calls the Read RPC, writing the requested data to w. The RPC can be
   449  // cancelled (with a granularity of one section) via the cancel channel.
   450  func (s *Session) Read(w io.Writer, req modules.LoopReadRequest, cancel <-chan struct{}) (_ skymodules.RenterContract, err error) {
   451  	// Reset deadline when finished.
   452  	defer extendDeadline(s.conn, time.Hour)
   453  
   454  	// Sanity-check the request.
   455  	for _, sec := range req.Sections {
   456  		if uint64(sec.Offset)+uint64(sec.Length) > modules.SectorSize {
   457  			return skymodules.RenterContract{}, errors.New("illegal offset and/or length")
   458  		}
   459  		if req.MerkleProof {
   460  			if sec.Offset%crypto.SegmentSize != 0 || sec.Length%crypto.SegmentSize != 0 {
   461  				return skymodules.RenterContract{}, errors.New("offset and length must be multiples of SegmentSize when requesting a Merkle proof")
   462  			}
   463  		}
   464  	}
   465  
   466  	// Acquire the contract.
   467  	sc, haveContract := s.contractSet.Acquire(s.contractID)
   468  	if !haveContract {
   469  		return skymodules.RenterContract{}, errors.New("contract not present in contract set")
   470  	}
   471  	defer s.contractSet.Return(sc)
   472  	contract := sc.header // for convenience
   473  
   474  	// calculate estimated bandwidth
   475  	var totalLength uint64
   476  	for _, sec := range req.Sections {
   477  		totalLength += uint64(sec.Length)
   478  	}
   479  	var estProofHashes uint64
   480  	if req.MerkleProof {
   481  		// use the worst-case proof size of 2*tree depth (this occurs when
   482  		// proving across the two leaves in the center of the tree)
   483  		estHashesPerProof := 2 * bits.Len64(modules.SectorSize/crypto.SegmentSize)
   484  		estProofHashes = uint64(len(req.Sections) * estHashesPerProof)
   485  	}
   486  	estBandwidth := totalLength + estProofHashes*crypto.HashSize
   487  	if estBandwidth < modules.RPCMinLen {
   488  		estBandwidth = modules.RPCMinLen
   489  	}
   490  	// calculate sector accesses
   491  	sectorAccesses := make(map[crypto.Hash]struct{})
   492  	for _, sec := range req.Sections {
   493  		sectorAccesses[sec.MerkleRoot] = struct{}{}
   494  	}
   495  	// calculate price
   496  	bandwidthPrice := s.host.DownloadBandwidthPrice.Mul64(estBandwidth)
   497  	sectorAccessPrice := s.host.SectorAccessPrice.Mul64(uint64(len(sectorAccesses)))
   498  	price := s.host.BaseRPCPrice.Add(bandwidthPrice).Add(sectorAccessPrice)
   499  	if contract.RenterFunds().Cmp(price) < 0 {
   500  		return skymodules.RenterContract{}, errors.New("contract has insufficient funds to support download")
   501  	}
   502  	// To mitigate small errors (e.g. differing block heights), fudge the
   503  	// price and collateral by 0.2%.
   504  	price = price.MulFloat(1 + hostPriceLeeway)
   505  
   506  	// create the download revision and sign it
   507  	rev, err := newDownloadRevision(contract.LastRevision(), price)
   508  	if err != nil {
   509  		return skymodules.RenterContract{}, errors.AddContext(err, "Error creating new download revision")
   510  	}
   511  
   512  	txn := types.Transaction{
   513  		FileContractRevisions: []types.FileContractRevision{rev},
   514  		TransactionSignatures: []types.TransactionSignature{
   515  			{
   516  				ParentID:       crypto.Hash(rev.ParentID),
   517  				CoveredFields:  types.CoveredFields{FileContractRevisions: []uint64{0}},
   518  				PublicKeyIndex: 0, // renter key is always first -- see formContract
   519  			},
   520  			{
   521  				ParentID:       crypto.Hash(rev.ParentID),
   522  				PublicKeyIndex: 1,
   523  				CoveredFields:  types.CoveredFields{FileContractRevisions: []uint64{0}},
   524  				Signature:      nil, // to be provided by host
   525  			},
   526  		},
   527  	}
   528  	sig := crypto.SignHash(txn.SigHash(0, s.height), contract.SecretKey)
   529  	txn.TransactionSignatures[0].Signature = sig[:]
   530  
   531  	req.NewRevisionNumber = rev.NewRevisionNumber
   532  	req.NewValidProofValues = make([]types.Currency, len(rev.NewValidProofOutputs))
   533  	for i, o := range rev.NewValidProofOutputs {
   534  		req.NewValidProofValues[i] = o.Value
   535  	}
   536  	req.NewMissedProofValues = make([]types.Currency, len(rev.NewMissedProofOutputs))
   537  	for i, o := range rev.NewMissedProofOutputs {
   538  		req.NewMissedProofValues[i] = o.Value
   539  	}
   540  	req.Signature = sig[:]
   541  
   542  	// record the change we are about to make to the contract. If we lose power
   543  	// mid-revision, this allows us to restore either the pre-revision or
   544  	// post-revision contract.
   545  	walTxn, err := sc.managedRecordDownloadIntent(rev, price)
   546  	if err != nil {
   547  		return skymodules.RenterContract{}, err
   548  	}
   549  
   550  	// Increase Successful/Failed interactions accordingly
   551  	defer func() {
   552  		if err != nil {
   553  			s.hdb.IncrementFailedInteractions(contract.HostPublicKey())
   554  		} else {
   555  			s.hdb.IncrementSuccessfulInteractions(contract.HostPublicKey())
   556  		}
   557  	}()
   558  
   559  	// Disrupt before sending the signed revision to the host.
   560  	if s.deps.Disrupt("InterruptDownloadBeforeSendingRevision") {
   561  		return skymodules.RenterContract{}, errors.New("InterruptDownloadBeforeSendingRevision disrupt")
   562  	}
   563  
   564  	// send request
   565  	extendDeadline(s.conn, modules.NegotiateDownloadTime)
   566  	err = s.writeRequest(modules.RPCLoopRead, req)
   567  	if err != nil {
   568  		return skymodules.RenterContract{}, err
   569  	}
   570  
   571  	// spawn a goroutine to handle cancellation
   572  	doneChan := make(chan struct{})
   573  	go func() {
   574  		select {
   575  		case <-cancel:
   576  		case <-doneChan:
   577  		}
   578  		s.writeResponse(modules.RPCLoopReadStop, nil)
   579  	}()
   580  	// ensure we send RPCLoopReadStop before returning
   581  	defer close(doneChan)
   582  
   583  	// read responses
   584  	var hostSig []byte
   585  	for _, sec := range req.Sections {
   586  		var resp modules.LoopReadResponse
   587  		err = s.readResponse(&resp, modules.RPCMinLen+uint64(sec.Length))
   588  		if err != nil {
   589  			return skymodules.RenterContract{}, err
   590  		}
   591  		// The host may have sent data, a signature, or both. If they sent data,
   592  		// validate it.
   593  		if len(resp.Data) > 0 {
   594  			if len(resp.Data) != int(sec.Length) {
   595  				return skymodules.RenterContract{}, errors.New("host did not send enough sector data")
   596  			}
   597  			if req.MerkleProof {
   598  				proofStart := int(sec.Offset) / crypto.SegmentSize
   599  				proofEnd := int(sec.Offset+sec.Length) / crypto.SegmentSize
   600  				if !crypto.VerifyRangeProof(resp.Data, resp.MerkleProof, proofStart, proofEnd, sec.MerkleRoot) {
   601  					return skymodules.RenterContract{}, errors.New("host provided incorrect sector data or Merkle proof")
   602  				}
   603  			}
   604  			// write sector data
   605  			if _, err := w.Write(resp.Data); err != nil {
   606  				return skymodules.RenterContract{}, err
   607  			}
   608  		}
   609  		// If the host sent a signature, exit the loop; they won't be sending
   610  		// any more data
   611  		if len(resp.Signature) > 0 {
   612  			hostSig = resp.Signature
   613  			break
   614  		}
   615  	}
   616  	if hostSig == nil {
   617  		// the host is required to send a signature; if they haven't sent one
   618  		// yet, they should send an empty ReadResponse containing just the
   619  		// signature.
   620  		var resp modules.LoopReadResponse
   621  		err = s.readResponse(&resp, modules.RPCMinLen)
   622  		if err != nil {
   623  			return skymodules.RenterContract{}, err
   624  		}
   625  		hostSig = resp.Signature
   626  	}
   627  	txn.TransactionSignatures[1].Signature = hostSig
   628  
   629  	// Disrupt before committing.
   630  	if s.deps.Disrupt("InterruptDownloadAfterSendingRevision") {
   631  		return skymodules.RenterContract{}, errors.New("InterruptDownloadAfterSendingRevision disrupt")
   632  	}
   633  
   634  	// update contract and metrics
   635  	if err := sc.managedCommitDownload(walTxn, txn, price); err != nil {
   636  		return skymodules.RenterContract{}, err
   637  	}
   638  
   639  	return sc.Metadata(), nil
   640  }
   641  
   642  // ReadSection calls the Read RPC with a single section and returns the
   643  // requested data. A Merkle proof is always requested.
   644  func (s *Session) ReadSection(root crypto.Hash, offset, length uint32) (_ skymodules.RenterContract, _ []byte, err error) {
   645  	req := modules.LoopReadRequest{
   646  		Sections: []modules.LoopReadRequestSection{{
   647  			MerkleRoot: root,
   648  			Offset:     offset,
   649  			Length:     length,
   650  		}},
   651  		MerkleProof: true,
   652  	}
   653  	var buf bytes.Buffer
   654  	buf.Grow(int(length))
   655  	contract, err := s.Read(&buf, req, nil)
   656  	return contract, buf.Bytes(), err
   657  }
   658  
   659  // SectorRoots calls the contract roots download RPC and returns the requested sector roots. The
   660  // Revision and Signature fields of req are filled in automatically. If a
   661  // Merkle proof is requested, it is verified.
   662  func (s *Session) SectorRoots(req modules.LoopSectorRootsRequest) (_ skymodules.RenterContract, _ []crypto.Hash, err error) {
   663  	// Reset deadline when finished.
   664  	defer extendDeadline(s.conn, time.Hour)
   665  
   666  	// Acquire the contract.
   667  	sc, haveContract := s.contractSet.Acquire(s.contractID)
   668  	if !haveContract {
   669  		return skymodules.RenterContract{}, nil, errors.New("contract not present in contract set")
   670  	}
   671  	defer s.contractSet.Return(sc)
   672  	contract := sc.header // for convenience
   673  
   674  	// calculate price
   675  	estProofHashes := bits.Len64(contract.LastRevision().NewFileSize / modules.SectorSize)
   676  	estBandwidth := (uint64(estProofHashes) + req.NumRoots) * crypto.HashSize
   677  	if estBandwidth < modules.RPCMinLen {
   678  		estBandwidth = modules.RPCMinLen
   679  	}
   680  	bandwidthPrice := s.host.DownloadBandwidthPrice.Mul64(estBandwidth)
   681  	price := s.host.BaseRPCPrice.Add(bandwidthPrice)
   682  	if contract.RenterFunds().Cmp(price) < 0 {
   683  		return skymodules.RenterContract{}, nil, errors.New("contract has insufficient funds to support sector roots download")
   684  	}
   685  	// To mitigate small errors (e.g. differing block heights), fudge the
   686  	// price and collateral by 0.2%.
   687  	price = price.MulFloat(1 + hostPriceLeeway)
   688  
   689  	// create the download revision and sign it
   690  	rev, err := newDownloadRevision(contract.LastRevision(), price)
   691  	if err != nil {
   692  		return skymodules.RenterContract{}, nil, errors.AddContext(err, "Error creating new download revision")
   693  	}
   694  
   695  	txn := types.Transaction{
   696  		FileContractRevisions: []types.FileContractRevision{rev},
   697  		TransactionSignatures: []types.TransactionSignature{
   698  			{
   699  				ParentID:       crypto.Hash(rev.ParentID),
   700  				CoveredFields:  types.CoveredFields{FileContractRevisions: []uint64{0}},
   701  				PublicKeyIndex: 0, // renter key is always first -- see formContract
   702  			},
   703  			{
   704  				ParentID:       crypto.Hash(rev.ParentID),
   705  				PublicKeyIndex: 1,
   706  				CoveredFields:  types.CoveredFields{FileContractRevisions: []uint64{0}},
   707  				Signature:      nil, // to be provided by host
   708  			},
   709  		},
   710  	}
   711  	sig := crypto.SignHash(txn.SigHash(0, s.height), contract.SecretKey)
   712  	txn.TransactionSignatures[0].Signature = sig[:]
   713  
   714  	// fill in the missing request fields
   715  	req.NewRevisionNumber = rev.NewRevisionNumber
   716  	req.NewValidProofValues = make([]types.Currency, len(rev.NewValidProofOutputs))
   717  	for i, o := range rev.NewValidProofOutputs {
   718  		req.NewValidProofValues[i] = o.Value
   719  	}
   720  	req.NewMissedProofValues = make([]types.Currency, len(rev.NewMissedProofOutputs))
   721  	for i, o := range rev.NewMissedProofOutputs {
   722  		req.NewMissedProofValues[i] = o.Value
   723  	}
   724  	req.Signature = sig[:]
   725  
   726  	// record the change we are about to make to the contract. If we lose power
   727  	// mid-revision, this allows us to restore either the pre-revision or
   728  	// post-revision contract.
   729  	walTxn, err := sc.managedRecordDownloadIntent(rev, price)
   730  	if err != nil {
   731  		return skymodules.RenterContract{}, nil, err
   732  	}
   733  
   734  	// Increase Successful/Failed interactions accordingly
   735  	defer func() {
   736  		if err != nil {
   737  			s.hdb.IncrementFailedInteractions(contract.HostPublicKey())
   738  		} else {
   739  			s.hdb.IncrementSuccessfulInteractions(contract.HostPublicKey())
   740  		}
   741  	}()
   742  
   743  	// send SectorRoots RPC request
   744  	extendDeadline(s.conn, modules.NegotiateDownloadTime)
   745  	var resp modules.LoopSectorRootsResponse
   746  	err = s.call(modules.RPCLoopSectorRoots, req, &resp, modules.RPCMinLen+(req.NumRoots*crypto.HashSize))
   747  	if err != nil {
   748  		return skymodules.RenterContract{}, nil, err
   749  	}
   750  	// verify the response
   751  	if len(resp.SectorRoots) != int(req.NumRoots) {
   752  		return skymodules.RenterContract{}, nil, errors.New("host did not send the requested number of sector roots")
   753  	}
   754  	proofStart, proofEnd := int(req.RootOffset), int(req.RootOffset+req.NumRoots)
   755  	if !crypto.VerifySectorRangeProof(resp.SectorRoots, resp.MerkleProof, proofStart, proofEnd, rev.NewFileMerkleRoot) {
   756  		return skymodules.RenterContract{}, nil, errors.New("host provided incorrect sector data or Merkle proof")
   757  	}
   758  
   759  	// add host signature
   760  	txn.TransactionSignatures[1].Signature = resp.Signature
   761  
   762  	// update contract and metrics
   763  	if err := sc.managedCommitDownload(walTxn, txn, price); err != nil {
   764  		return skymodules.RenterContract{}, nil, err
   765  	}
   766  
   767  	return sc.Metadata(), resp.SectorRoots, nil
   768  }
   769  
   770  // RecoverSectorRoots calls the contract roots download RPC and returns the requested sector roots. The
   771  // Revision and Signature fields of req are filled in automatically. If a
   772  // Merkle proof is requested, it is verified.
   773  func (s *Session) RecoverSectorRoots(lastRev types.FileContractRevision, sk crypto.SecretKey) (_ types.Transaction, _ []crypto.Hash, err error) {
   774  	// Calculate total roots we need to fetch.
   775  	numRoots := lastRev.NewFileSize / modules.SectorSize
   776  	if lastRev.NewFileSize%modules.SectorSize != 0 {
   777  		numRoots++
   778  	}
   779  	// Create the request.
   780  	req := modules.LoopSectorRootsRequest{
   781  		RootOffset: 0,
   782  		NumRoots:   numRoots,
   783  	}
   784  	// Reset deadline when finished.
   785  	defer extendDeadline(s.conn, time.Hour)
   786  
   787  	// calculate price
   788  	estProofHashes := bits.Len64(lastRev.NewFileSize / modules.SectorSize)
   789  	estBandwidth := (uint64(estProofHashes) + req.NumRoots) * crypto.HashSize
   790  	if estBandwidth < modules.RPCMinLen {
   791  		estBandwidth = modules.RPCMinLen
   792  	}
   793  	bandwidthPrice := s.host.DownloadBandwidthPrice.Mul64(estBandwidth)
   794  	price := s.host.BaseRPCPrice.Add(bandwidthPrice)
   795  	if lastRev.ValidRenterPayout().Cmp(price) < 0 {
   796  		return types.Transaction{}, nil, errors.New("contract has insufficient funds to support sector roots download")
   797  	}
   798  	// To mitigate small errors (e.g. differing block heights), fudge the
   799  	// price and collateral by 0.2%.
   800  	price = price.MulFloat(1 + hostPriceLeeway)
   801  
   802  	// create the download revision and sign it
   803  	rev, err := newDownloadRevision(lastRev, price)
   804  	if err != nil {
   805  		return types.Transaction{}, nil, errors.AddContext(err, "Error creating new download revision")
   806  	}
   807  
   808  	txn := types.Transaction{
   809  		FileContractRevisions: []types.FileContractRevision{rev},
   810  		TransactionSignatures: []types.TransactionSignature{
   811  			{
   812  				ParentID:       crypto.Hash(rev.ParentID),
   813  				CoveredFields:  types.CoveredFields{FileContractRevisions: []uint64{0}},
   814  				PublicKeyIndex: 0, // renter key is always first -- see formContract
   815  			},
   816  			{
   817  				ParentID:       crypto.Hash(rev.ParentID),
   818  				PublicKeyIndex: 1,
   819  				CoveredFields:  types.CoveredFields{FileContractRevisions: []uint64{0}},
   820  				Signature:      nil, // to be provided by host
   821  			},
   822  		},
   823  	}
   824  	sig := crypto.SignHash(txn.SigHash(0, s.height), sk)
   825  	txn.TransactionSignatures[0].Signature = sig[:]
   826  
   827  	// fill in the missing request fields
   828  	req.NewRevisionNumber = rev.NewRevisionNumber
   829  	req.NewValidProofValues = make([]types.Currency, len(rev.NewValidProofOutputs))
   830  	for i, o := range rev.NewValidProofOutputs {
   831  		req.NewValidProofValues[i] = o.Value
   832  	}
   833  	req.NewMissedProofValues = make([]types.Currency, len(rev.NewMissedProofOutputs))
   834  	for i, o := range rev.NewMissedProofOutputs {
   835  		req.NewMissedProofValues[i] = o.Value
   836  	}
   837  	req.Signature = sig[:]
   838  
   839  	// Increase Successful/Failed interactions accordingly
   840  	defer func() {
   841  		if err != nil {
   842  			s.hdb.IncrementFailedInteractions(s.host.PublicKey)
   843  		} else {
   844  			s.hdb.IncrementSuccessfulInteractions(s.host.PublicKey)
   845  		}
   846  	}()
   847  
   848  	// send SectorRoots RPC request
   849  	extendDeadline(s.conn, modules.NegotiateDownloadTime)
   850  	var resp modules.LoopSectorRootsResponse
   851  	err = s.call(modules.RPCLoopSectorRoots, req, &resp, modules.RPCMinLen+(req.NumRoots*crypto.HashSize))
   852  	if err != nil {
   853  		return types.Transaction{}, nil, err
   854  	}
   855  	// verify the response
   856  	if len(resp.SectorRoots) != int(req.NumRoots) {
   857  		return types.Transaction{}, nil, errors.New("host did not send the requested number of sector roots")
   858  	}
   859  	proofStart, proofEnd := int(req.RootOffset), int(req.RootOffset+req.NumRoots)
   860  	if !crypto.VerifySectorRangeProof(resp.SectorRoots, resp.MerkleProof, proofStart, proofEnd, rev.NewFileMerkleRoot) {
   861  		return types.Transaction{}, nil, errors.New("host provided incorrect sector data or Merkle proof")
   862  	}
   863  
   864  	// add host signature
   865  	txn.TransactionSignatures[1].Signature = resp.Signature
   866  	return txn, resp.SectorRoots, nil
   867  }
   868  
   869  // shutdown terminates the revision loop and signals the goroutine spawned in
   870  // NewSession to return.
   871  func (s *Session) shutdown() {
   872  	extendDeadline(s.conn, modules.NegotiateSettingsTime)
   873  	// don't care about this error
   874  	_ = s.writeRequest(modules.RPCLoopExit, nil)
   875  	close(s.closeChan)
   876  }
   877  
   878  // Close cleanly terminates the protocol session with the host and closes the
   879  // connection.
   880  func (s *Session) Close() error {
   881  	// using once ensures that Close is idempotent
   882  	s.once.Do(s.shutdown)
   883  	return s.conn.Close()
   884  }
   885  
   886  // NewSession initiates the RPC loop with a host and returns a Session.
   887  func (cs *ContractSet) NewSession(host skymodules.HostDBEntry, id types.FileContractID, currentHeight types.BlockHeight, hdb hostDB, logger *log.Logger, cancel <-chan struct{}) (_ *Session, err error) {
   888  	sc, ok := cs.Acquire(id)
   889  	if !ok {
   890  		return nil, errors.New("could not locate contract to create session")
   891  	}
   892  	defer cs.Return(sc)
   893  	s, err := cs.managedNewSession(host, currentHeight, hdb, cancel)
   894  	if err != nil {
   895  		return nil, errors.AddContext(err, "unable to create a new session with the host")
   896  	}
   897  	// Lock the contract
   898  	rev, sigs, err := s.Lock(id, sc.header.SecretKey)
   899  	if err != nil {
   900  		s.Close()
   901  		return nil, errors.AddContext(err, "unable to get a session lock")
   902  	}
   903  
   904  	// Resynchronize
   905  	err = sc.managedSyncRevision(rev, sigs)
   906  	if err != nil {
   907  		logger.Printf("%v revision resync failed, err: %v\n", host.PublicKey.String(), err)
   908  		err = errors.Compose(err, s.Close())
   909  		return nil, errors.AddContext(err, "unable to sync revisions when creating session")
   910  	}
   911  	logger.Debugf("%v revision resync attempted, succeeded: %v\n", host.PublicKey.String(), sc.LastRevision().NewRevisionNumber == rev.NewRevisionNumber)
   912  
   913  	return s, nil
   914  }
   915  
   916  // NewRawSession creates a new session unassociated with any contract.
   917  func (cs *ContractSet) NewRawSession(host skymodules.HostDBEntry, currentHeight types.BlockHeight, hdb hostDB, cancel <-chan struct{}) (_ *Session, err error) {
   918  	return cs.managedNewSession(host, currentHeight, hdb, cancel)
   919  }
   920  
   921  // managedNewSession initiates the RPC loop with a host and returns a Session.
   922  func (cs *ContractSet) managedNewSession(host skymodules.HostDBEntry, currentHeight types.BlockHeight, hdb hostDB, cancel <-chan struct{}) (_ *Session, err error) {
   923  	// Increase Successful/Failed interactions accordingly
   924  	defer func() {
   925  		if err != nil {
   926  			hdb.IncrementFailedInteractions(host.PublicKey)
   927  			err = errors.Extend(err, skymodules.ErrHostFault)
   928  		} else {
   929  			hdb.IncrementSuccessfulInteractions(host.PublicKey)
   930  		}
   931  	}()
   932  
   933  	// If we are using a custom resolver we need to replace the domain name
   934  	// with 127.0.0.1 to be able to dial the host.
   935  	if cs.staticDeps.Disrupt("customResolver") {
   936  		port := host.NetAddress.Port()
   937  		host.NetAddress = modules.NetAddress(fmt.Sprintf("127.0.0.1:%s", port))
   938  	}
   939  
   940  	c, err := (&net.Dialer{
   941  		Cancel:  cancel,
   942  		Timeout: sessionDialTimeout,
   943  	}).Dial("tcp", string(host.NetAddress))
   944  	if err != nil {
   945  		return nil, errors.AddContext(err, "unsuccessful dial when creating a new session")
   946  	}
   947  	conn := ratelimit.NewRLConn(c, cs.staticRL, cancel)
   948  
   949  	closeChan := make(chan struct{})
   950  	go func() {
   951  		select {
   952  		case <-cancel:
   953  			conn.Close()
   954  		case <-closeChan:
   955  			// we don't close the connection here because we want session.Close
   956  			// to be able to return the Close error directly
   957  		}
   958  	}()
   959  
   960  	// Perform the handshake and create the session object.
   961  	aead, challenge, err := performSessionHandshake(conn, host.PublicKey)
   962  	if err != nil {
   963  		conn.Close()
   964  		close(closeChan)
   965  		return nil, errors.AddContext(err, "session handshake failed")
   966  	}
   967  	s := &Session{
   968  		aead:        aead,
   969  		challenge:   challenge.Challenge,
   970  		closeChan:   closeChan,
   971  		conn:        conn,
   972  		contractSet: cs,
   973  		deps:        cs.staticDeps,
   974  		hdb:         hdb,
   975  		height:      currentHeight,
   976  		host:        host,
   977  	}
   978  
   979  	return s, nil
   980  }
   981  
   982  // calculateProofRanges returns the proof ranges that should be used to verify a
   983  // pre-modification Merkle diff proof for the specified actions.
   984  func calculateProofRanges(actions []modules.LoopWriteAction, oldNumSectors uint64) []crypto.ProofRange {
   985  	newNumSectors := oldNumSectors
   986  	sectorsChanged := make(map[uint64]struct{})
   987  	for _, action := range actions {
   988  		switch action.Type {
   989  		case modules.WriteActionAppend:
   990  			sectorsChanged[newNumSectors] = struct{}{}
   991  			newNumSectors++
   992  
   993  		case modules.WriteActionTrim:
   994  			newNumSectors--
   995  			sectorsChanged[newNumSectors] = struct{}{}
   996  
   997  		case modules.WriteActionSwap:
   998  			sectorsChanged[action.A] = struct{}{}
   999  			sectorsChanged[action.B] = struct{}{}
  1000  
  1001  		case modules.WriteActionUpdate:
  1002  			panic("update not supported")
  1003  		}
  1004  	}
  1005  
  1006  	oldRanges := make([]crypto.ProofRange, 0, len(sectorsChanged))
  1007  	for index := range sectorsChanged {
  1008  		if index < oldNumSectors {
  1009  			oldRanges = append(oldRanges, crypto.ProofRange{
  1010  				Start: index,
  1011  				End:   index + 1,
  1012  			})
  1013  		}
  1014  	}
  1015  	sort.Slice(oldRanges, func(i, j int) bool {
  1016  		return oldRanges[i].Start < oldRanges[j].Start
  1017  	})
  1018  
  1019  	return oldRanges
  1020  }
  1021  
  1022  // modifyProofRanges modifies the proof ranges produced by calculateProofRanges
  1023  // to verify a post-modification Merkle diff proof for the specified actions.
  1024  func modifyProofRanges(proofRanges []crypto.ProofRange, actions []modules.LoopWriteAction, numSectors uint64) []crypto.ProofRange {
  1025  	for _, action := range actions {
  1026  		switch action.Type {
  1027  		case modules.WriteActionAppend:
  1028  			proofRanges = append(proofRanges, crypto.ProofRange{
  1029  				Start: numSectors,
  1030  				End:   numSectors + 1,
  1031  			})
  1032  			numSectors++
  1033  
  1034  		case modules.WriteActionTrim:
  1035  			proofRanges = proofRanges[:uint64(len(proofRanges))-action.A]
  1036  			numSectors--
  1037  		}
  1038  	}
  1039  	return proofRanges
  1040  }
  1041  
  1042  // modifyLeaves modifies the leaf hashes of a Merkle diff proof to verify a
  1043  // post-modification Merkle diff proof for the specified actions.
  1044  func modifyLeaves(leafHashes []crypto.Hash, actions []modules.LoopWriteAction, numSectors uint64) []crypto.Hash {
  1045  	// determine which sector index corresponds to each leaf hash
  1046  	var indices []uint64
  1047  	for _, action := range actions {
  1048  		switch action.Type {
  1049  		case modules.WriteActionAppend:
  1050  			indices = append(indices, numSectors)
  1051  			numSectors++
  1052  		case modules.WriteActionTrim:
  1053  			for j := uint64(0); j < action.A; j++ {
  1054  				indices = append(indices, numSectors)
  1055  				numSectors--
  1056  			}
  1057  		case modules.WriteActionSwap:
  1058  			indices = append(indices, action.A, action.B)
  1059  		}
  1060  	}
  1061  	sort.Slice(indices, func(i, j int) bool {
  1062  		return indices[i] < indices[j]
  1063  	})
  1064  	indexMap := make(map[uint64]int, len(leafHashes))
  1065  	for i, index := range indices {
  1066  		if i > 0 && index == indices[i-1] {
  1067  			continue // remove duplicates
  1068  		}
  1069  		indexMap[index] = i
  1070  	}
  1071  
  1072  	for _, action := range actions {
  1073  		switch action.Type {
  1074  		case modules.WriteActionAppend:
  1075  			leafHashes = append(leafHashes, crypto.MerkleRoot(action.Data))
  1076  
  1077  		case modules.WriteActionTrim:
  1078  			leafHashes = leafHashes[:uint64(len(leafHashes))-action.A]
  1079  
  1080  		case modules.WriteActionSwap:
  1081  			i, j := indexMap[action.A], indexMap[action.B]
  1082  			leafHashes[i], leafHashes[j] = leafHashes[j], leafHashes[i]
  1083  
  1084  		case modules.WriteActionUpdate:
  1085  			panic("update not supported")
  1086  		}
  1087  	}
  1088  	return leafHashes
  1089  }