// newHostUploader initiates the contract revision process with a host, and // returns a hostUploader, which satisfies the Uploader interface. func (hdb *HostDB) newHostUploader(hc hostContract) (*hostUploader, error) { hdb.mu.RLock() settings, ok := hdb.allHosts[hc.IP] // or activeHosts? hdb.mu.RUnlock() if !ok { return nil, errors.New("no record of that host") } // TODO: check for excessive price again? // initiate revision loop conn, err := net.DialTimeout("tcp", string(hc.IP), 15*time.Second) if err != nil { return nil, err } if err := encoding.WriteObject(conn, modules.RPCRevise); err != nil { return nil, err } if err := encoding.WriteObject(conn, hc.ID); err != nil { return nil, err } // TODO: some sort of acceptance would be good here, so that we know the // uploader will actually work. Maybe send the Merkle root? hu := &hostUploader{ contract: hc, price: settings.Price, tree: crypto.NewTree(), conn: conn, hdb: hdb, } return hu, nil }
// downloadPiece attempts to retrieve a file piece from a host. func (d *Download) downloadPiece(piece filePiece) error { conn, err := net.DialTimeout("tcp", string(piece.HostIP), 10e9) if err != nil { return err } defer conn.Close() err = encoding.WriteObject(conn, [8]byte{'R', 'e', 't', 'r', 'i', 'e', 'v', 'e'}) if err != nil { return err } // Send the ID of the contract for the file piece we're requesting. if err := encoding.WriteObject(conn, piece.ContractID); err != nil { return err } // Simultaneously download, decrypt, and calculate the Merkle root of the file. tee := io.TeeReader( // Use a LimitedReader to ensure we don't read indefinitely. io.LimitReader(conn, int64(piece.Contract.FileSize)), // Write the decrypted bytes to the file. piece.EncryptionKey.NewWriter(d), ) merkleRoot, err := crypto.ReaderMerkleRoot(tee) if err != nil { return err } if merkleRoot != piece.Contract.FileMerkleRoot { return errors.New("host provided a file that's invalid") } return nil }
func (r *Renter) newHostUploader(settings modules.HostSettings, filesize uint64, duration types.BlockHeight, masterKey crypto.TwofishKey) (*hostUploader, error) { hu := &hostUploader{ settings: settings, masterKey: masterKey, tree: crypto.NewTree(), renter: r, } // TODO: maybe do this later? err := hu.negotiateContract(filesize, duration) if err != nil { return nil, err } // initiate the revision loop hu.conn, err = net.DialTimeout("tcp", string(hu.settings.IPAddress), 15*time.Second) if err != nil { return nil, err } if err := encoding.WriteObject(hu.conn, modules.RPCRevise); err != nil { return nil, err } if err := encoding.WriteObject(hu.conn, hu.contract.ID); err != nil { return nil, err } return hu, nil }
// newHostFetcher creates a new hostFetcher by connecting to a host. // TODO: We may not wind up requesting data from this, which means we will // connect and then disconnect without making any actual requests (but holding // the connection open the entire time). This is wasteful of host resources. // Consider only opening the connection after the first request has been made. func newHostFetcher(fc fileContract, pieceSize uint64, masterKey crypto.TwofishKey) (*hostFetcher, error) { conn, err := net.DialTimeout("tcp", string(fc.IP), 15*time.Second) if err != nil { return nil, err } conn.SetDeadline(time.Now().Add(15 * time.Second)) defer conn.SetDeadline(time.Time{}) // send RPC err = encoding.WriteObject(conn, modules.RPCDownload) if err != nil { return nil, err } // send contract ID err = encoding.WriteObject(conn, fc.ID) if err != nil { return nil, err } // make piece map pieceMap := make(map[uint64][]pieceData) for _, p := range fc.Pieces { pieceMap[p.Chunk] = append(pieceMap[p.Chunk], p) } return &hostFetcher{ conn: conn, pieceMap: pieceMap, pieceSize: pieceSize + crypto.TwofishOverhead, masterKey: masterKey, }, nil }
// acceptConn adds a connecting node as a peer. func (g *Gateway) acceptConn(conn net.Conn) { addr := modules.NetAddress(conn.RemoteAddr().String()) g.log.Printf("INFO: %v wants to connect", addr) // read version var remoteVersion string if err := encoding.ReadObject(conn, &remoteVersion, maxAddrLength); err != nil { conn.Close() g.log.Printf("INFO: %v wanted to connect, but we could not read their version: %v", addr, err) return } // check that version is acceptable // NOTE: this version must be bumped whenever the gateway or consensus // breaks compatibility. if build.VersionCmp(remoteVersion, "0.3.3") < 0 { encoding.WriteObject(conn, "reject") conn.Close() g.log.Printf("INFO: %v wanted to connect, but their version (%v) was unacceptable", addr, remoteVersion) return } // respond with our version if err := encoding.WriteObject(conn, build.Version); err != nil { conn.Close() g.log.Printf("INFO: could not write version ack to %v: %v", addr, err) return } // If we are already fully connected, kick out an old peer to make room // for the new one. Importantly, prioritize kicking a peer with the same // IP as the connecting peer. This protects against Sybil attacks. id := g.mu.Lock() if len(g.peers) >= fullyConnectedThreshold { // first choose a random peer, preferably inbound. If have only // outbound peers, we'll wind up kicking an outbound peer; but // subsequent inbound connections will kick each other instead of // continuing to replace outbound peers. kick, err := g.randomInboundPeer() if err != nil { kick, _ = g.randomPeer() } // if another peer shares this IP, choose that one instead for p := range g.peers { if p.Host() == addr.Host() { kick = p break } } g.peers[kick].sess.Close() delete(g.peers, kick) g.log.Printf("INFO: disconnected from %v to make room for %v", kick, addr) } // add the peer g.addPeer(&peer{addr: addr, sess: muxado.Server(conn), inbound: true}) g.mu.Unlock(id) g.log.Printf("INFO: accepted connection from new peer %v (v%v)", addr, remoteVersion) }
func TestThreadedHandleConn(t *testing.T) { g1 := newTestingGateway("TestThreadedHandleConn1", t) defer g1.Close() g2 := newTestingGateway("TestThreadedHandleConn2", t) defer g2.Close() err := g1.Connect(g2.Address()) if err != nil { t.Fatal("failed to connect:", err) } g2.RegisterRPC("Foo", func(conn modules.PeerConn) error { var i uint64 err := encoding.ReadObject(conn, &i, 8) if err != nil { return err } else if i == 0xdeadbeef { return encoding.WriteObject(conn, "foo") } else { return encoding.WriteObject(conn, "bar") } }) // custom rpc fn (doesn't automatically write rpcID) rpcFn := func(fn func(modules.PeerConn) error) error { conn, err := g1.peers[g2.Address()].open() if err != nil { return err } defer conn.Close() return fn(conn) } // bad rpcID err = rpcFn(func(conn modules.PeerConn) error { return encoding.WriteObject(conn, [3]byte{1, 2, 3}) }) if err != nil { t.Fatal("rpcFn failed:", err) } // unknown rpcID err = rpcFn(func(conn modules.PeerConn) error { return encoding.WriteObject(conn, handlerName("bar")) }) if err != nil { t.Fatal("rpcFn failed:", err) } // valid rpcID err = rpcFn(func(conn modules.PeerConn) error { return encoding.WriteObject(conn, handlerName("Foo")) }) if err != nil { t.Fatal("rpcFn failed:", err) } }
// negotiateRevision sends a revision and actions to the host for approval, // completing one iteration of the revision loop. func negotiateRevision(conn net.Conn, rev types.FileContractRevision, secretKey crypto.SecretKey) (types.Transaction, error) { // create transaction containing the revision signedTxn := types.Transaction{ FileContractRevisions: []types.FileContractRevision{rev}, TransactionSignatures: []types.TransactionSignature{{ ParentID: crypto.Hash(rev.ParentID), CoveredFields: types.CoveredFields{FileContractRevisions: []uint64{0}}, PublicKeyIndex: 0, // renter key is always first -- see formContract }}, } // sign the transaction encodedSig, _ := crypto.SignHash(signedTxn.SigHash(0), secretKey) // no error possible signedTxn.TransactionSignatures[0].Signature = encodedSig[:] // send the revision if err := encoding.WriteObject(conn, rev); err != nil { return types.Transaction{}, errors.New("couldn't send revision: " + err.Error()) } // read acceptance if err := modules.ReadNegotiationAcceptance(conn); err != nil { return types.Transaction{}, errors.New("host did not accept revision: " + err.Error()) } // send the new transaction signature if err := encoding.WriteObject(conn, signedTxn.TransactionSignatures[0]); err != nil { return types.Transaction{}, errors.New("couldn't send transaction signature: " + err.Error()) } // read the host's acceptance and transaction signature // NOTE: if the host sends ErrStopResponse, we should continue processing // the revision, but return the error anyway. responseErr := modules.ReadNegotiationAcceptance(conn) if responseErr != nil && responseErr != modules.ErrStopResponse { return types.Transaction{}, errors.New("host did not accept transaction signature: " + responseErr.Error()) } var hostSig types.TransactionSignature if err := encoding.ReadObject(conn, &hostSig, 16e3); err != nil { return types.Transaction{}, errors.New("couldn't read host's signature: " + err.Error()) } // add the signature to the transaction and verify it // NOTE: we can fake the blockheight here because it doesn't affect // verification; it just needs to be above the fork height and below the // contract expiration (which was checked earlier). verificationHeight := rev.NewWindowStart - 1 signedTxn.TransactionSignatures = append(signedTxn.TransactionSignatures, hostSig) if err := signedTxn.StandaloneValid(verificationHeight); err != nil { return types.Transaction{}, err } // if the host sent ErrStopResponse, return it return signedTxn, responseErr }
// managedRPCRecentRevision sends the most recent known file contract // revision, including signatures, to the renter, for the file contract with // the input id. func (h *Host) managedRPCRecentRevision(conn net.Conn) (types.FileContractID, *storageObligation, error) { // Set the negotiation deadline. conn.SetDeadline(time.Now().Add(modules.NegotiateRecentRevisionTime)) // Receive the file contract id from the renter. var fcid types.FileContractID err := encoding.ReadObject(conn, &fcid, uint64(len(fcid))) if err != nil { return types.FileContractID{}, nil, err } // Send a challenge to the renter to verify that the renter has write // access to the revision being opened. var challenge crypto.Hash _, err = rand.Read(challenge[:]) if err != nil { return types.FileContractID{}, nil, err } err = encoding.WriteObject(conn, challenge) if err != nil { return types.FileContractID{}, nil, err } // Read the signed response from the renter. var challengeResponse crypto.Signature err = encoding.ReadObject(conn, &challengeResponse, uint64(len(challengeResponse))) if err != nil { return types.FileContractID{}, nil, err } // Verify the response. In the process, fetch the related storage // obligation, file contract revision, and transaction signatures. so, recentRevision, revisionSigs, err := h.verifyChallengeResponse(fcid, challenge, challengeResponse) if err != nil { return types.FileContractID{}, nil, modules.WriteNegotiationRejection(conn, err) } // Send the file contract revision and the corresponding signatures to the // renter. err = modules.WriteNegotiationAcceptance(conn) if err != nil { return types.FileContractID{}, nil, err } err = encoding.WriteObject(conn, recentRevision) if err != nil { return types.FileContractID{}, nil, err } err = encoding.WriteObject(conn, revisionSigs) if err != nil { return types.FileContractID{}, nil, err } return fcid, so, nil }
// TestNegotiateRevisionStopResponse tests that when the host sends // StopResponse, the renter continues processing the revision instead of // immediately terminating. func TestNegotiateRevisionStopResponse(t *testing.T) { // simulate a renter-host connection rConn, hConn := net.Pipe() // handle the host's half of the pipe go func() { defer hConn.Close() // read revision encoding.ReadObject(hConn, new(types.FileContractRevision), 1<<22) // write acceptance modules.WriteNegotiationAcceptance(hConn) // read txn signature encoding.ReadObject(hConn, new(types.TransactionSignature), 1<<22) // write StopResponse modules.WriteNegotiationStop(hConn) // write txn signature encoding.WriteObject(hConn, types.TransactionSignature{}) }() // since the host wrote StopResponse, we should proceed to validating the // transaction. This will return a known error because we are supplying an // empty revision. _, err := negotiateRevision(rConn, types.FileContractRevision{}, crypto.SecretKey{}) if err != types.ErrFileContractWindowStartViolation { t.Fatalf("expected %q, got \"%v\"", types.ErrFileContractWindowStartViolation, err) } rConn.Close() // same as above, but send an error instead of StopResponse. The error // should be returned by negotiateRevision immediately (if it is not, we // should expect to see a transaction validation error instead). rConn, hConn = net.Pipe() go func() { defer hConn.Close() encoding.ReadObject(hConn, new(types.FileContractRevision), 1<<22) modules.WriteNegotiationAcceptance(hConn) encoding.ReadObject(hConn, new(types.TransactionSignature), 1<<22) // write a sentinel error modules.WriteNegotiationRejection(hConn, errors.New("sentinel")) encoding.WriteObject(hConn, types.TransactionSignature{}) }() expectedErr := "host did not accept transaction signature: sentinel" _, err = negotiateRevision(rConn, types.FileContractRevision{}, crypto.SecretKey{}) if err == nil || err.Error() != expectedErr { t.Fatalf("expected %q, got \"%v\"", expectedErr, err) } rConn.Close() }
// WriteNegotiationRejection will write a rejection response to w (usually a // net.Conn) and return the input error. If the write fails, the write error // is joined with the input error. func WriteNegotiationRejection(w io.Writer, err error) error { writeErr := encoding.WriteObject(w, err.Error()) if writeErr != nil { return build.JoinErrors([]error{err, writeErr}, "; ") } return err }
// NewEditor initiates the contract revision process with a host, and returns // an Editor. func NewEditor(host modules.HostDBEntry, contract modules.RenterContract, currentHeight types.BlockHeight) (*Editor, error) { // check that contract has enough value to support an upload if len(contract.LastRevision.NewValidProofOutputs) != 2 { return nil, errors.New("invalid contract") } // initiate revision loop conn, err := net.DialTimeout("tcp", string(contract.NetAddress), 15*time.Second) if err != nil { return nil, err } // allot 2 minutes for RPC request + revision exchange extendDeadline(conn, modules.NegotiateRecentRevisionTime) defer extendDeadline(conn, time.Hour) if err := encoding.WriteObject(conn, modules.RPCReviseContract); err != nil { conn.Close() return nil, errors.New("couldn't initiate RPC: " + err.Error()) } if err := verifyRecentRevision(conn, contract); err != nil { conn.Close() // TODO: close gracefully if host has entered revision loop return nil, err } // the host is now ready to accept revisions return &Editor{ host: host, height: currentHeight, contract: contract, conn: conn, }, nil }
// threadedProbeHosts tries to fetch the settings of a host. If successful, the // host is put in the set of active hosts. If unsuccessful, the host id deleted // from the set of active hosts. func (hdb *HostDB) threadedProbeHosts() { defer hdb.threadGroup.Done() for hostEntry := range hdb.scanPool { // Request settings from the queued host entry. // TODO: use dialer.Cancel to shutdown quickly hdb.log.Debugln("Scanning", hostEntry.NetAddress, hostEntry.PublicKey) var settings modules.HostExternalSettings err := func() error { conn, err := hdb.dialer.DialTimeout(hostEntry.NetAddress, hostRequestTimeout) if err != nil { return err } defer conn.Close() err = encoding.WriteObject(conn, modules.RPCSettings) if err != nil { return err } var pubkey crypto.PublicKey copy(pubkey[:], hostEntry.PublicKey.Key) return crypto.ReadSignedObject(conn, &settings, maxSettingsLen, pubkey) }() if err != nil { hdb.log.Debugln("Scanning", hostEntry.NetAddress, hostEntry.PublicKey, "failed", err) } else { hdb.log.Debugln("Scanning", hostEntry.NetAddress, hostEntry.PublicKey, "succeeded") } // Update the host tree to have a new entry. hdb.managedUpdateEntry(hostEntry, settings, err) } }
// connectPortHandshake performs the port handshake and should be called on the // side initiating the connection request. This shares our port with the peer // so they can connect to us in the future. func connectPortHandshake(conn net.Conn, port string) error { err := encoding.WriteObject(conn, port) if err != nil { return errors.New("could not write port #: " + err.Error()) } return nil }
// RPC calls an RPC on the given address. RPC cannot be called on an address // that the Gateway is not connected to. func (g *Gateway) RPC(addr modules.NetAddress, name string, fn modules.RPCFunc) error { id := g.mu.RLock() peer, ok := g.peers[addr] g.mu.RUnlock(id) if !ok { return errors.New("can't call RPC on unconnected peer " + string(addr)) } conn, err := peer.open() if err != nil { return err } defer conn.Close() // write header if err := encoding.WriteObject(conn, handlerName(name)); err != nil { return err } // call fn err = fn(conn) if err != nil { g.log.Printf("WARN: calling RPC \"%v\" on peer %v returned error: %v", name, addr, err) } return err }
// RPC calls an RPC on the given address. RPC cannot be called on an address // that the Gateway is not connected to. func (g *Gateway) RPC(addr modules.NetAddress, name string, fn modules.RPCFunc) error { if err := g.threads.Add(); err != nil { return err } defer g.threads.Done() g.mu.RLock() peer, ok := g.peers[addr] g.mu.RUnlock() if !ok { return errors.New("can't call RPC on unconnected peer " + string(addr)) } conn, err := peer.open() if err != nil { return err } defer conn.Close() // write header if err := encoding.WriteObject(conn, handlerName(name)); err != nil { return err } // call fn return fn(conn) }
// fetch downloads the piece specified by p. func (hf *hostFetcher) fetch(p pieceData) ([]byte, error) { hf.conn.SetDeadline(time.Now().Add(2 * time.Minute)) // sufficient to transfer 4 MB over 250 kbps defer hf.conn.SetDeadline(time.Time{}) // request piece err := encoding.WriteObject(hf.conn, modules.DownloadRequest{ Offset: p.Offset, Length: hf.pieceSize, }) if err != nil { return nil, err } // download piece data := make([]byte, hf.pieceSize) _, err = io.ReadFull(hf.conn, data) if err != nil { return nil, err } // generate decryption key key := deriveKey(hf.masterKey, p.Chunk, p.Piece) // decrypt and return return key.DecryptBytes(data) }
// sendAddress is the calling end of the RelayNode RPC. func (g *Gateway) sendAddress(conn modules.PeerConn) error { // don't send if we aren't connectible if g.Address().Host() == "::1" { return errors.New("can't send address without knowing external IP") } return encoding.WriteObject(conn, g.Address()) }
// NewDownloader initiates the download request loop with a host, and returns a // Downloader. func NewDownloader(host modules.HostDBEntry, contract modules.RenterContract) (*Downloader, error) { // check that contract has enough value to support a download if len(contract.LastRevision.NewValidProofOutputs) != 2 { return nil, errors.New("invalid contract") } sectorPrice := host.DownloadBandwidthPrice.Mul64(modules.SectorSize) if contract.RenterFunds().Cmp(sectorPrice) < 0 { return nil, errors.New("contract has insufficient funds to support download") } // initiate download loop conn, err := net.DialTimeout("tcp", string(contract.NetAddress), 15*time.Second) if err != nil { return nil, err } // allot 2 minutes for RPC request + revision exchange extendDeadline(conn, modules.NegotiateRecentRevisionTime) defer extendDeadline(conn, time.Hour) if err := encoding.WriteObject(conn, modules.RPCDownload); err != nil { conn.Close() return nil, errors.New("couldn't initiate RPC: " + err.Error()) } if err := verifyRecentRevision(conn, contract); err != nil { conn.Close() // TODO: close gracefully if host has entered revision loop return nil, err } // the host is now ready to accept revisions return &Downloader{ contract: contract, host: host, conn: conn, }, nil }
// rpcSendBlk is an RPC that sends the requested block to the requesting peer. func (cs *ConsensusSet) rpcSendBlk(conn modules.PeerConn) error { err := cs.tg.Add() if err != nil { return err } defer cs.tg.Done() // Decode the block id from the connection. var id types.BlockID err = encoding.ReadObject(conn, &id, crypto.HashSize) if err != nil { return err } // Lookup the corresponding block. var b types.Block cs.mu.RLock() err = cs.db.View(func(tx *bolt.Tx) error { pb, err := getBlockMap(tx, id) if err != nil { return err } b = pb.Block return nil }) cs.mu.RUnlock() if err != nil { return err } // Encode and send the block to the caller. err = encoding.WriteObject(conn, b) if err != nil { return err } return nil }
func (hu *hostUploader) Close() error { // send an empty revision to indicate that we are finished encoding.WriteObject(hu.conn, types.Transaction{}) hu.conn.Close() // submit the most recent revision to the blockchain hu.renter.tpool.AcceptTransactionSet([]types.Transaction{hu.lastTxn}) return nil }
// newHostUploader negotiates an initial file contract with the specified host // and returns a hostUploader, which satisfies the uploader interface. func (r *Renter) newHostUploader(settings modules.HostSettings, filesize uint64, duration types.BlockHeight, masterKey crypto.TwofishKey) (*hostUploader, error) { // reject hosts that are too expensive if settings.Price.Cmp(maxPrice) > 0 { return nil, errTooExpensive } hu := &hostUploader{ settings: settings, masterKey: masterKey, tree: crypto.NewTree(), renter: r, } // get an address to use for negotiation // TODO: use more than one shared address if r.cachedAddress == (types.UnlockHash{}) { uc, err := r.wallet.NextAddress() if err != nil { return nil, err } r.cachedAddress = uc.UnlockHash() } // TODO: check for existing contract? err := hu.negotiateContract(filesize, duration, r.cachedAddress) if err != nil { return nil, err } // if negotiation was sucessful, clear the cached address r.cachedAddress = types.UnlockHash{} // initiate the revision loop hu.conn, err = net.DialTimeout("tcp", string(hu.settings.IPAddress), 15*time.Second) if err != nil { return nil, err } if err := encoding.WriteObject(hu.conn, modules.RPCRevise); err != nil { return nil, err } if err := encoding.WriteObject(hu.conn, hu.contract.ID); err != nil { return nil, err } return hu, nil }
// acceptConnVersionHandshake performs the version handshake and should be // called on the side accepting a connection request. The remote version is // only returned if err == nil. func acceptConnVersionHandshake(conn net.Conn, version string) (remoteVersion string, err error) { // Read remote version. if err := encoding.ReadObject(conn, &remoteVersion, build.MaxEncodedVersionLength); err != nil { return "", fmt.Errorf("failed to read remote version: %v", err) } // Check that their version is acceptable. if err := acceptableVersion(remoteVersion); err != nil { if err := encoding.WriteObject(conn, "reject"); err != nil { return "", fmt.Errorf("failed to write reject: %v", err) } return "", err } // Send our version. if err := encoding.WriteObject(conn, version); err != nil { return "", fmt.Errorf("failed to write version: %v", err) } return remoteVersion, nil }
// Connect establishes a persistent connection to a peer, and adds it to the // Gateway's peer list. func (g *Gateway) Connect(addr modules.NetAddress) error { if addr == g.Address() { return errors.New("can't connect to our own address") } id := g.mu.RLock() _, exists := g.peers[addr] g.mu.RUnlock(id) if exists { return errors.New("peer already added") } conn, err := net.DialTimeout("tcp", string(addr), dialTimeout) if err != nil { return err } // send our version if err := encoding.WriteObject(conn, "0.3.3"); err != nil { return err } // read version ack var remoteVersion string if err := encoding.ReadObject(conn, &remoteVersion, maxAddrLength); err != nil { return err } else if remoteVersion == "reject" { return errors.New("peer rejected connection") } // decide whether to accept this version if build.VersionCmp(remoteVersion, "0.3.3") < 0 { conn.Close() return errors.New("unacceptable version: " + remoteVersion) } g.log.Println("INFO: connected to new peer", addr) id = g.mu.Lock() g.addPeer(&peer{addr: addr, sess: muxado.Client(conn), inbound: false}) g.mu.Unlock(id) // call initRPCs id = g.mu.RLock() var wg sync.WaitGroup wg.Add(len(g.initRPCs)) for name, fn := range g.initRPCs { go func(name string, fn modules.RPCFunc) { // errors here are non-fatal g.RPC(addr, name, fn) wg.Done() }(name, fn) } g.mu.RUnlock(id) wg.Wait() return nil }
// verifyRecentRevision confirms that the host and contractor agree upon the current // state of the contract being revised. func verifyRecentRevision(conn net.Conn, contract modules.RenterContract) error { // send contract ID if err := encoding.WriteObject(conn, contract.ID); err != nil { return errors.New("couldn't send contract ID: " + err.Error()) } // read challenge var challenge crypto.Hash if err := encoding.ReadObject(conn, &challenge, 32); err != nil { return errors.New("couldn't read challenge: " + err.Error()) } // sign and return sig, err := crypto.SignHash(challenge, contract.SecretKey) if err != nil { return err } else if err := encoding.WriteObject(conn, sig); err != nil { return errors.New("couldn't send challenge response: " + err.Error()) } // read acceptance if err := modules.ReadNegotiationAcceptance(conn); err != nil { return errors.New("host did not accept revision request: " + err.Error()) } // read last revision and signatures var lastRevision types.FileContractRevision var hostSignatures []types.TransactionSignature if err := encoding.ReadObject(conn, &lastRevision, 2048); err != nil { return errors.New("couldn't read last revision: " + err.Error()) } if err := encoding.ReadObject(conn, &hostSignatures, 2048); err != nil { return errors.New("couldn't read host signatures: " + err.Error()) } // Check that the unlock hashes match; if they do not, something is // seriously wrong. Otherwise, check that the revision numbers match. if lastRevision.UnlockConditions.UnlockHash() != contract.LastRevision.UnlockConditions.UnlockHash() { return errors.New("unlock conditions do not match") } else if lastRevision.NewRevisionNumber != contract.LastRevision.NewRevisionNumber { return &recentRevisionError{contract.LastRevision.NewRevisionNumber, lastRevision.NewRevisionNumber} } // NOTE: we can fake the blockheight here because it doesn't affect // verification; it just needs to be above the fork height and below the // contract expiration (which was checked earlier). return modules.VerifyFileContractRevisionTransactionSignatures(lastRevision, hostSignatures, contract.FileContract.WindowStart-1) }
// Close cleanly ends the revision process with the host, closes the // connection, and submits the last revision to the transaction pool. func (hu *hostUploader) Close() error { // send an empty revision to indicate that we are finished encoding.WriteObject(hu.conn, types.Transaction{}) hu.conn.Close() // submit the most recent revision to the blockchain err := hu.hdb.tpool.AcceptTransactionSet([]types.Transaction{hu.contract.LastRevisionTxn}) if err != nil && err != modules.ErrDuplicateTransactionSet { hu.hdb.log.Println("WARN: transaction pool rejected revision transaction:", err) } return err }
func (hu *hostUploader) Close() error { // send an empty revision to indicate that we are finished encoding.WriteObject(hu.conn, types.Transaction{}) hu.conn.Close() // submit the most recent revision to the blockchain err := hu.renter.tpool.AcceptTransactionSet([]types.Transaction{hu.lastTxn}) if err != nil { hu.renter.log.Println("Could not submit final contract revision:", err) } return err }
// receiveBlocks is the calling end of the SendBlocks RPC. func (s *ConsensusSet) receiveBlocks(conn modules.PeerConn) error { // get blockIDs to send lockID := s.mu.RLock() if !s.db.open { s.mu.RUnlock(lockID) return errors.New("database not open") } history := s.blockHistory() s.mu.RUnlock(lockID) if err := encoding.WriteObject(conn, history); err != nil { return err } // loop until no more blocks are available moreAvailable := true for moreAvailable { var newBlocks []types.Block if err := encoding.ReadObject(conn, &newBlocks, MaxCatchUpBlocks*types.BlockSizeLimit); err != nil { return err } if err := encoding.ReadObject(conn, &moreAvailable, 1); err != nil { return err } // integrate received blocks. for _, block := range newBlocks { // Blocks received during synchronize aren't trusted; activate full // verification. lockID := s.mu.Lock() if !s.db.open { s.mu.Unlock(lockID) return errors.New("database not open") } acceptErr := s.acceptBlock(block) s.mu.Unlock(lockID) // ErrNonExtendingBlock must be ignored until headers-first block // sharing is implemented. if acceptErr == modules.ErrNonExtendingBlock { acceptErr = nil } if acceptErr != nil { return acceptErr } // Yield the processor to give other processes time to grab a lock. // The Lock/Unlock cycle in this loop is very tight, and has been // known to prevent interrupts from getting lock access quickly. runtime.Gosched() } } return nil }
// shareNodes is the receiving end of the ShareNodes RPC. It writes up to 10 // randomly selected nodes to the caller. func (g *Gateway) shareNodes(conn modules.PeerConn) error { id := g.mu.RLock() var nodes []modules.NetAddress for node := range g.nodes { if len(nodes) == maxSharedNodes { break } nodes = append(nodes, node) } g.mu.RUnlock(id) return encoding.WriteObject(conn, nodes) }
// threadedNodeManager tries to keep the Gateway's node list healthy. As long // as the Gateway has fewer than minNodeListSize nodes, it asks a random peer // for more nodes. It also continually pings nodes in order to establish their // connectivity. Unresponsive nodes are aggressively removed. func (g *Gateway) threadedNodeManager() { for { select { case <-time.After(5 * time.Second): case <-g.closeChan: return } id := g.mu.RLock() numNodes := len(g.nodes) peer, err := g.randomPeer() g.mu.RUnlock(id) if err != nil { // can't do much until we have peers continue } if numNodes < minNodeListLen { g.RPC(peer, "ShareNodes", g.requestNodes) } // find an untested node to check id = g.mu.RLock() node, err := g.randomNode() g.mu.RUnlock(id) if err != nil { continue } // try to connect conn, err := net.DialTimeout("tcp", string(node), dialTimeout) if err != nil { id = g.mu.Lock() g.removeNode(node) g.save() g.mu.Unlock(id) continue } // if connection succeeds, supply an unacceptable version to ensure // they won't try to add us as a peer encoding.WriteObject(conn, "0.0.0") conn.Close() // sleep for an extra 10 minutes after success; we don't want to spam // connectable nodes select { case <-time.After(10 * time.Minute): case <-g.closeChan: return } } }
// negotiateRevision sends the revision and new piece data to the host. func negotiateRevision(conn net.Conn, rev types.FileContractRevision, piece []byte, secretKey crypto.SecretKey) (types.Transaction, error) { conn.SetDeadline(time.Now().Add(5 * time.Minute)) // sufficient to transfer 4 MB over 100 kbps defer conn.SetDeadline(time.Time{}) // reset timeout after each revision // create transaction containing the revision signedTxn := types.Transaction{ FileContractRevisions: []types.FileContractRevision{rev}, TransactionSignatures: []types.TransactionSignature{{ ParentID: crypto.Hash(rev.ParentID), CoveredFields: types.CoveredFields{FileContractRevisions: []uint64{0}}, PublicKeyIndex: 0, // renter key is always first -- see negotiateContract }}, } // sign the transaction encodedSig, _ := crypto.SignHash(signedTxn.SigHash(0), secretKey) // no error possible signedTxn.TransactionSignatures[0].Signature = encodedSig[:] // send the transaction if err := encoding.WriteObject(conn, signedTxn); err != nil { return types.Transaction{}, errors.New("couldn't send revision transaction: " + err.Error()) } // host sends acceptance var response string if err := encoding.ReadObject(conn, &response, 128); err != nil { return types.Transaction{}, errors.New("couldn't read host acceptance: " + err.Error()) } if response != modules.AcceptResponse { return types.Transaction{}, errors.New("host rejected revision: " + response) } // transfer piece if _, err := conn.Write(piece); err != nil { return types.Transaction{}, errors.New("couldn't transfer piece: " + err.Error()) } // read txn signed by host var signedHostTxn types.Transaction if err := encoding.ReadObject(conn, &signedHostTxn, types.BlockSizeLimit); err != nil { return types.Transaction{}, errors.New("couldn't read signed revision transaction: " + err.Error()) } if signedHostTxn.ID() != signedTxn.ID() { return types.Transaction{}, errors.New("host sent bad signed transaction") } return signedHostTxn, nil }