Exemple #1
0
// Editor initiates the contract revision process with a host, and returns
// an Editor.
func (c *Contractor) Editor(contract modules.RenterContract) (Editor, error) {
	c.mu.RLock()
	height := c.blockHeight
	c.mu.RUnlock()
	if height > contract.EndHeight() {
		return nil, errors.New("contract has already ended")
	}
	host, ok := c.hdb.Host(contract.NetAddress)
	if !ok {
		return nil, errors.New("no record of that host")
	}
	if host.StoragePrice.Cmp(maxStoragePrice) > 0 {
		return nil, errTooExpensive
	}
	// cap host.Collateral on new hosts
	if build.VersionCmp(host.Version, "0.6.0") > 0 {
		if host.Collateral.Cmp(maxUploadCollateral) > 0 {
			host.Collateral = maxUploadCollateral
		}
	}

	// create editor
	e, err := proto.NewEditor(host, contract, height)
	if proto.IsRevisionMismatch(err) {
		// try again with the cached revision
		c.mu.RLock()
		cached, ok := c.cachedRevisions[contract.ID]
		c.mu.RUnlock()
		if !ok {
			// nothing we can do; return original error
			return nil, err
		}
		c.log.Printf("host %v has different revision for %v; retrying with cached revision", contract.NetAddress, contract.ID)
		contract.LastRevision = cached.revision
		contract.MerkleRoots = cached.merkleRoots
		e, err = proto.NewEditor(host, contract, height)
	}
	if err != nil {
		return nil, err
	}
	// supply a SaveFn that saves the revision to the contractor's persist
	// (the existing revision will be overwritten when SaveFn is called)
	e.SaveFn = c.saveRevision(contract.ID)

	return &hostEditor{
		editor:     e,
		contract:   contract,
		contractor: c,
	}, nil
}
Exemple #2
0
// Downloader initiates the download request loop with a host, and returns a
// Downloader.
func (c *Contractor) Downloader(contract modules.RenterContract) (Downloader, error) {
	c.mu.RLock()
	height := c.blockHeight
	c.mu.RUnlock()
	if height > contract.EndHeight() {
		return nil, errors.New("contract has already ended")
	}
	host, ok := c.hdb.Host(contract.NetAddress)
	if !ok {
		return nil, errors.New("no record of that host")
	}
	if host.DownloadBandwidthPrice.Cmp(maxDownloadPrice) > 0 {
		return nil, errTooExpensive
	}

	// create downloader
	d, err := proto.NewDownloader(host, contract)
	if proto.IsRevisionMismatch(err) {
		// try again with the cached revision
		c.mu.RLock()
		cached, ok := c.cachedRevisions[contract.ID]
		c.mu.RUnlock()
		if !ok {
			// nothing we can do; return original error
			return nil, err
		}
		c.log.Printf("host %v has different revision for %v; retrying with cached revision", contract.NetAddress, contract.ID)
		contract.LastRevision = cached.revision
		d, err = proto.NewDownloader(host, contract)
	}
	if err != nil {
		return nil, err
	}
	// supply a SaveFn that saves the revision to the contractor's persist
	// (the existing revision will be overwritten when SaveFn is called)
	d.SaveFn = c.saveRevision(contract.ID)

	return &hostDownloader{
		downloader: d,
		contractor: c,
	}, nil
}
// TestResync tests that the contractor can resync with a host after being
// interrupted during contract revision.
func TestResync(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	t.Parallel()
	// create testing trio
	h, c, _, err := newTestingTrio("TestResync")
	if err != nil {
		t.Fatal(err)
	}

	// get the host's entry from the db
	hostEntry, ok := c.hdb.Host(h.ExternalSettings().NetAddress)
	if !ok {
		t.Fatal("no entry for host in db")
	}

	// form a contract with the host
	contract, err := c.managedNewContract(hostEntry, 10, c.blockHeight+100)
	if err != nil {
		t.Fatal(err)
	}

	// revise the contract
	editor, err := c.Editor(contract)
	if err != nil {
		t.Fatal(err)
	}
	data, err := crypto.RandBytes(int(modules.SectorSize))
	if err != nil {
		t.Fatal(err)
	}
	root, err := editor.Upload(data)
	if err != nil {
		t.Fatal(err)
	}
	err = editor.Close()
	if err != nil {
		t.Fatal(err)
	}

	// download the data
	contract = c.contracts[contract.ID]
	downloader, err := c.Downloader(contract)
	if err != nil {
		t.Fatal(err)
	}
	retrieved, err := downloader.Sector(root)
	if err != nil {
		t.Fatal(err)
	}
	if !bytes.Equal(data, retrieved) {
		t.Fatal("downloaded data does not match original")
	}
	err = downloader.Close()
	if err != nil {
		t.Fatal(err)
	}
	contract = c.contracts[contract.ID]

	// corrupt contract and delete its cachedRevision
	badContract := contract
	badContract.LastRevision.NewRevisionNumber--
	badContract.LastRevisionTxn.TransactionSignatures = nil // delete signatures

	c.mu.Lock()
	delete(c.cachedRevisions, contract.ID)
	c.mu.Unlock()

	// Editor and Downloader should fail with the bad contract
	_, err = c.Editor(badContract)
	if !proto.IsRevisionMismatch(err) {
		t.Fatal("expected revision mismatch, got", err)
	}
	_, err = c.Downloader(badContract)
	if !proto.IsRevisionMismatch(err) {
		t.Fatal("expected revision mismatch, got", err)
	}

	// add cachedRevision
	cachedRev := cachedRevision{contract.LastRevision, contract.MerkleRoots}
	c.mu.Lock()
	c.cachedRevisions[contract.ID] = cachedRev
	c.mu.Unlock()

	// Editor and Downloader should now succeed after loading the cachedRevision
	editor, err = c.Editor(badContract)
	if err != nil {
		t.Fatal(err)
	}
	editor.Close()

	downloader, err = c.Downloader(badContract)
	if err != nil {
		t.Fatal(err)
	}
	downloader.Close()

	// corrupt contract and delete its cachedRevision
	badContract = contract
	badContract.LastRevision.NewRevisionNumber--
	badContract.MerkleRoots = nil // delete Merkle roots

	c.mu.Lock()
	delete(c.cachedRevisions, contract.ID)
	c.mu.Unlock()

	// Editor and Downloader should fail with the bad contract
	_, err = c.Editor(badContract)
	if !proto.IsRevisionMismatch(err) {
		t.Fatal("expected revision mismatch, got", err)
	}
	_, err = c.Downloader(badContract)
	if !proto.IsRevisionMismatch(err) {
		t.Fatal("expected revision mismatch, got", err)
	}

	// add cachedRevision
	c.mu.Lock()
	c.cachedRevisions[contract.ID] = cachedRev
	c.mu.Unlock()

	// should be able to upload after loading the cachedRevision
	editor, err = c.Editor(badContract)
	if err != nil {
		t.Fatal(err)
	}
	_, err = editor.Upload(data)
	if err != nil {
		t.Fatal(err)
	}
	editor.Close()
}