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