gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/consensus/validtransaction.go (about)

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