github.com/NebulousLabs/Sia@v1.3.7/modules/consensus/validtransaction.go (about)

     1  package consensus
     2  
     3  import (
     4  	"errors"
     5  	"math/big"
     6  
     7  	"github.com/NebulousLabs/Sia/build"
     8  	"github.com/NebulousLabs/Sia/crypto"
     9  	"github.com/NebulousLabs/Sia/encoding"
    10  	"github.com/NebulousLabs/Sia/modules"
    11  	"github.com/NebulousLabs/Sia/types"
    12  
    13  	"github.com/coreos/bbolt"
    14  )
    15  
    16  var (
    17  	errAlteredRevisionPayouts     = errors.New("file contract revision has altered payout volume")
    18  	errInvalidStorageProof        = errors.New("provided storage proof is invalid")
    19  	errLateRevision               = errors.New("file contract revision submitted after deadline")
    20  	errLowRevisionNumber          = errors.New("transaction has a file contract with an outdated revision number")
    21  	errMissingSiacoinOutput       = errors.New("transaction spends a nonexisting siacoin output")
    22  	errMissingSiafundOutput       = errors.New("transaction spends a nonexisting siafund output")
    23  	errSiacoinInputOutputMismatch = errors.New("siacoin inputs do not equal siacoin outputs for transaction")
    24  	errSiafundInputOutputMismatch = errors.New("siafund inputs do not equal siafund outputs for transaction")
    25  	errUnfinishedFileContract     = errors.New("file contract window has not yet openend")
    26  	errUnrecognizedFileContractID = errors.New("cannot fetch storage proof segment for unknown file contract")
    27  	errWrongUnlockConditions      = errors.New("transaction contains incorrect unlock conditions")
    28  )
    29  
    30  // validSiacoins checks that the siacoin inputs and outputs are valid in the
    31  // context of the current consensus set.
    32  func validSiacoins(tx *bolt.Tx, t types.Transaction) error {
    33  	scoBucket := tx.Bucket(SiacoinOutputs)
    34  	var inputSum types.Currency
    35  	for _, sci := range t.SiacoinInputs {
    36  		// Check that the input spends an existing output.
    37  		scoBytes := scoBucket.Get(sci.ParentID[:])
    38  		if scoBytes == nil {
    39  			return errMissingSiacoinOutput
    40  		}
    41  
    42  		// Check that the unlock conditions match the required unlock hash.
    43  		var sco types.SiacoinOutput
    44  		err := encoding.Unmarshal(scoBytes, &sco)
    45  		if build.DEBUG && err != nil {
    46  			panic(err)
    47  		}
    48  		if sci.UnlockConditions.UnlockHash() != sco.UnlockHash {
    49  			return errWrongUnlockConditions
    50  		}
    51  
    52  		inputSum = inputSum.Add(sco.Value)
    53  	}
    54  	if !inputSum.Equals(t.SiacoinOutputSum()) {
    55  		return errSiacoinInputOutputMismatch
    56  	}
    57  	return nil
    58  }
    59  
    60  // storageProofSegment returns the index of the segment that needs to be proven
    61  // exists in a file contract.
    62  func storageProofSegment(tx *bolt.Tx, fcid types.FileContractID) (uint64, error) {
    63  	// Check that the parent file contract exists.
    64  	fcBucket := tx.Bucket(FileContracts)
    65  	fcBytes := fcBucket.Get(fcid[:])
    66  	if fcBytes == nil {
    67  		return 0, errUnrecognizedFileContractID
    68  	}
    69  
    70  	// Decode the file contract.
    71  	var fc types.FileContract
    72  	err := encoding.Unmarshal(fcBytes, &fc)
    73  	if build.DEBUG && err != nil {
    74  		panic(err)
    75  	}
    76  
    77  	// Get the trigger block id.
    78  	blockPath := tx.Bucket(BlockPath)
    79  	triggerHeight := fc.WindowStart - 1
    80  	if triggerHeight > blockHeight(tx) {
    81  		return 0, errUnfinishedFileContract
    82  	}
    83  	var triggerID types.BlockID
    84  	copy(triggerID[:], blockPath.Get(encoding.EncUint64(uint64(triggerHeight))))
    85  
    86  	// Get the index by appending the file contract ID to the trigger block and
    87  	// taking the hash, then converting the hash to a numerical value and
    88  	// modding it against the number of segments in the file. The result is a
    89  	// random number in range [0, numSegments]. The probability is very
    90  	// slightly weighted towards the beginning of the file, but because the
    91  	// size difference between the number of segments and the random number
    92  	// being modded, the difference is too small to make any practical
    93  	// difference.
    94  	seed := crypto.HashAll(triggerID, fcid)
    95  	numSegments := int64(crypto.CalculateLeaves(fc.FileSize))
    96  	seedInt := new(big.Int).SetBytes(seed[:])
    97  	index := seedInt.Mod(seedInt, big.NewInt(numSegments)).Uint64()
    98  	return index, nil
    99  }
   100  
   101  // validStorageProofsPre100e3 runs the code that was running before height
   102  // 100e3, which contains a hardforking bug, fixed at block 100e3.
   103  //
   104  // HARDFORK 100,000
   105  //
   106  // Originally, it was impossible to provide a storage proof for data of length
   107  // zero. A hardfork was added triggering at block 100,000 to enable an
   108  // optimization where hosts could submit empty storage proofs for files of size
   109  // 0, saving space on the blockchain in conditions where the renter is content.
   110  func validStorageProofs100e3(tx *bolt.Tx, t types.Transaction) error {
   111  	for _, sp := range t.StorageProofs {
   112  		// Check that the storage proof itself is valid.
   113  		segmentIndex, err := storageProofSegment(tx, sp.ParentID)
   114  		if err != nil {
   115  			return err
   116  		}
   117  
   118  		fc, err := getFileContract(tx, sp.ParentID)
   119  		if err != nil {
   120  			return err
   121  		}
   122  		leaves := crypto.CalculateLeaves(fc.FileSize)
   123  		segmentLen := uint64(crypto.SegmentSize)
   124  		if segmentIndex == leaves-1 {
   125  			segmentLen = fc.FileSize % crypto.SegmentSize
   126  		}
   127  
   128  		// HARDFORK 21,000
   129  		//
   130  		// Originally, the code used the entire segment to verify the
   131  		// correctness of the storage proof. This made the code incompatible
   132  		// with data sizes that did not fill an entire segment.
   133  		//
   134  		// This was patched with a hardfork in block 21,000. The new code made
   135  		// it possible to perform successful storage proofs on the final
   136  		// segment of a file if the final segment was not crypto.SegmentSize
   137  		// bytes.
   138  		//
   139  		// Unfortunately, a new bug was introduced where storage proofs on the
   140  		// final segment would fail if the final segment was selected and was
   141  		// crypto.SegmentSize bytes, because the segmentLen would be set to 0
   142  		// instead of crypto.SegmentSize, due to an error with the modulus
   143  		// math. This new error has been fixed with the block 100,000 hardfork.
   144  		if (build.Release == "standard" && blockHeight(tx) < 21e3) || (build.Release == "testing" && blockHeight(tx) < 10) {
   145  			segmentLen = uint64(crypto.SegmentSize)
   146  		}
   147  
   148  		verified := crypto.VerifySegment(
   149  			sp.Segment[:segmentLen],
   150  			sp.HashSet,
   151  			leaves,
   152  			segmentIndex,
   153  			fc.FileMerkleRoot,
   154  		)
   155  		if !verified {
   156  			return errInvalidStorageProof
   157  		}
   158  	}
   159  
   160  	return nil
   161  }
   162  
   163  // validStorageProofs checks that the storage proofs are valid in the context
   164  // of the consensus set.
   165  func validStorageProofs(tx *bolt.Tx, t types.Transaction) error {
   166  	if (build.Release == "standard" && blockHeight(tx) < 100e3) || (build.Release == "testing" && blockHeight(tx) < 10) {
   167  		return validStorageProofs100e3(tx, t)
   168  	}
   169  
   170  	for _, sp := range t.StorageProofs {
   171  		// Check that the storage proof itself is valid.
   172  		segmentIndex, err := storageProofSegment(tx, sp.ParentID)
   173  		if err != nil {
   174  			return err
   175  		}
   176  
   177  		fc, err := getFileContract(tx, sp.ParentID)
   178  		if err != nil {
   179  			return err
   180  		}
   181  		leaves := crypto.CalculateLeaves(fc.FileSize)
   182  		segmentLen := uint64(crypto.SegmentSize)
   183  
   184  		// If this segment chosen is the final segment, it should only be as
   185  		// long as necessary to complete the filesize.
   186  		if segmentIndex == leaves-1 {
   187  			segmentLen = fc.FileSize % crypto.SegmentSize
   188  		}
   189  		if segmentLen == 0 {
   190  			segmentLen = uint64(crypto.SegmentSize)
   191  		}
   192  
   193  		verified := crypto.VerifySegment(
   194  			sp.Segment[:segmentLen],
   195  			sp.HashSet,
   196  			leaves,
   197  			segmentIndex,
   198  			fc.FileMerkleRoot,
   199  		)
   200  		if !verified && fc.FileSize > 0 {
   201  			return errInvalidStorageProof
   202  		}
   203  	}
   204  
   205  	return nil
   206  }
   207  
   208  // validFileContractRevision checks that each file contract revision is valid
   209  // in the context of the current consensus set.
   210  func validFileContractRevisions(tx *bolt.Tx, t types.Transaction) error {
   211  	for _, fcr := range t.FileContractRevisions {
   212  		fc, err := getFileContract(tx, fcr.ParentID)
   213  		if err != nil {
   214  			return err
   215  		}
   216  
   217  		// Check that the height is less than fc.WindowStart - revisions are
   218  		// not allowed to be submitted once the storage proof window has
   219  		// opened.  This reduces complexity for unconfirmed transactions.
   220  		if blockHeight(tx) > fc.WindowStart {
   221  			return errLateRevision
   222  		}
   223  
   224  		// Check that the revision number of the revision is greater than the
   225  		// revision number of the existing file contract.
   226  		if fc.RevisionNumber >= fcr.NewRevisionNumber {
   227  			return errLowRevisionNumber
   228  		}
   229  
   230  		// Check that the unlock conditions match the unlock hash.
   231  		if fcr.UnlockConditions.UnlockHash() != fc.UnlockHash {
   232  			return errWrongUnlockConditions
   233  		}
   234  
   235  		// Check that the payout of the revision matches the payout of the
   236  		// original, and that the payouts match each other.
   237  		var validPayout, missedPayout, oldPayout types.Currency
   238  		for _, output := range fcr.NewValidProofOutputs {
   239  			validPayout = validPayout.Add(output.Value)
   240  		}
   241  		for _, output := range fcr.NewMissedProofOutputs {
   242  			missedPayout = missedPayout.Add(output.Value)
   243  		}
   244  		for _, output := range fc.ValidProofOutputs {
   245  			oldPayout = oldPayout.Add(output.Value)
   246  		}
   247  		if !validPayout.Equals(oldPayout) {
   248  			return errAlteredRevisionPayouts
   249  		}
   250  		if !missedPayout.Equals(oldPayout) {
   251  			return errAlteredRevisionPayouts
   252  		}
   253  	}
   254  	return nil
   255  }
   256  
   257  // validSiafunds checks that the siafund portions of the transaction are valid
   258  // in the context of the consensus set.
   259  func validSiafunds(tx *bolt.Tx, t types.Transaction) (err error) {
   260  	// Compare the number of input siafunds to the output siafunds.
   261  	var siafundInputSum types.Currency
   262  	var siafundOutputSum types.Currency
   263  	for _, sfi := range t.SiafundInputs {
   264  		sfo, err := getSiafundOutput(tx, sfi.ParentID)
   265  		if err != nil {
   266  			return err
   267  		}
   268  
   269  		// Check the unlock conditions match the unlock hash.
   270  		if sfi.UnlockConditions.UnlockHash() != sfo.UnlockHash {
   271  			return errWrongUnlockConditions
   272  		}
   273  
   274  		siafundInputSum = siafundInputSum.Add(sfo.Value)
   275  	}
   276  	for _, sfo := range t.SiafundOutputs {
   277  		siafundOutputSum = siafundOutputSum.Add(sfo.Value)
   278  	}
   279  	if !siafundOutputSum.Equals(siafundInputSum) {
   280  		return errSiafundInputOutputMismatch
   281  	}
   282  	return
   283  }
   284  
   285  // validTransaction checks that all fields are valid within the current
   286  // consensus state. If not an error is returned.
   287  func validTransaction(tx *bolt.Tx, t types.Transaction) error {
   288  	// StandaloneValid will check things like signatures and properties that
   289  	// should be inherent to the transaction. (storage proof rules, etc.)
   290  	err := t.StandaloneValid(blockHeight(tx))
   291  	if err != nil {
   292  		return err
   293  	}
   294  
   295  	// Check that each portion of the transaction is legal given the current
   296  	// consensus set.
   297  	err = validSiacoins(tx, t)
   298  	if err != nil {
   299  		return err
   300  	}
   301  	err = validStorageProofs(tx, t)
   302  	if err != nil {
   303  		return err
   304  	}
   305  	err = validFileContractRevisions(tx, t)
   306  	if err != nil {
   307  		return err
   308  	}
   309  	err = validSiafunds(tx, t)
   310  	if err != nil {
   311  		return err
   312  	}
   313  	return nil
   314  }
   315  
   316  // tryTransactionSet applies the input transactions to the consensus set to
   317  // determine if they are valid. An error is returned IFF they are not a valid
   318  // set in the current consensus set. The size of the transactions and the set
   319  // is not checked. After the transactions have been validated, a consensus
   320  // change is returned detailing the diffs that the transactions set would have.
   321  func (cs *ConsensusSet) tryTransactionSet(txns []types.Transaction) (modules.ConsensusChange, error) {
   322  	// applyTransaction will apply the diffs from a transaction and store them
   323  	// in a block node. diffHolder is the blockNode that tracks the temporary
   324  	// changes. At the end of the function, all changes that were made to the
   325  	// consensus set get reverted.
   326  	diffHolder := new(processedBlock)
   327  
   328  	// Boltdb will only roll back a tx if an error is returned. In the case of
   329  	// TryTransactionSet, we want to roll back the tx even if there is no
   330  	// error. So errSuccess is returned. An alternate method would be to
   331  	// manually manage the tx instead of using 'Update', but that has safety
   332  	// concerns and is more difficult to implement correctly.
   333  	errSuccess := errors.New("success")
   334  	err := cs.db.Update(func(tx *bolt.Tx) error {
   335  		diffHolder.Height = blockHeight(tx)
   336  		for _, txn := range txns {
   337  			err := validTransaction(tx, txn)
   338  			if err != nil {
   339  				return err
   340  			}
   341  			applyTransaction(tx, diffHolder, txn)
   342  		}
   343  		return errSuccess
   344  	})
   345  	if err != errSuccess {
   346  		return modules.ConsensusChange{}, err
   347  	}
   348  	cc := modules.ConsensusChange{
   349  		SiacoinOutputDiffs:        diffHolder.SiacoinOutputDiffs,
   350  		FileContractDiffs:         diffHolder.FileContractDiffs,
   351  		SiafundOutputDiffs:        diffHolder.SiafundOutputDiffs,
   352  		DelayedSiacoinOutputDiffs: diffHolder.DelayedSiacoinOutputDiffs,
   353  		SiafundPoolDiffs:          diffHolder.SiafundPoolDiffs,
   354  	}
   355  	return cc, nil
   356  }
   357  
   358  // TryTransactionSet applies the input transactions to the consensus set to
   359  // determine if they are valid. An error is returned IFF they are not a valid
   360  // set in the current consensus set. The size of the transactions and the set
   361  // is not checked. After the transactions have been validated, a consensus
   362  // change is returned detailing the diffs that the transactions set would have.
   363  func (cs *ConsensusSet) TryTransactionSet(txns []types.Transaction) (modules.ConsensusChange, error) {
   364  	err := cs.tg.Add()
   365  	if err != nil {
   366  		return modules.ConsensusChange{}, err
   367  	}
   368  	defer cs.tg.Done()
   369  	cs.mu.RLock()
   370  	defer cs.mu.RUnlock()
   371  	return cs.tryTransactionSet(txns)
   372  }
   373  
   374  // LockedTryTransactionSet calls fn while under read-lock, passing it a
   375  // version of TryTransactionSet that can be called under read-lock. This fixes
   376  // an edge case in the transaction pool.
   377  func (cs *ConsensusSet) LockedTryTransactionSet(fn func(func(txns []types.Transaction) (modules.ConsensusChange, error)) error) error {
   378  	err := cs.tg.Add()
   379  	if err != nil {
   380  		return err
   381  	}
   382  	defer cs.tg.Done()
   383  	cs.mu.RLock()
   384  	defer cs.mu.RUnlock()
   385  	return fn(cs.tryTransactionSet)
   386  }