Ejemplo n.º 1
0
// TestUploadAndDownload creates a network with a host and then uploads a file
// from the renter to the host, and then downloads it.
func TestUploadAndDownload(t *testing.T) {
	t.Skip("broken -- fix when host/renter are revamped")

	// Create a server and add a host to the network.
	st := newServerTester("TestUploadAndDownload", t)
	err := st.announceHost()
	if err != nil {
		t.Fatal(err)
	}
	for len(st.server.hostdb.ActiveHosts()) == 0 {
		time.Sleep(time.Millisecond)
	}

	// Upload to the host.
	uploadName := "api.go"
	st.callAPI("/renter/files/upload?pieces=1&nickname=api.go&source=" + uploadName)

	// Wait for the upload to finish - this is necessary due to the
	// fact that zero-conf transactions aren't actually propagated properly.
	//
	// TODO: There should be some way to just spinblock until the download
	// completes. Except there's no exported function in the renter that will
	// indicate if a download has completed or not.
	time.Sleep(types.RenterZeroConfDelay + time.Second*10)

	files := st.server.renter.FileList()
	if len(files) != 1 || !files[0].Available() {
		t.Fatal("file is not uploaded")
	}

	// Try to download the file.
	downloadName := build.TempDir("api", "TestUploadAndDownload", "downloadTestData")
	st.callAPI("/renter/files/download?nickname=api.go&destination=" + downloadName)
	time.Sleep(time.Second * 2)

	// Check that the downloaded file is equal to the uploaded file.
	upFile, err := os.Open(uploadName)
	if err != nil {
		t.Fatal(err)
	}
	defer upFile.Close()
	downFile, err := os.Open(downloadName)
	if err != nil {
		t.Fatal(err)
	}
	defer upFile.Close()
	upRoot, err := crypto.ReaderMerkleRoot(upFile)
	if err != nil {
		t.Fatal(err)
	}
	downRoot, err := crypto.ReaderMerkleRoot(downFile)
	if err != nil {
		t.Fatal(err)
	}
	if upRoot != downRoot {
		t.Error("uploaded and downloaded file have a hash mismatch")
	}
}
Ejemplo n.º 2
0
// 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
}
Ejemplo n.º 3
0
func TestStorageProof(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	ht := CreateHostTester("TestStorageProof", t)

	// create a file contract
	fc := types.FileContract{
		WindowStart:        types.MaturityDelay + 3,
		WindowEnd:          1000,
		Payout:             types.NewCurrency64(1),
		UnlockHash:         types.UnlockConditions{}.UnlockHash(),
		ValidProofOutputs:  []types.SiacoinOutput{{Value: types.NewCurrency64(1)}},
		MissedProofOutputs: []types.SiacoinOutput{{Value: types.NewCurrency64(1)}},
	}
	txnBuilder := ht.wallet.StartTransaction()
	err := txnBuilder.FundSiacoins(fc.Payout)
	if err != nil {
		t.Fatal(err)
	}
	txnBuilder.AddFileContract(fc)
	signedTxnSet, err := txnBuilder.Sign(true)
	if err != nil {
		t.Fatal(err)
	}
	fcid := signedTxnSet[len(signedTxnSet)-1].FileContractID(0)

	// generate data
	const dataSize = 777
	data := make([]byte, dataSize)
	rand.Read(data)
	root, err := crypto.ReaderMerkleRoot(bytes.NewReader(data))
	if err != nil {
		t.Fatal(err)
	}
	ioutil.WriteFile(filepath.Join(ht.host.persistDir, "foo"), data, 0777)

	// create revision
	rev := types.FileContractRevision{
		ParentID:              fcid,
		UnlockConditions:      types.UnlockConditions{},
		NewFileSize:           dataSize,
		NewWindowStart:        fc.WindowStart,
		NewFileMerkleRoot:     root,
		NewWindowEnd:          fc.WindowEnd,
		NewValidProofOutputs:  fc.ValidProofOutputs,
		NewMissedProofOutputs: fc.MissedProofOutputs,
		NewRevisionNumber:     1,
	}
	revTxn := types.Transaction{
		FileContractRevisions: []types.FileContractRevision{rev},
	}

	// create obligation
	obligation := &contractObligation{
		ID:           fcid,
		FileContract: fc,
		Path:         filepath.Join(ht.host.persistDir, "foo"),
	}
	ht.host.obligationsByID[fcid] = obligation
	ht.host.obligationsByHeight[fc.WindowStart+1] = []*contractObligation{obligation}

	// submit both to tpool
	err = ht.tpool.AcceptTransactionSet(append(signedTxnSet, revTxn))
	if err != nil {
		t.Fatal(err)
	}
	_, err = ht.miner.AddBlock()
	if err != nil {
		t.Fatal(err)
	}

	// storage proof will be submitted after mining one more block
	_, err = ht.miner.AddBlock()
	if err != nil {
		t.Fatal(err)
	}
}
Ejemplo n.º 4
0
// testFileContractRevision creates and revises a file contract on the
// blockchain.
func (cst *consensusSetTester) testFileContractRevision() {
	// COMPATv0.4.0 - Step the block height up past the hardfork amount. This
	// code stops nondeterministic failures when producing storage proofs that
	// is related to buggy old code.
	for cst.cs.dbBlockHeight() <= 10 {
		_, err := cst.miner.AddBlock()
		if err != nil {
			panic(err)
		}
	}

	// Create a file (as a bytes.Buffer) that will be used for the file
	// contract.
	filesize := uint64(4e3)
	file := randFile(filesize)
	merkleRoot, err := crypto.ReaderMerkleRoot(file)
	if err != nil {
		panic(err)
	}
	file.Seek(0, 0)

	// Create a spendable unlock hash for the file contract.
	sk, pk, err := crypto.GenerateKeyPair()
	if err != nil {
		panic(err)
	}
	uc := types.UnlockConditions{
		PublicKeys: []types.SiaPublicKey{{
			Algorithm: types.SignatureEd25519,
			Key:       pk[:],
		}},
		SignaturesRequired: 1,
	}

	// Create a file contract that will be revised.
	validProofDest := randAddress()
	payout := types.NewCurrency64(400e6)
	fc := types.FileContract{
		FileSize:       filesize,
		FileMerkleRoot: crypto.Hash{},
		WindowStart:    cst.cs.dbBlockHeight() + 2,
		WindowEnd:      cst.cs.dbBlockHeight() + 3,
		Payout:         payout,
		ValidProofOutputs: []types.SiacoinOutput{{
			UnlockHash: validProofDest,
			Value:      types.PostTax(cst.cs.dbBlockHeight(), payout),
		}},
		MissedProofOutputs: []types.SiacoinOutput{{
			UnlockHash: types.UnlockHash{},
			Value:      types.PostTax(cst.cs.dbBlockHeight(), payout),
		}},
		UnlockHash: uc.UnlockHash(),
	}

	// Submit a transaction with the file contract.
	txnBuilder := cst.wallet.StartTransaction()
	err = txnBuilder.FundSiacoins(payout)
	if err != nil {
		panic(err)
	}
	fcIndex := txnBuilder.AddFileContract(fc)
	txnSet, err := txnBuilder.Sign(true)
	if err != nil {
		panic(err)
	}
	err = cst.tpool.AcceptTransactionSet(txnSet)
	if err != nil {
		panic(err)
	}
	_, err = cst.miner.AddBlock()
	if err != nil {
		panic(err)
	}

	// Submit a revision for the file contract.
	ti := len(txnSet) - 1
	fcid := txnSet[ti].FileContractID(fcIndex)
	fcr := types.FileContractRevision{
		ParentID:          fcid,
		UnlockConditions:  uc,
		NewRevisionNumber: 69292,

		NewFileSize:           filesize,
		NewFileMerkleRoot:     merkleRoot,
		NewWindowStart:        cst.cs.dbBlockHeight() + 1,
		NewWindowEnd:          cst.cs.dbBlockHeight() + 2,
		NewValidProofOutputs:  fc.ValidProofOutputs,
		NewMissedProofOutputs: fc.MissedProofOutputs,
		NewUnlockHash:         uc.UnlockHash(),
	}
	ts := types.TransactionSignature{
		ParentID:       crypto.Hash(fcid),
		CoveredFields:  types.CoveredFields{WholeTransaction: true},
		PublicKeyIndex: 0,
	}
	txn := types.Transaction{
		FileContractRevisions: []types.FileContractRevision{fcr},
		TransactionSignatures: []types.TransactionSignature{ts},
	}
	encodedSig, err := crypto.SignHash(txn.SigHash(0), sk)
	if err != nil {
		panic(err)
	}
	txn.TransactionSignatures[0].Signature = encodedSig[:]
	err = cst.tpool.AcceptTransactionSet([]types.Transaction{txn})
	if err != nil {
		panic(err)
	}
	_, err = cst.miner.AddBlock()
	if err != nil {
		panic(err)
	}

	// Create and submit a storage proof for the file contract.
	segmentIndex, err := cst.cs.StorageProofSegment(fcid)
	if err != nil {
		panic(err)
	}
	segment, hashSet, err := crypto.BuildReaderProof(file, segmentIndex)
	if err != nil {
		panic(err)
	}
	sp := types.StorageProof{
		ParentID: fcid,
		HashSet:  hashSet,
	}
	copy(sp.Segment[:], segment)
	txnBuilder = cst.wallet.StartTransaction()
	txnBuilder.AddStorageProof(sp)
	txnSet, err = txnBuilder.Sign(true)
	if err != nil {
		panic(err)
	}
	err = cst.tpool.AcceptTransactionSet(txnSet)
	if err != nil {
		panic(err)
	}
	_, err = cst.miner.AddBlock()
	if err != nil {
		panic(err)
	}

	// Check that the file contract has been removed.
	_, err = cst.cs.dbGetFileContract(fcid)
	if err != errNilItem {
		panic("file contract should not exist in the database")
	}
}
Ejemplo n.º 5
0
// testValidStorageProofBlocks adds a block with a file contract, and then
// submits a storage proof for that file contract.
func (cst *consensusSetTester) testValidStorageProofBlocks() {
	// COMPATv0.4.0 - Step the block height up past the hardfork amount. This
	// code stops nondeterministic failures when producing storage proofs that
	// is related to buggy old code.
	for cst.cs.dbBlockHeight() <= 10 {
		_, err := cst.miner.AddBlock()
		if err != nil {
			panic(err)
		}
	}

	// Create a file (as a bytes.Buffer) that will be used for the file
	// contract.
	filesize := uint64(4e3)
	file := randFile(filesize)
	merkleRoot, err := crypto.ReaderMerkleRoot(file)
	if err != nil {
		panic(err)
	}
	file.Seek(0, 0)

	// Create a file contract that will be successful.
	validProofDest := randAddress()
	payout := types.NewCurrency64(400e6)
	fc := types.FileContract{
		FileSize:       filesize,
		FileMerkleRoot: merkleRoot,
		WindowStart:    cst.cs.dbBlockHeight() + 1,
		WindowEnd:      cst.cs.dbBlockHeight() + 2,
		Payout:         payout,
		ValidProofOutputs: []types.SiacoinOutput{{
			UnlockHash: validProofDest,
			Value:      types.PostTax(cst.cs.dbBlockHeight(), payout),
		}},
		MissedProofOutputs: []types.SiacoinOutput{{
			UnlockHash: types.UnlockHash{},
			Value:      types.PostTax(cst.cs.dbBlockHeight(), payout),
		}},
	}

	// Submit a transaction with the file contract.
	oldSiafundPool := cst.cs.dbGetSiafundPool()
	txnBuilder := cst.wallet.StartTransaction()
	err = txnBuilder.FundSiacoins(payout)
	if err != nil {
		panic(err)
	}
	fcIndex := txnBuilder.AddFileContract(fc)
	txnSet, err := txnBuilder.Sign(true)
	if err != nil {
		panic(err)
	}
	err = cst.tpool.AcceptTransactionSet(txnSet)
	if err != nil {
		panic(err)
	}
	_, err = cst.miner.AddBlock()
	if err != nil {
		panic(err)
	}

	// Check that the siafund pool was increased by the tax on the payout.
	siafundPool := cst.cs.dbGetSiafundPool()
	if siafundPool.Cmp(oldSiafundPool.Add(types.Tax(cst.cs.dbBlockHeight()-1, payout))) != 0 {
		panic("siafund pool was not increased correctly")
	}

	// Check that the file contract made it into the database.
	ti := len(txnSet) - 1
	fcid := txnSet[ti].FileContractID(fcIndex)
	_, err = cst.cs.dbGetFileContract(fcid)
	if err != nil {
		panic(err)
	}

	// Create and submit a storage proof for the file contract.
	segmentIndex, err := cst.cs.StorageProofSegment(fcid)
	if err != nil {
		panic(err)
	}
	segment, hashSet, err := crypto.BuildReaderProof(file, segmentIndex)
	if err != nil {
		panic(err)
	}
	sp := types.StorageProof{
		ParentID: fcid,
		HashSet:  hashSet,
	}
	copy(sp.Segment[:], segment)
	txnBuilder = cst.wallet.StartTransaction()
	txnBuilder.AddStorageProof(sp)
	txnSet, err = txnBuilder.Sign(true)
	if err != nil {
		panic(err)
	}
	err = cst.tpool.AcceptTransactionSet(txnSet)
	if err != nil {
		panic(err)
	}
	_, err = cst.miner.AddBlock()
	if err != nil {
		panic(err)
	}

	// Check that the file contract has been removed.
	_, err = cst.cs.dbGetFileContract(fcid)
	if err != errNilItem {
		panic("file contract should not exist in the database")
	}

	// Check that the siafund pool has not changed.
	postProofPool := cst.cs.dbGetSiafundPool()
	if postProofPool.Cmp(siafundPool) != 0 {
		panic("siafund pool should not change after submitting a storage proof")
	}

	// Check that a delayed output was created for the valid proof.
	spoid := fcid.StorageProofOutputID(types.ProofValid, 0)
	dsco, err := cst.cs.dbGetDSCO(cst.cs.dbBlockHeight()+types.MaturityDelay, spoid)
	if err != nil {
		panic(err)
	}
	if dsco.UnlockHash != fc.ValidProofOutputs[0].UnlockHash {
		panic("wrong unlock hash in dsco")
	}
	if dsco.Value.Cmp(fc.ValidProofOutputs[0].Value) != 0 {
		panic("wrong sco value in dsco")
	}
}
Ejemplo n.º 6
0
// uploadFile uploads a file to the host from the tester's renter. The data
// used to make the file is returned. The nickname of the file in the renter is
// the same as the name provided as input.
func (ht *hostTester) uploadFile(path string, renew bool) ([]byte, error) {
	// Check that renting is initialized properly.
	err := ht.initRenting()
	if err != nil {
		return nil, err
	}

	// Create a file to upload to the host.
	source := filepath.Join(ht.persistDir, path+".testfile")
	datasize := uint64(1024)
	data, err := crypto.RandBytes(int(datasize))
	if err != nil {
		return nil, err
	}
	dataMerkleRoot, err := crypto.ReaderMerkleRoot(bytes.NewReader(data))
	if err != nil {
		return nil, err
	}
	err = ioutil.WriteFile(source, data, 0600)
	if err != nil {
		return nil, err
	}

	// Have the renter upload to the host.
	rsc, err := renter.NewRSCode(1, 1)
	if err != nil {
		return nil, err
	}
	fup := modules.FileUploadParams{
		Source:      source,
		SiaPath:     path,
		Duration:    testUploadDuration,
		Renew:       renew,
		ErasureCode: rsc,
		PieceSize:   0,
	}
	err = ht.renter.Upload(fup)
	if err != nil {
		return nil, err
	}

	// Wait until the upload has finished.
	for i := 0; i < 100; i++ {
		time.Sleep(time.Millisecond * 100)

		// Asynchronous processes in the host access obligations by id,
		// therefore a lock is required to scan the set of obligations.
		if func() bool {
			ht.host.mu.Lock()
			defer ht.host.mu.Unlock()

			for _, ob := range ht.host.obligationsByID {
				if dataMerkleRoot == ob.merkleRoot() {
					return true
				}
			}
			return false
		}() {
			break
		}
	}

	// Block until the renter is at 50 upload progress - it takes time for the
	// contract to confirm renter-side.
	complete := false
	for i := 0; i < 50 && !complete; i++ {
		fileInfos := ht.renter.FileList()
		for _, fileInfo := range fileInfos {
			if fileInfo.UploadProgress >= 50 {
				complete = true
			}
		}
		if complete {
			break
		}
		time.Sleep(time.Millisecond * 50)
	}
	if !complete {
		return nil, errors.New("renter never recognized that the upload completed")
	}

	// The rest of the upload can be performed under lock.
	ht.host.mu.Lock()
	defer ht.host.mu.Unlock()

	if len(ht.host.obligationsByID) != 1 {
		return nil, errors.New("expecting a single obligation")
	}
	for _, ob := range ht.host.obligationsByID {
		if ob.fileSize() >= datasize {
			return data, nil
		}
	}
	return nil, errors.New("ht.uploadFile: upload failed")
}
Ejemplo n.º 7
0
// rpcContract is an RPC that negotiates a file contract. If the
// negotiation is successful, the file is downloaded and the host begins
// submitting proofs of storage.
func (h *Host) rpcContract(conn net.Conn) (err error) {
	// Read the contract terms.
	var terms modules.ContractTerms
	err = encoding.ReadObject(conn, &terms, maxContractLen)
	if err != nil {
		return
	}

	// Consider the contract terms. If they are unacceptable, return an error
	// describing why.
	lockID := h.mu.RLock()
	err = h.considerTerms(terms)
	h.mu.RUnlock(lockID)
	if err != nil {
		err = encoding.WriteObject(conn, err.Error())
		return
	}

	// terms are acceptable; allocate space for file
	lockID = h.mu.Lock()
	file, path, err := h.allocate(terms.FileSize)
	h.mu.Unlock(lockID)
	if err != nil {
		return
	}
	defer file.Close()

	// rollback everything if something goes wrong
	defer func() {
		lockID := h.mu.Lock()
		defer h.mu.Unlock(lockID)
		if err != nil {
			h.deallocate(terms.FileSize, path)
		}
	}()

	// signal that we are ready to download file
	err = encoding.WriteObject(conn, modules.AcceptTermsResponse)
	if err != nil {
		return
	}

	// simultaneously download file and calculate its Merkle root.
	tee := io.TeeReader(
		// use a LimitedReader to ensure we don't read indefinitely
		io.LimitReader(conn, int64(terms.FileSize)),
		// each byte we read from tee will also be written to file
		file,
	)
	merkleRoot, err := crypto.ReaderMerkleRoot(tee)
	if err != nil {
		return
	}

	// Data has been sent, read in the unsigned transaction with the file
	// contract.
	var unsignedTxn types.Transaction
	err = encoding.ReadObject(conn, &unsignedTxn, maxContractLen)
	if err != nil {
		return
	}

	// Verify that the transaction matches the agreed upon terms, and that the
	// Merkle root in the file contract matches our independently calculated
	// Merkle root.
	err = verifyTransaction(unsignedTxn, terms, merkleRoot)
	if err != nil {
		err = errors.New("transaction does not satisfy terms: " + err.Error())
		return
	}

	// Add the collateral to the transaction, but do not sign the transaction.
	collateralTxn, txnBuilder, err := h.addCollateral(unsignedTxn, terms)
	if err != nil {
		return
	}
	err = encoding.WriteObject(conn, collateralTxn)
	if err != nil {
		return
	}

	// Read in the renter-signed transaction and check that it matches the
	// previously accepted transaction.
	var signedTxn types.Transaction
	err = encoding.ReadObject(conn, &signedTxn, maxContractLen)
	if err != nil {
		return
	}
	if collateralTxn.ID() != signedTxn.ID() {
		err = errors.New("signed transaction does not match the transaction with collateral")
		return
	}

	// Add the signatures from the renter signed transaction, and then sign the
	// transaction, then submit the transaction.
	for _, sig := range signedTxn.TransactionSignatures {
		txnBuilder.AddTransactionSignature(sig)
		if err != nil {
			return
		}
	}
	txnSet, err := txnBuilder.Sign(true)
	if err != nil {
		return
	}
	err = h.tpool.AcceptTransactionSet(txnSet)
	if err != nil {
		return
	}

	// Add this contract to the host's list of obligations.
	fcid := signedTxn.FileContractID(0)
	fc := signedTxn.FileContracts[0]
	proofHeight := fc.WindowStart + StorageProofReorgDepth
	co := contractObligation{
		ID:           fcid,
		FileContract: fc,
		Path:         path,
	}
	lockID = h.mu.Lock()
	h.obligationsByHeight[proofHeight] = append(h.obligationsByHeight[proofHeight], co)
	h.obligationsByID[fcid] = co
	h.save()
	h.mu.Unlock(lockID)

	// Send an ack to the renter that all is well.
	err = encoding.WriteObject(conn, true)
	if err != nil {
		return
	}

	// TODO: we don't currently watch the blockchain to make sure that the
	// transaction actually gets into the blockchain.

	return
}
Ejemplo n.º 8
0
// compatibilityLoad tries to load the file as a compatible version.
func (h *Host) compatibilityLoad() error {
	// Try loading the file as a 0.4 file.
	c04h := new(compat04Host)
	err := persist.LoadFile(compat04Metadata, c04h, filepath.Join(h.persistDir, "settings.json"))
	if err != nil {
		// 0.4.x is the only backwards compatibility provided. File could not
		// be loaded.
		return err
	}

	// Copy over host identity.
	h.publicKey = c04h.PublicKey
	h.secretKey = c04h.SecretKey

	// Copy file management, including providing compatibility for old
	// obligations.
	h.fileCounter = int64(c04h.FileCounter)
	h.spaceRemaining = c04h.HostSettings.TotalStorage
	upgradedObligations := h.loadCompat04Obligations(c04h.Obligations)

	// Copy over statistics.
	h.revenue = c04h.Profit

	// Copy over utilities.
	h.settings = c04h.HostSettings
	// AcceptingContracts should be true by default
	h.settings.AcceptingContracts = true

	// Subscribe to the consensus set.
	if build.DEBUG && h.recentChange != (modules.ConsensusChangeID{}) {
		panic("compatibility loading is not starting from blank consensus?")
	}
	// initConsensusSubscription will scan through the consensus set and set
	// 'OriginConfirmed' and 'RevisionConfirmed' when the accociated file
	// contract and file contract revisions are found.
	err = h.initConsensusSubscription()
	if err != nil {
		return err
	}

	// Remove all obligations that have not had their origin transactions
	// confirmed on the blockchain, and add action items for the rest.
	for _, uo := range upgradedObligations {
		if !uo.OriginConfirmed {
			// Because there is no transaction on the blockchain, and because
			// the old host did not keep the transaction, it's highly unlikely
			// that a transaction will appear - the obligation should be
			// removed.
			h.removeObligation(uo, obligationUnconfirmed)
			continue
		}
		// Check that the file Merkle root matches the Merkle root found in the
		// blockchain.
		file, err := os.Open(uo.Path)
		if err != nil {
			h.log.Println("Compatibility contract file could not be opened.")
			h.removeObligation(uo, obligationFailed)
			continue
		}
		merkleRoot, err := crypto.ReaderMerkleRoot(file)
		file.Close() // Close the file after use, to prevent buildup if the loop iterates many times.
		if err != nil {
			h.log.Println("Compatibility contract file could not be checksummed")
			h.removeObligation(uo, obligationFailed)
			continue
		}
		if merkleRoot != uo.merkleRoot() {
			h.log.Println("Compatibility contract file has the wrong merkle root")
			h.removeObligation(uo, obligationFailed)
			continue
		}
	}
	return nil
}
Ejemplo n.º 9
0
// negotiateContract creates a file contract for a host according to the
// requests of the host. There is an assumption that only hosts with acceptable
// terms will be put into the hostdb.
func (r *Renter) negotiateContract(host modules.HostSettings, up modules.FileUploadParams, piece *filePiece) error {
	lockID := r.mu.RLock()
	height := r.blockHeight
	r.mu.RUnlock(lockID)

	key, err := crypto.GenerateTwofishKey()
	if err != nil {
		return err
	}

	file, err := os.Open(up.Filename)
	if err != nil {
		return err
	}
	defer file.Close()

	info, err := file.Stat()
	if err != nil {
		return err
	}

	filesize := uint64(info.Size())

	// Get the price and payout.
	sizeCurrency := types.NewCurrency64(filesize)
	durationCurrency := types.NewCurrency64(uint64(up.Duration))
	clientCost := host.Price.Mul(sizeCurrency).Mul(durationCurrency)
	hostCollateral := host.Collateral.Mul(sizeCurrency).Mul(durationCurrency)
	payout := clientCost.Add(hostCollateral)
	validOutputValue := payout.Sub(types.FileContract{Payout: payout}.Tax())

	// Create the contract terms.
	terms := modules.ContractTerms{
		FileSize:      filesize,
		Duration:      up.Duration,
		DurationStart: height - 3,
		WindowSize:    defaultWindowSize,
		Price:         host.Price,
		Collateral:    host.Collateral,

		ValidProofOutputs: []types.SiacoinOutput{
			{Value: validOutputValue, UnlockHash: host.UnlockHash},
		},

		MissedProofOutputs: []types.SiacoinOutput{
			{Value: validOutputValue, UnlockHash: types.UnlockHash{}},
		},
	}

	// TODO: This is a hackish sleep, we need to be certain that all dependent
	// transactions have propgated to the host's transaction pool. Instead,
	// built into the protocol should be a step where any dependent
	// transactions are automatically provided.
	if build.Release == "standard" {
		time.Sleep(time.Minute)
	} else if build.Release == "testing" {
		time.Sleep(time.Second * 15)
	} else {
		time.Sleep(time.Second)
	}

	// Perform the negotiations with the host through a network call.
	conn, err := net.DialTimeout("tcp", string(host.IPAddress), 10e9)
	if err != nil {
		return err
	}
	defer conn.Close()
	err = encoding.WriteObject(conn, [8]byte{'C', 'o', 'n', 't', 'r', 'a', 'c', 't'})
	if err != nil {
		return err
	}

	// Send the contract terms and read the response.
	if err = encoding.WriteObject(conn, terms); err != nil {
		return err
	}
	var response string
	if err = encoding.ReadObject(conn, &response, 128); err != nil {
		return err
	}
	if response != modules.AcceptTermsResponse {
		return errors.New(response)
	}

	// Encrypt and transmit the file data while calculating its Merkle root.
	tee := io.TeeReader(
		// wrap file reader in encryption layer
		key.NewReader(file),
		// each byte we read from tee will also be written to conn;
		// the uploadWriter updates the piece's 'Transferred' field
		&uploadWriter{piece, conn},
	)
	merkleRoot, err := crypto.ReaderMerkleRoot(tee)
	if err != nil {
		return err
	}

	// Create the transaction holding the contract. This is done first so the
	// transaction is created sooner, which will impact the user's wallet
	// balance faster vs. waiting for the whole thing to upload before
	// affecting the user's balance.
	unsignedTxn, txnBuilder, err := r.createContractTransaction(terms, merkleRoot)
	if err != nil {
		return err
	}

	// Send the unsigned transaction to the host.
	err = encoding.WriteObject(conn, unsignedTxn)
	if err != nil {
		return err
	}

	// The host will respond with a transaction with the collateral added.
	// Add the collateral inputs from the host to the original wallet
	// transaction.
	var collateralTxn types.Transaction
	err = encoding.ReadObject(conn, &collateralTxn, 16e3)
	if err != nil {
		return err
	}
	for i := len(unsignedTxn.SiacoinInputs); i < len(collateralTxn.SiacoinInputs); i++ {
		txnBuilder.AddSiacoinInput(collateralTxn.SiacoinInputs[i])
	}
	signedTxn, err := txnBuilder.Sign(true)
	if err != nil {
		return err
	}

	// Send the signed transaction back to the host.
	err = encoding.WriteObject(conn, signedTxn)
	if err != nil {
		return err
	}

	// Read an ack from the host that all is well.
	var ack bool
	err = encoding.ReadObject(conn, &ack, 1)
	if err != nil {
		return err
	}
	if !ack {
		return errors.New("host negotiation failed")
	}

	// TODO: We don't actually watch the blockchain to make sure that the
	// file contract made it.

	// Negotiation was successful; update the filePiece.
	txIndex := len(signedTxn) - 1
	lockID = r.mu.Lock()
	piece.Active = true
	piece.Repairing = false
	piece.Contract = signedTxn[txIndex].FileContracts[0]
	piece.ContractID = signedTxn[txIndex].FileContractID(0)
	piece.HostIP = host.IPAddress
	piece.EncryptionKey = key
	r.save()
	r.mu.Unlock(lockID)

	return nil
}
Ejemplo n.º 10
0
// TestValidStorageProofs probes the validStorageProofs method of the consensus
// set.
func TestValidStorageProofs(t *testing.T) {
	if testing.Short() {
		t.SkipNow()
	}
	cst, err := createConsensusSetTester("TestValidStorageProofs")
	if err != nil {
		t.Fatal(err)
	}

	// Create a file contract for which a storage proof can be created.
	var fcid types.FileContractID
	fcid[0] = 12
	simFile := make([]byte, 64*1024)
	rand.Read(simFile)
	buffer := bytes.NewReader(simFile)
	root, err := crypto.ReaderMerkleRoot(buffer)
	if err != nil {
		t.Fatal(err)
	}
	fc := types.FileContract{
		FileSize:       64 * 1024,
		FileMerkleRoot: root,
		WindowStart:    2,
		WindowEnd:      1200,
	}
	cst.cs.fileContracts[fcid] = fc
	buffer.Seek(0, 0)

	// Create a transaction with a storage proof.
	proofIndex, err := cst.cs.storageProofSegment(fcid)
	if err != nil {
		t.Fatal(err)
	}
	base, proofSet, err := crypto.BuildReaderProof(buffer, proofIndex)
	if err != nil {
		t.Fatal(err)
	}
	txn := types.Transaction{
		StorageProofs: []types.StorageProof{
			{
				ParentID: fcid,
				Segment:  base,
				HashSet:  proofSet,
			},
		},
	}
	err = cst.cs.validStorageProofs(txn)
	if err != nil {
		t.Error(err)
	}

	// Corrupt the proof set.
	proofSet[0][0]++
	txn = types.Transaction{
		StorageProofs: []types.StorageProof{
			{
				ParentID: fcid,
				Segment:  base,
				HashSet:  proofSet,
			},
		},
	}
	err = cst.cs.validStorageProofs(txn)
	if err != ErrInvalidStorageProof {
		t.Error(err)
	}
}
Ejemplo n.º 11
0
// TestValidFileContractRevisions probes the validFileContractRevisions method
// of the consensus set.
func TestValidFileContractRevisions(t *testing.T) {
	if testing.Short() {
		// t.SkipNow()
	}
	cst, err := createConsensusSetTester("TestValidStorageProofs")
	if err != nil {
		t.Fatal(err)
	}

	// Grab an address + unlock conditions for the transaction.
	unlockHash, unlockConditions, err := cst.wallet.CoinAddress(false)
	if err != nil {
		t.Fatal(err)
	}

	// Create a file contract for which a storage proof can be created.
	var fcid types.FileContractID
	fcid[0] = 12
	simFile := make([]byte, 64*1024)
	rand.Read(simFile)
	buffer := bytes.NewReader(simFile)
	root, err := crypto.ReaderMerkleRoot(buffer)
	if err != nil {
		t.Fatal(err)
	}
	fc := types.FileContract{
		FileSize:       64 * 1024,
		FileMerkleRoot: root,
		WindowStart:    102,
		WindowEnd:      1200,
		UnlockHash:     unlockHash,
		RevisionNumber: 1,
	}
	cst.cs.fileContracts[fcid] = fc

	// Try a working file contract revision.
	txn := types.Transaction{
		FileContractRevisions: []types.FileContractRevision{
			{
				ParentID:          fcid,
				UnlockConditions:  unlockConditions,
				NewRevisionNumber: 2,
			},
		},
	}
	err = cst.cs.validFileContractRevisions(txn)
	if err != nil {
		t.Error(err)
	}

	// Try a transaction with an insufficient revision number.
	txn = types.Transaction{
		FileContractRevisions: []types.FileContractRevision{
			{
				ParentID:          fcid,
				UnlockConditions:  unlockConditions,
				NewRevisionNumber: 1,
			},
		},
	}
	err = cst.cs.validFileContractRevisions(txn)
	if err != ErrLowRevisionNumber {
		t.Error(err)
	}
	txn = types.Transaction{
		FileContractRevisions: []types.FileContractRevision{
			{
				ParentID:          fcid,
				UnlockConditions:  unlockConditions,
				NewRevisionNumber: 0,
			},
		},
	}
	err = cst.cs.validFileContractRevisions(txn)
	if err != ErrLowRevisionNumber {
		t.Error(err)
	}
}