github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/types/validtransaction.go (about)

     1  package types
     2  
     3  // validtransaction.go has functions for checking whether a transaction is
     4  // valid outside of the context of a consensus set. This means checking the
     5  // size of the transaction, the content of the signatures, and a large set of
     6  // other rules that are inherent to how a transaction should be constructed.
     7  
     8  import (
     9  	"errors"
    10  
    11  	"github.com/NebulousLabs/Sia/encoding"
    12  )
    13  
    14  var (
    15  	ErrDoubleSpend                      = errors.New("transaction uses a parent object twice")
    16  	ErrFileContractWindowEndViolation   = errors.New("file contract window must end at least one block after it starts")
    17  	ErrFileContractWindowStartViolation = errors.New("file contract window must start in the future")
    18  	ErrFileContractOutputSumViolation   = errors.New("file contract has invalid output sums")
    19  	ErrNonZeroClaimStart                = errors.New("transaction has a siafund output with a non-zero siafund claim")
    20  	ErrNonZeroRevision                  = errors.New("new file contract has a nonzero revision number")
    21  	ErrStorageProofWithOutputs          = errors.New("transaction has both a storage proof and other outputs")
    22  	ErrTimelockNotSatisfied             = errors.New("timelock has not been met")
    23  	ErrTransactionTooLarge              = errors.New("transaction is too large to fit in a block")
    24  	ErrZeroMinerFee                     = errors.New("transaction has a zero value miner fee")
    25  	ErrZeroOutput                       = errors.New("transaction cannot have an output or payout that has zero value")
    26  	ErrZeroRevision                     = errors.New("transaction has a file contract revision with RevisionNumber=0")
    27  )
    28  
    29  // correctFileContracts checks that the file contracts adhere to the file
    30  // contract rules.
    31  func (t Transaction) correctFileContracts(currentHeight BlockHeight) error {
    32  	// Check that FileContract rules are being followed.
    33  	for _, fc := range t.FileContracts {
    34  		// Check that start and expiration are reasonable values.
    35  		if fc.WindowStart <= currentHeight {
    36  			return ErrFileContractWindowStartViolation
    37  		}
    38  		if fc.WindowEnd <= fc.WindowStart {
    39  			return ErrFileContractWindowEndViolation
    40  		}
    41  
    42  		// Check that the proof outputs sum to the payout after the
    43  		// siafund fee has been applied.
    44  		var validProofOutputSum, missedProofOutputSum Currency
    45  		for _, output := range fc.ValidProofOutputs {
    46  			/* - Future hardforking code.
    47  			if output.Value.IsZero() {
    48  				return ErrZeroOutput
    49  			}
    50  			*/
    51  			validProofOutputSum = validProofOutputSum.Add(output.Value)
    52  		}
    53  		for _, output := range fc.MissedProofOutputs {
    54  			/* - Future hardforking code.
    55  			if output.Value.IsZero() {
    56  				return ErrZeroOutput
    57  			}
    58  			*/
    59  			missedProofOutputSum = missedProofOutputSum.Add(output.Value)
    60  		}
    61  		outputPortion := PostTax(currentHeight, fc.Payout)
    62  		if validProofOutputSum.Cmp(outputPortion) != 0 {
    63  			return ErrFileContractOutputSumViolation
    64  		}
    65  		if missedProofOutputSum.Cmp(outputPortion) != 0 {
    66  			return ErrFileContractOutputSumViolation
    67  		}
    68  	}
    69  	return nil
    70  }
    71  
    72  // correctFileContractRevisions checks that any file contract revisions adhere
    73  // to the revision rules.
    74  func (t Transaction) correctFileContractRevisions(currentHeight BlockHeight) error {
    75  	for _, fcr := range t.FileContractRevisions {
    76  		// Check that start and expiration are reasonable values.
    77  		if fcr.NewWindowStart <= currentHeight {
    78  			return ErrFileContractWindowStartViolation
    79  		}
    80  		if fcr.NewWindowEnd <= fcr.NewWindowStart {
    81  			return ErrFileContractWindowEndViolation
    82  		}
    83  
    84  		// Check that the valid outputs and missed outputs sum to the same
    85  		// value.
    86  		var validProofOutputSum, missedProofOutputSum Currency
    87  		for _, output := range fcr.NewValidProofOutputs {
    88  			/* - Future hardforking code.
    89  			if output.Value.IsZero() {
    90  				return ErrZeroOutput
    91  			}
    92  			*/
    93  			validProofOutputSum = validProofOutputSum.Add(output.Value)
    94  		}
    95  		for _, output := range fcr.NewMissedProofOutputs {
    96  			/* - Future hardforking code.
    97  			if output.Value.IsZero() {
    98  				return ErrZeroOutput
    99  			}
   100  			*/
   101  			missedProofOutputSum = missedProofOutputSum.Add(output.Value)
   102  		}
   103  		if validProofOutputSum.Cmp(missedProofOutputSum) != 0 {
   104  			return ErrFileContractOutputSumViolation
   105  		}
   106  	}
   107  	return nil
   108  }
   109  
   110  // fitsInABlock checks if the transaction is likely to fit in a block.
   111  // Currently there is no limitation on transaction size other than it must fit
   112  // in a block.
   113  func (t Transaction) fitsInABlock() error {
   114  	// Check that the transaction will fit inside of a block, leaving 5kb for
   115  	// overhead.
   116  	if uint64(len(encoding.Marshal(t))) > BlockSizeLimit-5e3 {
   117  		return ErrTransactionTooLarge
   118  	}
   119  	return nil
   120  }
   121  
   122  // followsMinimumValues checks that all outputs adhere to the rules for the
   123  // minimum allowed value (generally 1).
   124  func (t Transaction) followsMinimumValues() error {
   125  	for _, sco := range t.SiacoinOutputs {
   126  		if sco.Value.IsZero() {
   127  			return ErrZeroOutput
   128  		}
   129  	}
   130  	for _, fc := range t.FileContracts {
   131  		if fc.Payout.IsZero() {
   132  			return ErrZeroOutput
   133  		}
   134  	}
   135  	for _, sfo := range t.SiafundOutputs {
   136  		// SiafundOutputs are special in that they have a reserved field, the
   137  		// ClaimStart, which gets sent over the wire but must always be set to
   138  		// 0. The Value must always be greater than 0.
   139  		if !sfo.ClaimStart.IsZero() {
   140  			return ErrNonZeroClaimStart
   141  		}
   142  		if sfo.Value.IsZero() {
   143  			return ErrZeroOutput
   144  		}
   145  	}
   146  	for _, fee := range t.MinerFees {
   147  		if fee.IsZero() {
   148  			return ErrZeroMinerFee
   149  		}
   150  	}
   151  	return nil
   152  }
   153  
   154  // FollowsStorageProofRules checks that a transaction follows the limitations
   155  // placed on transactions that have storage proofs.
   156  func (t Transaction) followsStorageProofRules() error {
   157  	// No storage proofs, no problems.
   158  	if len(t.StorageProofs) == 0 {
   159  		return nil
   160  	}
   161  
   162  	// If there are storage proofs, there can be no siacoin outputs, siafund
   163  	// outputs, new file contracts, or file contract terminations. These
   164  	// restrictions are in place because a storage proof can be invalidated by
   165  	// a simple reorg, which will also invalidate the rest of the transaction.
   166  	// These restrictions minimize blockchain turbulence. These other types
   167  	// cannot be invalidated by a simple reorg, and must instead by replaced by
   168  	// a conflicting transaction.
   169  	if len(t.SiacoinOutputs) != 0 {
   170  		return ErrStorageProofWithOutputs
   171  	}
   172  	if len(t.FileContracts) != 0 {
   173  		return ErrStorageProofWithOutputs
   174  	}
   175  	if len(t.FileContractRevisions) != 0 {
   176  		return ErrStorageProofWithOutputs
   177  	}
   178  	if len(t.SiafundOutputs) != 0 {
   179  		return ErrStorageProofWithOutputs
   180  	}
   181  
   182  	return nil
   183  }
   184  
   185  // noRepeats checks that a transaction does not spend multiple outputs twice,
   186  // submit two valid storage proofs for the same file contract, etc. We
   187  // frivilously check that a file contract termination and storage proof don't
   188  // act on the same file contract. There is very little overhead for doing so,
   189  // and the check is only frivilous because of the current rule that file
   190  // contract terminations are not valid after the proof window opens.
   191  func (t Transaction) noRepeats() error {
   192  	// Check that there are no repeat instances of siacoin outputs, storage
   193  	// proofs, contract terminations, or siafund outputs.
   194  	siacoinInputs := make(map[SiacoinOutputID]struct{})
   195  	for _, sci := range t.SiacoinInputs {
   196  		_, exists := siacoinInputs[sci.ParentID]
   197  		if exists {
   198  			return ErrDoubleSpend
   199  		}
   200  		siacoinInputs[sci.ParentID] = struct{}{}
   201  	}
   202  	doneFileContracts := make(map[FileContractID]struct{})
   203  	for _, sp := range t.StorageProofs {
   204  		_, exists := doneFileContracts[sp.ParentID]
   205  		if exists {
   206  			return ErrDoubleSpend
   207  		}
   208  		doneFileContracts[sp.ParentID] = struct{}{}
   209  	}
   210  	for _, fcr := range t.FileContractRevisions {
   211  		_, exists := doneFileContracts[fcr.ParentID]
   212  		if exists {
   213  			return ErrDoubleSpend
   214  		}
   215  		doneFileContracts[fcr.ParentID] = struct{}{}
   216  	}
   217  	siafundInputs := make(map[SiafundOutputID]struct{})
   218  	for _, sfi := range t.SiafundInputs {
   219  		_, exists := siafundInputs[sfi.ParentID]
   220  		if exists {
   221  			return ErrDoubleSpend
   222  		}
   223  		siafundInputs[sfi.ParentID] = struct{}{}
   224  	}
   225  	return nil
   226  }
   227  
   228  // validUnlockConditions checks that the conditions of uc have been met. The
   229  // height is taken as input so that modules who might be at a different height
   230  // can do the verification without needing to use their own function.
   231  // Additionally, it means that the function does not need to be a method of the
   232  // consensus set.
   233  func validUnlockConditions(uc UnlockConditions, currentHeight BlockHeight) (err error) {
   234  	if uc.Timelock > currentHeight {
   235  		return ErrTimelockNotSatisfied
   236  	}
   237  	return
   238  }
   239  
   240  // validUnlockConditions checks that all of the unlock conditions in the
   241  // transaction are valid.
   242  func (t Transaction) validUnlockConditions(currentHeight BlockHeight) (err error) {
   243  	for _, sci := range t.SiacoinInputs {
   244  		err = validUnlockConditions(sci.UnlockConditions, currentHeight)
   245  		if err != nil {
   246  			return
   247  		}
   248  	}
   249  	for _, fcr := range t.FileContractRevisions {
   250  		err = validUnlockConditions(fcr.UnlockConditions, currentHeight)
   251  		if err != nil {
   252  			return
   253  		}
   254  	}
   255  	for _, sfi := range t.SiafundInputs {
   256  		err = validUnlockConditions(sfi.UnlockConditions, currentHeight)
   257  		if err != nil {
   258  			return
   259  		}
   260  	}
   261  	return
   262  }
   263  
   264  // StandaloneValid returns an error if a transaction is not valid in any
   265  // context, for example if the same output is spent twice in the same
   266  // transaction. StandaloneValid will not check that all outputs being spent are
   267  // legal outputs, as it has no confirmed or unconfirmed set to look at.
   268  func (t Transaction) StandaloneValid(currentHeight BlockHeight) (err error) {
   269  	err = t.fitsInABlock()
   270  	if err != nil {
   271  		return
   272  	}
   273  	err = t.followsStorageProofRules()
   274  	if err != nil {
   275  		return
   276  	}
   277  	err = t.noRepeats()
   278  	if err != nil {
   279  		return
   280  	}
   281  	err = t.followsMinimumValues()
   282  	if err != nil {
   283  		return
   284  	}
   285  	err = t.correctFileContracts(currentHeight)
   286  	if err != nil {
   287  		return
   288  	}
   289  	err = t.correctFileContractRevisions(currentHeight)
   290  	if err != nil {
   291  		return
   292  	}
   293  	err = t.validUnlockConditions(currentHeight)
   294  	if err != nil {
   295  		return
   296  	}
   297  	err = t.validSignatures(currentHeight)
   298  	if err != nil {
   299  		return
   300  	}
   301  	return
   302  }