Exemple #1
0
// cachedMerkleRoot calculates the root of a set of existing Merkle roots.
func cachedMerkleRoot(roots []crypto.Hash) crypto.Hash {
	tree := crypto.NewCachedTree(sectorHeight) // NOTE: height is not strictly necessary here
	for _, h := range roots {
		tree.Push(h)
	}
	return tree.Root()
}
// verifyRevision checks that the revision pays the host correctly, and that
// the revision does not attempt any malicious or unexpected changes.
func verifyRevision(so *storageObligation, revision types.FileContractRevision, blockHeight types.BlockHeight, newRevenue, newCollateral types.Currency) error {
	// Check that the revision is well-formed.
	if len(revision.NewValidProofOutputs) != 2 || len(revision.NewMissedProofOutputs) != 3 {
		return errInsaneFileContractRevisionOutputCounts
	}

	// Check that the time to finalize and submit the file contract revision
	// has not already passed.
	if so.expiration()-revisionSubmissionBuffer <= blockHeight {
		return errLateRevision
	}

	oldFCR := so.RevisionTransactionSet[len(so.RevisionTransactionSet)-1].FileContractRevisions[0]

	// Check that all non-volatile fields are the same.
	if oldFCR.ParentID != revision.ParentID {
		return errReviseBadParent
	}
	if oldFCR.UnlockConditions.UnlockHash() != revision.UnlockConditions.UnlockHash() {
		return errReviseBadUnlockConditions
	}
	if oldFCR.NewRevisionNumber >= revision.NewRevisionNumber {
		return errReviseBadRevisionNumber
	}
	if revision.NewFileSize != uint64(len(so.SectorRoots))*modules.SectorSize {
		return errReviseBadNewFileSize
	}
	if oldFCR.NewWindowStart != revision.NewWindowStart {
		return errReviseBadNewWindowStart
	}
	if oldFCR.NewWindowEnd != revision.NewWindowEnd {
		return errReviseBadNewWindowEnd
	}
	if oldFCR.NewUnlockHash != revision.NewUnlockHash {
		return errReviseBadUnlockHash
	}

	// The new revenue comes out of the renter's valid outputs.
	if revision.NewValidProofOutputs[0].Value.Add(newRevenue).Cmp(oldFCR.NewValidProofOutputs[0].Value) > 0 {
		return errReviseBadRenterValidOutput
	}
	// The new revenue goes into the host's valid outputs.
	if oldFCR.NewValidProofOutputs[1].Value.Add(newRevenue).Cmp(revision.NewValidProofOutputs[1].Value) < 0 {
		return errReviseBadHostValidOutput
	}
	// The new revenue comes out of the renter's missed outputs.
	if revision.NewMissedProofOutputs[0].Value.Add(newRevenue).Cmp(oldFCR.NewMissedProofOutputs[0].Value) > 0 {
		return errReviseBadRenterMissedOutput
	}
	// The new collateral comes out of the host's missed outputs.
	if revision.NewMissedProofOutputs[1].Value.Add(newCollateral).Cmp(oldFCR.NewMissedProofOutputs[1].Value) < 0 {
		return errReviseBadCollateralDeduction
	}

	// The Merkle root is checked last because it is the most expensive check.
	log2SectorSize := uint64(0)
	for 1<<log2SectorSize < (modules.SectorSize / crypto.SegmentSize) {
		log2SectorSize++
	}
	ct := crypto.NewCachedTree(log2SectorSize)
	for _, root := range so.SectorRoots {
		ct.Push(root)
	}
	expectedMerkleRoot := ct.Root()
	if revision.NewFileMerkleRoot != expectedMerkleRoot {
		return errReviseBadFileMerkleRoot
	}

	return nil
}
// threadedHandleActionItem will look at a storage obligation and determine
// which action is necessary for the storage obligation to succeed.
func (h *Host) threadedHandleActionItem(soid types.FileContractID, wg *sync.WaitGroup) {
	// The calling thread is responsible for calling Add to the thread group.
	defer wg.Done()

	// Lock the storage obligation in question.
	h.managedLockStorageObligation(soid)
	defer func() {
		h.managedUnlockStorageObligation(soid)
	}()

	// Convert the storage obligation id into a storage obligation.
	var err error
	var so storageObligation
	h.mu.RLock()
	blockHeight := h.blockHeight
	err = h.db.View(func(tx *bolt.Tx) error {
		so, err = getStorageObligation(tx, soid)
		return err
	})
	h.mu.RUnlock()
	if err != nil {
		h.log.Println("Could not get storage obligation:", err)
		return
	}

	// Check whether the storage obligation has already been completed.
	if so.ObligationStatus != obligationUnresolved {
		// Storage obligation has already been completed, skip action item.
		return
	}

	// Check whether the file contract has been seen. If not, resubmit and
	// queue another action item. Check for death. (signature should have a
	// kill height)
	if !so.OriginConfirmed {
		// Submit the transaction set again, try to get the transaction
		// confirmed.
		err := h.tpool.AcceptTransactionSet(so.OriginTransactionSet)
		if err != nil {
			h.log.Debugln("Could not get origin transaction set accepted", err)

			// Check if the transaction is invalid with the current consensus set.
			// If so, the transaction is highly unlikely to ever be confirmed, and
			// the storage obligation should be removed. This check should come
			// after logging the errror so that the function can quit.
			//
			// TODO: If the host or tpool is behind consensus, might be difficult
			// to have certainty about the issue. If some but not all of the
			// parents are confirmed, might be some difficulty.
			_, t := err.(modules.ConsensusConflict)
			if t {
				h.log.Println("Consensus conflict on the origin transaction set, id", so.id())
				h.mu.Lock()
				err = h.removeStorageObligation(so, obligationRejected)
				h.mu.Unlock()
				if err != nil {
					h.log.Println("Error removing storage obligation:", err)
				}
				return
			}
		}

		// Queue another action item to check the status of the transaction.
		h.mu.Lock()
		err = h.queueActionItem(h.blockHeight+resubmissionTimeout, so.id())
		h.mu.Unlock()
		if err != nil {
			h.log.Println("Error queuing action item:", err)
		}
	}

	// Check if the file contract revision is ready for submission. Check for death.
	if !so.RevisionConfirmed && len(so.RevisionTransactionSet) > 0 && blockHeight > so.expiration()-revisionSubmissionBuffer {
		// Sanity check - there should be a file contract revision.
		rtsLen := len(so.RevisionTransactionSet)
		if rtsLen < 1 || len(so.RevisionTransactionSet[rtsLen-1].FileContractRevisions) != 1 {
			h.log.Critical("transaction revision marked as unconfirmed, yet there is no transaction revision")
			return
		}

		// Check if the revision has failed to submit correctly.
		if blockHeight > so.expiration() {
			// TODO: Check this error.
			//
			// TODO: this is not quite right, because a previous revision may
			// be confirmed, and the origin transaction may be confirmed, which
			// would confuse the revenue stuff a bit. Might happen frequently
			// due to the dynamic fee pool.
			h.log.Println("Full time has elapsed, but the revision transaction could not be submitted to consensus, id", so.id())
			h.mu.Lock()
			h.removeStorageObligation(so, obligationRejected)
			h.mu.Unlock()
			return
		}

		// Queue another action item to check the status of the transaction.
		h.mu.Lock()
		err := h.queueActionItem(blockHeight+resubmissionTimeout, so.id())
		h.mu.Unlock()
		if err != nil {
			h.log.Println("Error queuing action item:", err)
		}

		// Add a miner fee to the transaction and submit it to the blockchain.
		revisionTxnIndex := len(so.RevisionTransactionSet) - 1
		revisionParents := so.RevisionTransactionSet[:revisionTxnIndex]
		revisionTxn := so.RevisionTransactionSet[revisionTxnIndex]
		builder := h.wallet.RegisterTransaction(revisionTxn, revisionParents)
		_, feeRecommendation := h.tpool.FeeEstimation()
		if so.value().Div64(2).Cmp(feeRecommendation) < 0 {
			// There's no sense submitting the revision if the fee is more than
			// half of the anticipated revenue - fee market went up
			// unexpectedly, and the money that the renter paid to cover the
			// fees is no longer enough.
			return
		}
		txnSize := uint64(len(encoding.MarshalAll(so.RevisionTransactionSet)) + 300)
		requiredFee := feeRecommendation.Mul64(txnSize)
		err = builder.FundSiacoins(requiredFee)
		if err != nil {
			h.log.Println("Error funding transaction fees", err)
		}
		builder.AddMinerFee(requiredFee)
		if err != nil {
			h.log.Println("Error adding miner fees", err)
		}
		feeAddedRevisionTransactionSet, err := builder.Sign(true)
		if err != nil {
			h.log.Println("Error signing transaction", err)
		}
		err = h.tpool.AcceptTransactionSet(feeAddedRevisionTransactionSet)
		if err != nil {
			h.log.Println("Error submitting transaction to transaction pool", err)
		}
		so.TransactionFeesAdded = so.TransactionFeesAdded.Add(requiredFee)
		// return
	}

	// Check whether a storage proof is ready to be provided, and whether it
	// has been accepted. Check for death.
	if !so.ProofConfirmed && blockHeight >= so.expiration()+resubmissionTimeout {
		h.log.Debugln("Host is attempting a storage proof for", so.id())

		// If the window has closed, the host has failed and the obligation can
		// be removed.
		if so.proofDeadline() < blockHeight || len(so.SectorRoots) == 0 {
			h.log.Debugln("storage proof not confirmed by deadline, id", so.id())
			h.mu.Lock()
			err := h.removeStorageObligation(so, obligationFailed)
			h.mu.Unlock()
			if err != nil {
				h.log.Println("Error removing storage obligation:", err)
			}
			return
		}

		// Get the index of the segment, and the index of the sector containing
		// the segment.
		segmentIndex, err := h.cs.StorageProofSegment(so.id())
		if err != nil {
			h.log.Debugln("Host got an error when fetching a storage proof segment:", err)
			return
		}
		sectorIndex := segmentIndex / (modules.SectorSize / crypto.SegmentSize)
		// Pull the corresponding sector into memory.
		sectorRoot := so.SectorRoots[sectorIndex]
		sectorBytes, err := h.ReadSector(sectorRoot)
		if err != nil {
			h.log.Debugln(err)
			return
		}

		// Build the storage proof for just the sector.
		sectorSegment := segmentIndex % (modules.SectorSize / crypto.SegmentSize)
		base, cachedHashSet := crypto.MerkleProof(sectorBytes, sectorSegment)

		// Using the sector, build a cached root.
		log2SectorSize := uint64(0)
		for 1<<log2SectorSize < (modules.SectorSize / crypto.SegmentSize) {
			log2SectorSize++
		}
		ct := crypto.NewCachedTree(log2SectorSize)
		ct.SetIndex(segmentIndex)
		for _, root := range so.SectorRoots {
			ct.Push(root)
		}
		hashSet := ct.Prove(base, cachedHashSet)
		sp := types.StorageProof{
			ParentID: so.id(),
			HashSet:  hashSet,
		}
		copy(sp.Segment[:], base)

		// Create and build the transaction with the storage proof.
		builder := h.wallet.StartTransaction()
		_, feeRecommendation := h.tpool.FeeEstimation()
		if so.value().Cmp(feeRecommendation) < 0 {
			// There's no sense submitting the storage proof if the fee is more
			// than the anticipated revenue.
			h.log.Debugln("Host not submitting storage proof due to a value that does not sufficiently exceed the fee cost")
			return
		}
		txnSize := uint64(len(encoding.Marshal(sp)) + 300)
		requiredFee := feeRecommendation.Mul64(txnSize)
		err = builder.FundSiacoins(requiredFee)
		if err != nil {
			h.log.Println("Host error when funding a storage proof transaction fee:", err)
			return
		}
		builder.AddMinerFee(requiredFee)
		builder.AddStorageProof(sp)
		storageProofSet, err := builder.Sign(true)
		if err != nil {
			h.log.Println("Host error when signing the storage proof transaction:", err)
			return
		}
		err = h.tpool.AcceptTransactionSet(storageProofSet)
		if err != nil {
			h.log.Println("Host unable to submit storage proof transaction to transaction pool:", err)
			return
		}
		so.TransactionFeesAdded = so.TransactionFeesAdded.Add(requiredFee)

		// Queue another action item to check whether there the storage proof
		// got confirmed.
		h.mu.Lock()
		err = h.queueActionItem(so.proofDeadline(), so.id())
		h.mu.Unlock()
		if err != nil {
			h.log.Println("Error queuing action item:", err)
		}
	}

	// Save the storage obligation to account for any fee changes.
	err = h.db.Update(func(tx *bolt.Tx) error {
		soBytes, err := json.Marshal(so)
		if err != nil {
			return err
		}
		return tx.Bucket(bucketStorageObligations).Put(soid[:], soBytes)
	})
	if err != nil {
		h.log.Println("Error updating the storage obligations", err)
	}

	// Check if all items have succeeded with the required confirmations. Report
	// success, delete the obligation.
	if so.ProofConfirmed && blockHeight >= so.proofDeadline() {
		h.log.Println("file contract complete, id", so.id())
		h.mu.Lock()
		h.removeStorageObligation(so, obligationSucceeded)
		h.mu.Unlock()
	}
}