github.com/johnathanhowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/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/NebulousLabs/bolt"
    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.Cmp(t.SiacoinOutputSum()) != 0 {
    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  // validStorageProofs checks that the storage proofs are valid in the context
   102  // of the consensus set.
   103  func validStorageProofs(tx *bolt.Tx, t types.Transaction) error {
   104  	for _, sp := range t.StorageProofs {
   105  		// Check that the storage proof itself is valid.
   106  		segmentIndex, err := storageProofSegment(tx, sp.ParentID)
   107  		if err != nil {
   108  			return err
   109  		}
   110  
   111  		fc, err := getFileContract(tx, sp.ParentID)
   112  		if err != nil {
   113  			return err
   114  		}
   115  		leaves := crypto.CalculateLeaves(fc.FileSize)
   116  		segmentLen := uint64(crypto.SegmentSize)
   117  		if segmentIndex == leaves-1 {
   118  			segmentLen = fc.FileSize % crypto.SegmentSize
   119  		}
   120  
   121  		// COMPATv0.4.0
   122  		//
   123  		// Fixing the padding situation resulted in a hardfork. The below code
   124  		// will stop the hardfork from triggering before block 21,000.
   125  		if (build.Release == "standard" && blockHeight(tx) < 21e3) || (build.Release == "testing" && blockHeight(tx) < 10) {
   126  			segmentLen = uint64(crypto.SegmentSize)
   127  		}
   128  
   129  		verified := crypto.VerifySegment(
   130  			sp.Segment[:segmentLen],
   131  			sp.HashSet,
   132  			leaves,
   133  			segmentIndex,
   134  			fc.FileMerkleRoot,
   135  		)
   136  		if !verified {
   137  			return errInvalidStorageProof
   138  		}
   139  	}
   140  
   141  	return nil
   142  }
   143  
   144  // validFileContractRevision checks that each file contract revision is valid
   145  // in the context of the current consensus set.
   146  func validFileContractRevisions(tx *bolt.Tx, t types.Transaction) error {
   147  	for _, fcr := range t.FileContractRevisions {
   148  		fc, err := getFileContract(tx, fcr.ParentID)
   149  		if err != nil {
   150  			return err
   151  		}
   152  
   153  		// Check that the height is less than fc.WindowStart - revisions are
   154  		// not allowed to be submitted once the storage proof window has
   155  		// opened.  This reduces complexity for unconfirmed transactions.
   156  		if blockHeight(tx) > fc.WindowStart {
   157  			return errLateRevision
   158  		}
   159  
   160  		// Check that the revision number of the revision is greater than the
   161  		// revision number of the existing file contract.
   162  		if fc.RevisionNumber >= fcr.NewRevisionNumber {
   163  			return errLowRevisionNumber
   164  		}
   165  
   166  		// Check that the unlock conditions match the unlock hash.
   167  		if fcr.UnlockConditions.UnlockHash() != fc.UnlockHash {
   168  			return errWrongUnlockConditions
   169  		}
   170  
   171  		// Check that the payout of the revision matches the payout of the
   172  		// original, and that the payouts match eachother.
   173  		var validPayout, missedPayout, oldPayout types.Currency
   174  		for _, output := range fcr.NewValidProofOutputs {
   175  			validPayout = validPayout.Add(output.Value)
   176  		}
   177  		for _, output := range fcr.NewMissedProofOutputs {
   178  			missedPayout = missedPayout.Add(output.Value)
   179  		}
   180  		for _, output := range fc.ValidProofOutputs {
   181  			oldPayout = oldPayout.Add(output.Value)
   182  		}
   183  		if validPayout.Cmp(oldPayout) != 0 {
   184  			return errAlteredRevisionPayouts
   185  		}
   186  		if missedPayout.Cmp(oldPayout) != 0 {
   187  			return errAlteredRevisionPayouts
   188  		}
   189  	}
   190  	return nil
   191  }
   192  
   193  // validSiafunds checks that the siafund portions of the transaction are valid
   194  // in the context of the consensus set.
   195  func validSiafunds(tx *bolt.Tx, t types.Transaction) (err error) {
   196  	// Compare the number of input siafunds to the output siafunds.
   197  	var siafundInputSum types.Currency
   198  	var siafundOutputSum types.Currency
   199  	for _, sfi := range t.SiafundInputs {
   200  		sfo, err := getSiafundOutput(tx, sfi.ParentID)
   201  		if err != nil {
   202  			return err
   203  		}
   204  
   205  		// Check the unlock conditions match the unlock hash.
   206  		if sfi.UnlockConditions.UnlockHash() != sfo.UnlockHash {
   207  			return errWrongUnlockConditions
   208  		}
   209  
   210  		siafundInputSum = siafundInputSum.Add(sfo.Value)
   211  	}
   212  	for _, sfo := range t.SiafundOutputs {
   213  		siafundOutputSum = siafundOutputSum.Add(sfo.Value)
   214  	}
   215  	if siafundOutputSum.Cmp(siafundInputSum) != 0 {
   216  		return errSiafundInputOutputMismatch
   217  	}
   218  	return
   219  }
   220  
   221  // validTransaction checks that all fields are valid within the current
   222  // consensus state. If not an error is returned.
   223  func validTransaction(tx *bolt.Tx, t types.Transaction) error {
   224  	// StandaloneValid will check things like signatures and properties that
   225  	// should be inherent to the transaction. (storage proof rules, etc.)
   226  	err := t.StandaloneValid(blockHeight(tx))
   227  	if err != nil {
   228  		return err
   229  	}
   230  
   231  	// Check that each portion of the transaction is legal given the current
   232  	// consensus set.
   233  	err = validSiacoins(tx, t)
   234  	if err != nil {
   235  		return err
   236  	}
   237  	err = validStorageProofs(tx, t)
   238  	if err != nil {
   239  		return err
   240  	}
   241  	err = validFileContractRevisions(tx, t)
   242  	if err != nil {
   243  		return err
   244  	}
   245  	err = validSiafunds(tx, t)
   246  	if err != nil {
   247  		return err
   248  	}
   249  	return nil
   250  }
   251  
   252  // TryTransactionSet applies the input transactions to the consensus set to
   253  // determine if they are valid. An error is returned IFF they are not a valid
   254  // set in the current consensus set. The size of the transactions and the set
   255  // is not checked. After the transactions have been validated, a consensus
   256  // change is returned detailing the diffs that the transaciton set would have.
   257  func (cs *ConsensusSet) TryTransactionSet(txns []types.Transaction) (modules.ConsensusChange, error) {
   258  	cs.mu.RLock()
   259  	defer cs.mu.RUnlock()
   260  
   261  	// applyTransaction will apply the diffs from a transaction and store them
   262  	// in a block node. diffHolder is the blockNode that tracks the temporary
   263  	// changes. At the end of the function, all changes that were made to the
   264  	// consensus set get reverted.
   265  	diffHolder := new(processedBlock)
   266  
   267  	// Boltdb will only roll back a tx if an error is returned. In the case of
   268  	// TryTransactionSet, we want to roll back the tx even if there is no
   269  	// error. So errSuccess is returned. An alternate method would be to
   270  	// manually manage the tx instead of using 'Update', but that has safety
   271  	// concerns and is more difficult to implement correctly.
   272  	errSuccess := errors.New("success")
   273  	err := cs.db.Update(func(tx *bolt.Tx) error {
   274  		diffHolder.Height = blockHeight(tx)
   275  		for _, txn := range txns {
   276  			err := validTransaction(tx, txn)
   277  			if err != nil {
   278  				return err
   279  			}
   280  			applyTransaction(tx, diffHolder, txn)
   281  		}
   282  		return errSuccess
   283  	})
   284  	if err != errSuccess {
   285  		return modules.ConsensusChange{}, err
   286  	}
   287  	cc := modules.ConsensusChange{
   288  		SiacoinOutputDiffs:        diffHolder.SiacoinOutputDiffs,
   289  		FileContractDiffs:         diffHolder.FileContractDiffs,
   290  		SiafundOutputDiffs:        diffHolder.SiafundOutputDiffs,
   291  		DelayedSiacoinOutputDiffs: diffHolder.DelayedSiacoinOutputDiffs,
   292  		SiafundPoolDiffs:          diffHolder.SiafundPoolDiffs,
   293  	}
   294  	return cc, nil
   295  }