github.com/johnathanhowell/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 }