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