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

     1  package proto
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net"
     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  )
    15  
    16  // extendDeadline is a helper function for extending the connection timeout.
    17  func extendDeadline(conn net.Conn, d time.Duration) { _ = conn.SetDeadline(time.Now().Add(d)) }
    18  
    19  // startRevision is run at the beginning of each revision iteration. It reads
    20  // the host's settings confirms that the values are acceptable, and writes an acceptance.
    21  func startRevision(conn net.Conn, host modules.HostDBEntry) error {
    22  	// verify the host's settings and confirm its identity
    23  	recvSettings, err := verifySettings(conn, host)
    24  	if err != nil {
    25  		return err
    26  	} else if !recvSettings.AcceptingContracts {
    27  		// no need to reject; host will already have disconnected at this point
    28  		return errors.New("host is not accepting contracts")
    29  	}
    30  	return modules.WriteNegotiationAcceptance(conn)
    31  }
    32  
    33  // startDownload is run at the beginning of each download iteration. It reads
    34  // the host's settings confirms that the values are acceptable, and writes an acceptance.
    35  func startDownload(conn net.Conn, host modules.HostDBEntry) error {
    36  	// verify the host's settings and confirm its identity
    37  	_, err := verifySettings(conn, host)
    38  	if err != nil {
    39  		return err
    40  	}
    41  	return modules.WriteNegotiationAcceptance(conn)
    42  }
    43  
    44  // verifySettings reads a signed HostSettings object from conn, validates the
    45  // signature, and checks for discrepancies between the known settings and the
    46  // received settings. If there is a discrepancy, the hostDB is notified. The
    47  // received settings are returned.
    48  func verifySettings(conn net.Conn, host modules.HostDBEntry) (modules.HostDBEntry, error) {
    49  	// convert host key (types.SiaPublicKey) to a crypto.PublicKey
    50  	if host.PublicKey.Algorithm != types.SignatureEd25519 || len(host.PublicKey.Key) != crypto.PublicKeySize {
    51  		build.Critical("hostdb did not filter out host with wrong signature algorithm:", host.PublicKey.Algorithm)
    52  		return modules.HostDBEntry{}, errors.New("host used unsupported signature algorithm")
    53  	}
    54  	var pk crypto.PublicKey
    55  	copy(pk[:], host.PublicKey.Key)
    56  
    57  	// read signed host settings
    58  	var recvSettings modules.HostExternalSettings
    59  	if err := crypto.ReadSignedObject(conn, &recvSettings, modules.NegotiateMaxHostExternalSettingsLen, pk); err != nil {
    60  		return modules.HostDBEntry{}, errors.New("couldn't read host's settings: " + err.Error())
    61  	}
    62  	// TODO: check recvSettings against host.HostExternalSettings. If there is
    63  	// a discrepancy, write the error to conn.
    64  	if recvSettings.NetAddress != host.NetAddress {
    65  		// for now, just overwrite the NetAddress, since we know that
    66  		// host.NetAddress works (it was the one we dialed to get conn)
    67  		recvSettings.NetAddress = host.NetAddress
    68  	}
    69  	host.HostExternalSettings = recvSettings
    70  	return host, nil
    71  }
    72  
    73  // verifyRecentRevision confirms that the host and contractor agree upon the current
    74  // state of the contract being revisde.
    75  func verifyRecentRevision(conn net.Conn, contract modules.RenterContract) error {
    76  	// send contract ID
    77  	if err := encoding.WriteObject(conn, contract.ID); err != nil {
    78  		return errors.New("couldn't send contract ID: " + err.Error())
    79  	}
    80  	// read challenge
    81  	var challenge crypto.Hash
    82  	if err := encoding.ReadObject(conn, &challenge, 32); err != nil {
    83  		return errors.New("couldn't read challenge: " + err.Error())
    84  	}
    85  	// sign and return
    86  	sig, err := crypto.SignHash(challenge, contract.SecretKey)
    87  	if err != nil {
    88  		return err
    89  	} else if err := encoding.WriteObject(conn, sig); err != nil {
    90  		return errors.New("couldn't send challenge response: " + err.Error())
    91  	}
    92  	// read acceptance
    93  	if err := modules.ReadNegotiationAcceptance(conn); err != nil {
    94  		return errors.New("host did not accept revision request: " + err.Error())
    95  	}
    96  	// read last revision and signatures
    97  	var lastRevision types.FileContractRevision
    98  	var hostSignatures []types.TransactionSignature
    99  	if err := encoding.ReadObject(conn, &lastRevision, 2048); err != nil {
   100  		return errors.New("couldn't read last revision: " + err.Error())
   101  	}
   102  	if err := encoding.ReadObject(conn, &hostSignatures, 2048); err != nil {
   103  		return errors.New("couldn't read host signatures: " + err.Error())
   104  	}
   105  	// check that revision number matches; if it does, do a more thorough
   106  	// check by comparing unlock hashes.
   107  	if lastRevision.NewRevisionNumber != contract.LastRevision.NewRevisionNumber {
   108  		return fmt.Errorf("our revision number (%v) does not match the host's (%v)", contract.LastRevision.NewRevisionNumber, lastRevision.NewRevisionNumber)
   109  	} else if lastRevision.UnlockConditions.UnlockHash() != contract.LastRevision.UnlockConditions.UnlockHash() {
   110  		return errors.New("unlock conditions do not match")
   111  	}
   112  	// NOTE: we can fake the blockheight here because it doesn't affect
   113  	// verification; it just needs to be above the fork height and below the
   114  	// contract expiration (which was checked earlier).
   115  	return modules.VerifyFileContractRevisionTransactionSignatures(lastRevision, hostSignatures, contract.FileContract.WindowStart-1)
   116  }
   117  
   118  // negotiateRevision sends a revision and actions to the host for approval,
   119  // completing one iteration of the revision loop.
   120  func negotiateRevision(conn net.Conn, rev types.FileContractRevision, secretKey crypto.SecretKey) (types.Transaction, error) {
   121  	// create transaction containing the revision
   122  	signedTxn := types.Transaction{
   123  		FileContractRevisions: []types.FileContractRevision{rev},
   124  		TransactionSignatures: []types.TransactionSignature{{
   125  			ParentID:       crypto.Hash(rev.ParentID),
   126  			CoveredFields:  types.CoveredFields{FileContractRevisions: []uint64{0}},
   127  			PublicKeyIndex: 0, // renter key is always first -- see formContract
   128  		}},
   129  	}
   130  	// sign the transaction
   131  	encodedSig, _ := crypto.SignHash(signedTxn.SigHash(0), secretKey) // no error possible
   132  	signedTxn.TransactionSignatures[0].Signature = encodedSig[:]
   133  
   134  	// send the revision
   135  	if err := encoding.WriteObject(conn, rev); err != nil {
   136  		return types.Transaction{}, errors.New("couldn't send revision: " + err.Error())
   137  	}
   138  	// read acceptance
   139  	if err := modules.ReadNegotiationAcceptance(conn); err != nil {
   140  		return types.Transaction{}, errors.New("host did not accept revision: " + err.Error())
   141  	}
   142  
   143  	// send the new transaction signature
   144  	if err := encoding.WriteObject(conn, signedTxn.TransactionSignatures[0]); err != nil {
   145  		return types.Transaction{}, errors.New("couldn't send transaction signature: " + err.Error())
   146  	}
   147  	// read the host's acceptance and transaction signature
   148  	if err := modules.ReadNegotiationAcceptance(conn); err != nil {
   149  		return types.Transaction{}, errors.New("host did not accept transaction signature: " + err.Error())
   150  	}
   151  	var hostSig types.TransactionSignature
   152  	if err := encoding.ReadObject(conn, &hostSig, 16e3); err != nil {
   153  		return types.Transaction{}, errors.New("couldn't read host's signature: " + err.Error())
   154  	}
   155  
   156  	// add the signature to the transaction and verify it
   157  	// NOTE: we can fake the blockheight here because it doesn't affect
   158  	// verification; it just needs to be above the fork height and below the
   159  	// contract expiration (which was checked earlier).
   160  	verificationHeight := rev.NewWindowStart - 1
   161  	signedTxn.TransactionSignatures = append(signedTxn.TransactionSignatures, hostSig)
   162  	if err := signedTxn.StandaloneValid(verificationHeight); err != nil {
   163  		return types.Transaction{}, err
   164  	}
   165  	return signedTxn, nil
   166  }
   167  
   168  // newRevision creates a copy of current with its revision number incremented,
   169  // and with cost transferred from the renter to the host.
   170  func newRevision(current types.FileContractRevision, cost types.Currency) types.FileContractRevision {
   171  	rev := current
   172  
   173  	// need to manually copy slice memory
   174  	rev.NewValidProofOutputs = make([]types.SiacoinOutput, 2)
   175  	rev.NewMissedProofOutputs = make([]types.SiacoinOutput, 3)
   176  	copy(rev.NewValidProofOutputs, current.NewValidProofOutputs)
   177  	copy(rev.NewMissedProofOutputs, current.NewMissedProofOutputs)
   178  
   179  	// move valid payout from renter to host
   180  	rev.NewValidProofOutputs[0].Value = current.NewValidProofOutputs[0].Value.Sub(cost)
   181  	rev.NewValidProofOutputs[1].Value = current.NewValidProofOutputs[1].Value.Add(cost)
   182  
   183  	// move missed payout from renter to void
   184  	rev.NewMissedProofOutputs[0].Value = current.NewMissedProofOutputs[0].Value.Sub(cost)
   185  	rev.NewMissedProofOutputs[2].Value = current.NewMissedProofOutputs[2].Value.Add(cost)
   186  
   187  	// increment revision number
   188  	rev.NewRevisionNumber++
   189  
   190  	return rev
   191  }
   192  
   193  // newDownloadRevision revises the current revision to cover the cost of
   194  // downloading data.
   195  func newDownloadRevision(current types.FileContractRevision, downloadCost types.Currency) types.FileContractRevision {
   196  	return newRevision(current, downloadCost)
   197  }
   198  
   199  // newUploadRevision revises the current revision to cover the cost of
   200  // uploading a sector.
   201  func newUploadRevision(current types.FileContractRevision, merkleRoot crypto.Hash, price, collateral types.Currency) types.FileContractRevision {
   202  	rev := newRevision(current, price)
   203  
   204  	// move collateral from host to void
   205  	rev.NewMissedProofOutputs[1].Value = rev.NewMissedProofOutputs[1].Value.Sub(collateral)
   206  	rev.NewMissedProofOutputs[2].Value = rev.NewMissedProofOutputs[2].Value.Add(collateral)
   207  
   208  	// set new filesize and Merkle root
   209  	rev.NewFileSize += modules.SectorSize
   210  	rev.NewFileMerkleRoot = merkleRoot
   211  	return rev
   212  }
   213  
   214  // newDeleteRevision revises the current revision to cover the cost of
   215  // deleting a sector.
   216  func newDeleteRevision(current types.FileContractRevision, merkleRoot crypto.Hash) types.FileContractRevision {
   217  	rev := newRevision(current, types.ZeroCurrency)
   218  	rev.NewFileSize -= modules.SectorSize
   219  	rev.NewFileMerkleRoot = merkleRoot
   220  	return rev
   221  }
   222  
   223  // newModifyRevision revises the current revision to cover the cost of
   224  // modifying a sector.
   225  func newModifyRevision(current types.FileContractRevision, merkleRoot crypto.Hash, uploadCost types.Currency) types.FileContractRevision {
   226  	rev := newRevision(current, uploadCost)
   227  	rev.NewFileMerkleRoot = merkleRoot
   228  	return rev
   229  }