Example #1
0
// validStorageProofsPre100e3 runs the code that was running before height
// 100e3, which contains a hardforking bug, fixed at block 100e3.
//
// HARDFORK 100,000
//
// Originally, it was impossible to provide a storage proof for data of length
// zero. A hardfork was added triggering at block 100,000 to enable an
// optimization where hosts could submit empty storage proofs for files of size
// 0, saving space on the blockchain in conditions where the renter is content.
func validStorageProofs100e3(tx *bolt.Tx, t types.Transaction) error {
	for _, sp := range t.StorageProofs {
		// Check that the storage proof itself is valid.
		segmentIndex, err := storageProofSegment(tx, sp.ParentID)
		if err != nil {
			return err
		}

		fc, err := getFileContract(tx, sp.ParentID)
		if err != nil {
			return err
		}
		leaves := crypto.CalculateLeaves(fc.FileSize)
		segmentLen := uint64(crypto.SegmentSize)
		if segmentIndex == leaves-1 {
			segmentLen = fc.FileSize % crypto.SegmentSize
		}

		// HARDFORK 21,000
		//
		// Originally, the code used the entire segment to verify the
		// correctness of the storage proof. This made the code incompatible
		// with data sizes that did not fill an entire segment.
		//
		// This was patched with a hardfork in block 21,000. The new code made
		// it possible to perform successful storage proofs on the final
		// segment of a file if the final segment was not crypto.SegmentSize
		// bytes.
		//
		// Unfortunately, a new bug was introduced where storage proofs on the
		// final segment would fail if the final segment was selected and was
		// crypto.SegmentSize bytes, because the segmentLen would be set to 0
		// instead of crypto.SegmentSize, due to an error with the modulus
		// math. This new error has been fixed with the block 100,000 hardfork.
		if (build.Release == "standard" && blockHeight(tx) < 21e3) || (build.Release == "testing" && blockHeight(tx) < 10) {
			segmentLen = uint64(crypto.SegmentSize)
		}

		verified := crypto.VerifySegment(
			sp.Segment[:segmentLen],
			sp.HashSet,
			leaves,
			segmentIndex,
			fc.FileMerkleRoot,
		)
		if !verified {
			return errInvalidStorageProof
		}
	}

	return nil
}
// storageProofSegment returns the index of the segment that needs to be proven
// exists in a file contract.
func (cs *ConsensusSet) storageProofSegment(fcid types.FileContractID) (index uint64, err error) {
	err = cs.db.View(func(tx *bolt.Tx) error {
		// Check that the parent file contract exists.
		fcBucket := tx.Bucket(FileContracts)
		fcBytes := fcBucket.Get(fcid[:])
		if fcBytes == nil {
			return ErrUnrecognizedFileContractID
		}

		// Decode the file contract.
		var fc types.FileContract
		err := encoding.Unmarshal(fcBytes, &fc)
		if build.DEBUG && err != nil {
			panic(err)
		}

		// Get the trigger block id.
		blockPath := tx.Bucket(BlockPath)
		triggerHeight := fc.WindowStart - 1
		if triggerHeight > types.BlockHeight(blockPath.Stats().KeyN) {
			return ErrUnfinishedFileContract
		}
		var triggerID types.BlockID
		copy(triggerID[:], blockPath.Get(encoding.EncUint64(uint64(triggerHeight))))

		// Get the index by appending the file contract ID to the trigger block and
		// taking the hash, then converting the hash to a numerical value and
		// modding it against the number of segments in the file. The result is a
		// random number in range [0, numSegments]. The probability is very
		// slightly weighted towards the beginning of the file, but because the
		// size difference between the number of segments and the random number
		// being modded, the difference is too small to make any practical
		// difference.
		seed := crypto.HashAll(triggerID, fcid)
		numSegments := int64(crypto.CalculateLeaves(fc.FileSize))
		seedInt := new(big.Int).SetBytes(seed[:])
		index = seedInt.Mod(seedInt, big.NewInt(numSegments)).Uint64()
		return nil
	})
	if err != nil {
		return 0, err
	}
	return index, nil
}
Example #3
0
// validStorageProofs checks that the storage proofs are valid in the context
// of the consensus set.
func validStorageProofs(tx *bolt.Tx, t types.Transaction) error {
	if (build.Release == "standard" && blockHeight(tx) < 100e3) || (build.Release == "testing" && blockHeight(tx) < 10) {
		return validStorageProofs100e3(tx, t)
	}

	for _, sp := range t.StorageProofs {
		// Check that the storage proof itself is valid.
		segmentIndex, err := storageProofSegment(tx, sp.ParentID)
		if err != nil {
			return err
		}

		fc, err := getFileContract(tx, sp.ParentID)
		if err != nil {
			return err
		}
		leaves := crypto.CalculateLeaves(fc.FileSize)
		segmentLen := uint64(crypto.SegmentSize)

		// If this segment chosen is the final segment, it should only be as
		// long as necessary to complete the filesize.
		if segmentIndex == leaves-1 {
			segmentLen = fc.FileSize % crypto.SegmentSize
		}
		if segmentLen == 0 {
			segmentLen = uint64(crypto.SegmentSize)
		}

		verified := crypto.VerifySegment(
			sp.Segment[:segmentLen],
			sp.HashSet,
			leaves,
			segmentIndex,
			fc.FileMerkleRoot,
		)
		if !verified && fc.FileSize > 0 {
			return errInvalidStorageProof
		}
	}

	return nil
}
Example #4
0
// validTxStorageProofs checks that the storage proofs are valid in the context
// of the consensus set.
func (cs *ConsensusSet) validTxStorageProofs(tx *bolt.Tx, t types.Transaction) error {
	for _, sp := range t.StorageProofs {
		// Check that the storage proof itself is valid.
		segmentIndex, err := cs.txStorageProofSegment(tx, sp.ParentID)
		if err != nil {
			return err
		}

		fc, err := getFileContract(tx, sp.ParentID)
		if err != nil {
			return err
		}
		leaves := crypto.CalculateLeaves(fc.FileSize)
		segmentLen := uint64(crypto.SegmentSize)
		if segmentIndex == leaves-1 {
			segmentLen = fc.FileSize % crypto.SegmentSize
		}

		// COMPATv0.4.0
		//
		// Fixing the padding situation resulted in a hardfork. The below code
		// will stop the hardfork from triggering before block 20,000.
		types.CurrentHeightLock.Lock()
		if (build.Release == "standard" && types.CurrentHeight < 21e3) || (build.Release == "testing" && types.CurrentHeight < 10) {
			segmentLen = uint64(crypto.SegmentSize)
		}
		types.CurrentHeightLock.Unlock()

		verified := crypto.VerifySegment(
			sp.Segment[:segmentLen],
			sp.HashSet,
			leaves,
			segmentIndex,
			fc.FileMerkleRoot,
		)
		if !verified {
			return ErrInvalidStorageProof
		}
	}

	return nil
}