Beispiel #1
0
// 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
}
Beispiel #2
0
// managedRPCRevise is an RPC that allows a renter to revise a file contract. It will
// read new revisions in a loop until the renter sends a termination signal.
func (h *Host) managedRPCRevise(conn net.Conn) error {
	// read ID of contract to be revised
	var fcid types.FileContractID
	if err := encoding.ReadObject(conn, &fcid, crypto.HashSize); err != nil {
		return errors.New("couldn't read contract ID: " + err.Error())
	}

	// remove conn deadline while we wait for lock and rebuild the Merkle tree.
	err := conn.SetDeadline(time.Now().Add(15 * time.Minute))
	if err != nil {
		return err
	}

	h.mu.RLock()
	obligation, exists := h.obligationsByID[fcid]
	h.mu.RUnlock()
	if !exists {
		return errors.New("no record of that contract")
	}
	// need to protect against two simultaneous revisions to the same
	// contract; this can cause inconsistency and data loss, making storage
	// proofs impossible
	//
	// TODO: DOS vector - the host has locked the obligation even though the
	// renter has not proven themselves to be the owner of the file contract.
	obligation.mu.Lock()
	defer obligation.mu.Unlock()

	// open the file in append mode
	file, err := os.OpenFile(obligation.Path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0660)
	if err != nil {
		return err
	}

	// rebuild current Merkle tree
	tree := crypto.NewTree()
	err = tree.ReadSegments(file)
	if err != nil {
		// Error does not need to be checked when closing the file, already
		// there have been issues related to the filesystem.
		_ = file.Close()
		return err
	}

	// accept new revisions in a loop. The final good transaction will be
	// submitted to the blockchain.
	revisionErr := func() error {
		for {
			// allow 5 minutes between revisions
			err := conn.SetDeadline(time.Now().Add(5 * time.Minute))
			if err != nil {
				return err
			}

			// read proposed revision
			var revTxn types.Transaction
			if err = encoding.ReadObject(conn, &revTxn, types.BlockSizeLimit); err != nil {
				return errors.New("couldn't read revision: " + err.Error())
			}
			// an empty transaction indicates completion
			if revTxn.ID() == (types.Transaction{}).ID() {
				return nil
			}

			// allow 5 minutes for each revision
			err = conn.SetDeadline(time.Now().Add(5 * time.Minute))
			if err != nil {
				return err
			}

			// check revision against original file contract
			h.mu.RLock()
			err = h.considerRevision(revTxn, obligation)
			h.mu.RUnlock()
			if err != nil {
				// There is nothing that can be done if there is an error while
				// writing to a connection.
				_ = encoding.WriteObject(conn, err.Error())
				return err
			}

			// indicate acceptance
			if err := encoding.WriteObject(conn, modules.AcceptResponse); err != nil {
				return errors.New("couldn't write acceptance: " + err.Error())
			}

			// read piece
			// TODO: simultaneously read into tree and file
			rev := revTxn.FileContractRevisions[0]
			piece := make([]byte, rev.NewFileSize-obligation.fileSize())
			_, err = io.ReadFull(conn, piece)
			if err != nil {
				return errors.New("couldn't read piece data: " + err.Error())
			}

			// verify Merkle root
			err = tree.ReadSegments(bytes.NewReader(piece))
			if err != nil {
				return errors.New("couldn't verify Merkle root: " + err.Error())
			}
			if tree.Root() != rev.NewFileMerkleRoot {
				return errors.New("revision has bad Merkle root")
			}

			// manually sign the transaction
			revTxn.TransactionSignatures = append(revTxn.TransactionSignatures, types.TransactionSignature{
				ParentID:       crypto.Hash(fcid),
				CoveredFields:  types.CoveredFields{FileContractRevisions: []uint64{0}},
				PublicKeyIndex: 1, // host key is always second
			})
			encodedSig, err := crypto.SignHash(revTxn.SigHash(1), h.secretKey)
			if err != nil {
				return err
			}
			revTxn.TransactionSignatures[1].Signature = encodedSig[:]

			// append piece to file
			if _, err := file.Write(piece); err != nil {
				return errors.New("couldn't write new data to file: " + err.Error())
			}

			// save updated obligation to disk
			h.mu.Lock()
			h.reviseObligation(revTxn)
			h.mu.Unlock()

			// send the signed transaction - this must be the last thing that happens.
			if err := encoding.WriteObject(conn, revTxn); err != nil {
				return errors.New("couldn't write signed revision transaction: " + err.Error())
			}
		}
	}()
	err = file.Close()
	if err != nil {
		return err
	}

	err = h.tpool.AcceptTransactionSet([]types.Transaction{obligation.RevisionTransaction})
	if err != nil {
		h.log.Println("WARN: transaction pool rejected revision transaction: " + err.Error())
	}
	return revisionErr
}
Beispiel #3
0
// buildExplorerTransaction takes a transaction and the height + id of the
// block it appears in an uses that to build an explorer transaction.
func (srv *Server) buildExplorerTransaction(height types.BlockHeight, parent types.BlockID, txn types.Transaction) (et ExplorerTransaction) {
	// Get the header information for the transaction.
	et.ID = txn.ID()
	et.Height = height
	et.Parent = parent
	et.RawTransaction = txn

	// Add the siacoin outputs that correspond with each siacoin input.
	for _, sci := range txn.SiacoinInputs {
		sco, exists := srv.explorer.SiacoinOutput(sci.ParentID)
		if build.DEBUG && !exists {
			panic("could not find corresponding siacoin output")
		}
		et.SiacoinInputOutputs = append(et.SiacoinInputOutputs, sco)
	}

	for i := range txn.SiacoinOutputs {
		et.SiacoinOutputIDs = append(et.SiacoinOutputIDs, txn.SiacoinOutputID(uint64(i)))
	}

	// Add all of the valid and missed proof ids as extra data to the file
	// contracts.
	for i, fc := range txn.FileContracts {
		fcid := txn.FileContractID(uint64(i))
		var fcvpoids []types.SiacoinOutputID
		var fcmpoids []types.SiacoinOutputID
		for j := range fc.ValidProofOutputs {
			fcvpoids = append(fcvpoids, fcid.StorageProofOutputID(types.ProofValid, uint64(j)))
		}
		for j := range fc.MissedProofOutputs {
			fcmpoids = append(fcmpoids, fcid.StorageProofOutputID(types.ProofMissed, uint64(j)))
		}
		et.FileContractIDs = append(et.FileContractIDs, fcid)
		et.FileContractValidProofOutputIDs = append(et.FileContractValidProofOutputIDs, fcvpoids)
		et.FileContractMissedProofOutputIDs = append(et.FileContractMissedProofOutputIDs, fcmpoids)
	}

	// Add all of the valid and missed proof ids as extra data to the file
	// contract revisions.
	for _, fcr := range txn.FileContractRevisions {
		var fcrvpoids []types.SiacoinOutputID
		var fcrmpoids []types.SiacoinOutputID
		for j := range fcr.NewValidProofOutputs {
			fcrvpoids = append(fcrvpoids, fcr.ParentID.StorageProofOutputID(types.ProofValid, uint64(j)))
		}
		for j := range fcr.NewMissedProofOutputs {
			fcrmpoids = append(fcrmpoids, fcr.ParentID.StorageProofOutputID(types.ProofMissed, uint64(j)))
		}
		et.FileContractValidProofOutputIDs = append(et.FileContractValidProofOutputIDs, fcrvpoids)
		et.FileContractMissedProofOutputIDs = append(et.FileContractMissedProofOutputIDs, fcrmpoids)
	}

	// Add all of the output ids and outputs corresponding with each storage
	// proof.
	for _, sp := range txn.StorageProofs {
		fileContract, fileContractRevisions, fileContractExists, _ := srv.explorer.FileContractHistory(sp.ParentID)
		if !fileContractExists && build.DEBUG {
			panic("could not find a file contract connected with a storage proof")
		}
		var storageProofOutputs []types.SiacoinOutput
		if len(fileContractRevisions) > 0 {
			storageProofOutputs = fileContractRevisions[len(fileContractRevisions)-1].NewValidProofOutputs
		} else {
			storageProofOutputs = fileContract.ValidProofOutputs
		}
		var storageProofOutputIDs []types.SiacoinOutputID
		for i := range storageProofOutputs {
			storageProofOutputIDs = append(storageProofOutputIDs, sp.ParentID.StorageProofOutputID(types.ProofValid, uint64(i)))
		}
		et.StorageProofOutputIDs = append(et.StorageProofOutputIDs, storageProofOutputIDs)
		et.StorageProofOutputs = append(et.StorageProofOutputs, storageProofOutputs)
	}

	// Add the siafund outputs that correspond to each siacoin input.
	for _, sci := range txn.SiafundInputs {
		sco, exists := srv.explorer.SiafundOutput(sci.ParentID)
		if build.DEBUG && !exists {
			panic("could not find corresponding siafund output")
		}
		et.SiafundInputOutputs = append(et.SiafundInputOutputs, sco)
	}

	for i := range txn.SiafundOutputs {
		et.SiafundOutputIDs = append(et.SiafundOutputIDs, txn.SiafundOutputID(uint64(i)))
	}

	for _, sfi := range txn.SiafundInputs {
		et.SiaClaimOutputIDs = append(et.SiaClaimOutputIDs, sfi.ParentID.SiaClaimOutputID())
	}
	return et
}
Beispiel #4
0
// revise revises fc to cover piece and uploads both the revision and the
// piece data to the host.
func (hu *hostUploader) revise(fc types.FileContract, piece []byte, height types.BlockHeight) error {
	hu.conn.SetDeadline(time.Now().Add(5 * time.Minute)) // sufficient to transfer 4 MB over 100 kbps
	defer hu.conn.SetDeadline(time.Time{})               // reset timeout after each revision

	// calculate new merkle root
	err := hu.tree.ReadSegments(bytes.NewReader(piece))
	if err != nil {
		return err
	}

	// create revision
	rev := types.FileContractRevision{
		ParentID:          hu.contract.ID,
		UnlockConditions:  hu.unlockConditions,
		NewRevisionNumber: fc.RevisionNumber + 1,

		NewFileSize:           fc.FileSize + uint64(len(piece)),
		NewFileMerkleRoot:     hu.tree.Root(),
		NewWindowStart:        fc.WindowStart,
		NewWindowEnd:          fc.WindowEnd,
		NewValidProofOutputs:  fc.ValidProofOutputs,
		NewMissedProofOutputs: fc.MissedProofOutputs,
		NewUnlockHash:         fc.UnlockHash,
	}
	// transfer value of piece from renter to host
	safeDuration := uint64(fc.WindowStart - height + 20) // buffer in case host is behind
	piecePrice := types.NewCurrency64(uint64(len(piece))).Mul(types.NewCurrency64(safeDuration)).Mul(hu.settings.Price)
	// prevent a negative currency panic
	if piecePrice.Cmp(fc.ValidProofOutputs[0].Value) > 0 {
		// probably not enough money, but the host might accept it anyway
		piecePrice = fc.ValidProofOutputs[0].Value
	}
	rev.NewValidProofOutputs[0].Value = rev.NewValidProofOutputs[0].Value.Sub(piecePrice)   // less returned to renter
	rev.NewValidProofOutputs[1].Value = rev.NewValidProofOutputs[1].Value.Add(piecePrice)   // more given to host
	rev.NewMissedProofOutputs[0].Value = rev.NewMissedProofOutputs[0].Value.Sub(piecePrice) // less returned to renter
	rev.NewMissedProofOutputs[1].Value = rev.NewMissedProofOutputs[1].Value.Add(piecePrice) // more given to void

	// create transaction containing the revision
	signedTxn := types.Transaction{
		FileContractRevisions: []types.FileContractRevision{rev},
		TransactionSignatures: []types.TransactionSignature{{
			ParentID:       crypto.Hash(hu.contract.ID),
			CoveredFields:  types.CoveredFields{FileContractRevisions: []uint64{0}},
			PublicKeyIndex: 0, // renter key is always first -- see negotiateContract
		}},
	}

	// sign the transaction
	encodedSig, err := crypto.SignHash(signedTxn.SigHash(0), hu.secretKey)
	if err != nil {
		return err
	}
	signedTxn.TransactionSignatures[0].Signature = encodedSig[:]

	// send the transaction
	if err := encoding.WriteObject(hu.conn, signedTxn); err != nil {
		return err
	}

	// host sends acceptance
	var response string
	if err := encoding.ReadObject(hu.conn, &response, 128); err != nil {
		return err
	}
	if response != modules.AcceptResponse {
		return errors.New("host rejected revision: " + response)
	}

	// transfer piece
	if _, err := hu.conn.Write(piece); err != nil {
		return err
	}

	// read txn signed by host
	var signedHostTxn types.Transaction
	if err := encoding.ReadObject(hu.conn, &signedHostTxn, types.BlockSizeLimit); err != nil {
		return err
	}

	if signedHostTxn.ID() != signedTxn.ID() {
		return errors.New("host sent bad signed transaction")
	} else if err = signedHostTxn.StandaloneValid(height); err != nil {
		return err
	}

	hu.lastTxn = signedHostTxn

	return nil
}
Beispiel #5
0
// addTransaction is called from addBlockDB, and delegates the adding
// of information to the database to the functions defined above
func (tx *boltTx) addTransaction(txn types.Transaction) {
	// Store this for quick lookup
	txid := txn.ID()

	// Append each input to the list of modifications
	for _, input := range txn.SiacoinInputs {
		tx.addAddress(input.UnlockConditions.UnlockHash(), txid)
		tx.addSiacoinInput(input.ParentID, txid)
	}

	// Handle all the transaction outputs
	for i, output := range txn.SiacoinOutputs {
		tx.addAddress(output.UnlockHash, txid)
		tx.addNewOutput(txn.SiacoinOutputID(uint64(i)), txid)
	}

	// Handle each file contract individually
	for i, contract := range txn.FileContracts {
		fcid := txn.FileContractID(uint64(i))
		tx.addNewHash("FileContracts", hashFilecontract, crypto.Hash(fcid), fcInfo{
			Contract: txid,
		})

		for j, output := range contract.ValidProofOutputs {
			tx.addAddress(output.UnlockHash, txid)
			tx.addNewOutput(fcid.StorageProofOutputID(true, uint64(j)), txid)
		}
		for j, output := range contract.MissedProofOutputs {
			tx.addAddress(output.UnlockHash, txid)
			tx.addNewOutput(fcid.StorageProofOutputID(false, uint64(j)), txid)
		}

		tx.addAddress(contract.UnlockHash, txid)
	}

	// Update the list of revisions
	for _, revision := range txn.FileContractRevisions {
		tx.addFcRevision(revision.ParentID, txid)

		// Note the old outputs will still be there in the
		// database. This is to provide information to the
		// people who may just need it.
		for i, output := range revision.NewValidProofOutputs {
			tx.addAddress(output.UnlockHash, txid)
			tx.addNewOutput(revision.ParentID.StorageProofOutputID(true, uint64(i)), txid)
		}
		for i, output := range revision.NewMissedProofOutputs {
			tx.addAddress(output.UnlockHash, txid)
			tx.addNewOutput(revision.ParentID.StorageProofOutputID(false, uint64(i)), txid)
		}

		tx.addAddress(revision.NewUnlockHash, txid)
	}

	// Update the list of storage proofs
	for _, proof := range txn.StorageProofs {
		tx.addFcProof(proof.ParentID, txid)
	}

	// Append all the siafund inputs to the modification list
	for _, input := range txn.SiafundInputs {
		tx.addSiafundInput(input.ParentID, txid)
	}

	// Handle all the siafund outputs
	for i, output := range txn.SiafundOutputs {
		tx.addAddress(output.UnlockHash, txid)
		tx.addNewSFOutput(txn.SiafundOutputID(uint64(i)), txid)

	}

	tx.putObject("Hashes", txid, hashTransaction)
}
Beispiel #6
0
// rpcRevise is an RPC that allows a renter to revise a file contract. It will
// read new revisions in a loop until the renter sends a termination signal.
func (h *Host) rpcRevise(conn net.Conn) error {
	// read ID of contract to be revised
	var fcid types.FileContractID
	if err := encoding.ReadObject(conn, &fcid, crypto.HashSize); err != nil {
		return err
	}
	lockID := h.mu.RLock()
	obligation, exists := h.obligationsByID[fcid]
	h.mu.RUnlock(lockID)
	if !exists {
		return errors.New("no record of that contract")
	}

	// need to protect against two simultaneous revisions to the same
	// contract; this can cause inconsistency and data loss, making storage
	// proofs impossible
	obligation.mu.Lock()
	defer obligation.mu.Unlock()

	// open the file in append mode
	file, err := os.OpenFile(obligation.Path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0660)
	if err != nil {
		return err
	}
	defer func() {
		// if a newly-created file was not updated, remove it
		if stat, _ := file.Stat(); stat.Size() == 0 {
			os.Remove(obligation.Path)
		}
		file.Close()
	}()

	// rebuild current Merkle tree
	tree := crypto.NewTree()
	buf := make([]byte, crypto.SegmentSize)
	for {
		_, err := io.ReadFull(file, buf)
		if err == io.EOF {
			break
		} else if err != nil && err != io.ErrUnexpectedEOF {
			return err
		}
		tree.Push(buf)
	}

	// accept new revisions in a loop. The final good transaction will be
	// submitted to the blockchain.
	var finalTxn types.Transaction
	defer func() {
		h.tpool.AcceptTransactionSet([]types.Transaction{finalTxn})
	}()
	for {
		// read proposed revision
		var revTxn types.Transaction
		if err := encoding.ReadObject(conn, &revTxn, types.BlockSizeLimit); err != nil {
			return err
		}
		// an empty transaction indicates completion
		if revTxn.ID() == (types.Transaction{}).ID() {
			break
		}

		// check revision against original file contract
		lockID = h.mu.RLock()
		err := h.considerRevision(revTxn, obligation)
		h.mu.RUnlock(lockID)
		if err != nil {
			encoding.WriteObject(conn, err.Error())
			continue // don't terminate loop; subsequent revisions may be okay
		}

		// indicate acceptance
		if err := encoding.WriteObject(conn, modules.AcceptResponse); err != nil {
			return err
		}

		// read piece
		// TODO: simultaneously read into tree?
		rev := revTxn.FileContractRevisions[0]
		piece := make([]byte, rev.NewFileSize-obligation.FileContract.FileSize)
		_, err = io.ReadFull(conn, piece)
		if err != nil {
			return err
		}

		// verify Merkle root
		r := bytes.NewReader(piece)
		for {
			_, err := io.ReadFull(r, buf)
			if err == io.EOF {
				break
			} else if err != nil && err != io.ErrUnexpectedEOF {
				return err
			}
			tree.Push(buf)
		}
		if tree.Root() != rev.NewFileMerkleRoot {
			return errors.New("revision has bad Merkle root")
		}

		// manually sign the transaction
		revTxn.TransactionSignatures = append(revTxn.TransactionSignatures, types.TransactionSignature{
			ParentID:       crypto.Hash(fcid),
			CoveredFields:  types.CoveredFields{FileContractRevisions: []uint64{0}},
			PublicKeyIndex: 1, // host key is always second
		})
		encodedSig, err := crypto.SignHash(revTxn.SigHash(1), h.secretKey)
		if err != nil {
			return err
		}
		revTxn.TransactionSignatures[1].Signature = encodedSig[:]

		// send the signed transaction
		if err := encoding.WriteObject(conn, revTxn); err != nil {
			return err
		}

		// append piece to file
		if _, err := file.Write(piece); err != nil {
			return err
		}

		// save updated obligation to disk
		lockID = h.mu.Lock()
		h.spaceRemaining -= int64(len(piece))
		obligation.FileContract.RevisionNumber = rev.NewRevisionNumber
		obligation.FileContract.FileSize = rev.NewFileSize
		obligation.FileContract.FileMerkleRoot = rev.NewFileMerkleRoot
		h.obligationsByID[obligation.ID] = obligation
		heightObligations := h.obligationsByHeight[obligation.FileContract.WindowStart+StorageProofReorgDepth]
		for i := range heightObligations {
			if heightObligations[i].ID == obligation.ID {
				heightObligations[i] = obligation
			}
		}
		h.save()
		h.mu.Unlock(lockID)

		finalTxn = revTxn
	}

	return nil
}
Beispiel #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
}
Beispiel #8
0
// rpcRevise is an RPC that allows a renter to revise a file contract. It will
// read new revisions in a loop until the renter sends a termination signal.
func (h *Host) rpcRevise(conn net.Conn) error {
	// read ID of contract to be revised
	var fcid types.FileContractID
	if err := encoding.ReadObject(conn, &fcid, crypto.HashSize); err != nil {
		return errors.New("couldn't read contract ID: " + err.Error())
	}

	// remove conn deadline while we wait for lock and rebuild the Merkle tree
	conn.SetDeadline(time.Time{})

	h.mu.RLock()
	obligation, exists := h.obligationsByID[fcid]
	h.mu.RUnlock()
	if !exists {
		return errors.New("no record of that contract")
	}

	// need to protect against two simultaneous revisions to the same
	// contract; this can cause inconsistency and data loss, making storage
	// proofs impossible
	obligation.mu.Lock()
	defer obligation.mu.Unlock()

	// open the file in append mode
	file, err := os.OpenFile(obligation.Path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0660)
	if err != nil {
		return err
	}

	// rebuild current Merkle tree
	tree := crypto.NewTree()
	err = tree.ReadSegments(file)
	if err != nil {
		file.Close()
		return err
	}

	// accept new revisions in a loop. The final good transaction will be
	// submitted to the blockchain.
	revisionErr := func() error {
		for {
			// allow 2 minutes between revisions
			conn.SetDeadline(time.Now().Add(2 * time.Minute))

			// read proposed revision
			var revTxn types.Transaction
			if err := encoding.ReadObject(conn, &revTxn, types.BlockSizeLimit); err != nil {
				return errors.New("couldn't read revision: " + err.Error())
			}
			// an empty transaction indicates completion
			if revTxn.ID() == (types.Transaction{}).ID() {
				return nil
			}

			// allow 5 minutes for each revision
			conn.SetDeadline(time.Now().Add(5 * time.Minute))

			// check revision against original file contract
			h.mu.RLock()
			err := h.considerRevision(revTxn, *obligation)
			h.mu.RUnlock()
			if err != nil {
				encoding.WriteObject(conn, err.Error())
				continue // don't terminate loop; subsequent revisions may be okay
			}

			// indicate acceptance
			if err := encoding.WriteObject(conn, modules.AcceptResponse); err != nil {
				return errors.New("couldn't write acceptance: " + err.Error())
			}

			// read piece
			// TODO: simultaneously read into tree and file
			rev := revTxn.FileContractRevisions[0]
			last := obligation.LastRevisionTxn.FileContractRevisions[0]
			piece := make([]byte, rev.NewFileSize-last.NewFileSize)
			_, err = io.ReadFull(conn, piece)
			if err != nil {
				return errors.New("couldn't read piece data: " + err.Error())
			}

			// verify Merkle root
			err = tree.ReadSegments(bytes.NewReader(piece))
			if err != nil {
				return errors.New("couldn't verify Merkle root: " + err.Error())
			}
			if tree.Root() != rev.NewFileMerkleRoot {
				return errors.New("revision has bad Merkle root")
			}

			// manually sign the transaction
			revTxn.TransactionSignatures = append(revTxn.TransactionSignatures, types.TransactionSignature{
				ParentID:       crypto.Hash(fcid),
				CoveredFields:  types.CoveredFields{FileContractRevisions: []uint64{0}},
				PublicKeyIndex: 1, // host key is always second
			})
			encodedSig, err := crypto.SignHash(revTxn.SigHash(1), h.secretKey)
			if err != nil {
				return err
			}
			revTxn.TransactionSignatures[1].Signature = encodedSig[:]

			// send the signed transaction
			if err := encoding.WriteObject(conn, revTxn); err != nil {
				return errors.New("couldn't write signed revision transaction: " + err.Error())
			}

			// append piece to file
			if _, err := file.Write(piece); err != nil {
				return errors.New("couldn't write new data to file: " + err.Error())
			}

			// save updated obligation to disk
			h.mu.Lock()
			obligation.LastRevisionTxn = revTxn
			h.spaceRemaining -= int64(len(piece))
			h.save()
			h.mu.Unlock()
		}
	}()
	file.Close()

	// if a newly-created file was not updated, remove it
	if obligation.LastRevisionTxn.FileContractRevisions[0].NewRevisionNumber == 0 {
		os.Remove(obligation.Path)
		return revisionErr
	}

	err = h.tpool.AcceptTransactionSet([]types.Transaction{obligation.LastRevisionTxn})
	if err != nil {
		h.log.Println("WARN: transaction pool rejected revision transaction: " + err.Error())
	}

	return revisionErr
}