gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/transactionpool/accept.go (about)

     1  package transactionpool
     2  
     3  // TODO: It seems like the transaction pool is not properly detecting conflicts
     4  // between a file contract revision and a file contract.
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"math"
    10  	"time"
    11  
    12  	"gitlab.com/SiaPrime/SiaPrime/build"
    13  	"gitlab.com/SiaPrime/SiaPrime/crypto"
    14  	"gitlab.com/SiaPrime/SiaPrime/encoding"
    15  	"gitlab.com/SiaPrime/SiaPrime/modules"
    16  	"gitlab.com/SiaPrime/SiaPrime/types"
    17  )
    18  
    19  var (
    20  	errEmptySet            = errors.New("transaction set is empty")
    21  	errFullTransactionPool = errors.New("transaction pool cannot accept more transactions")
    22  	errLowMinerFees        = errors.New("transaction set needs more miner fees to be accepted")
    23  	errObjectConflict      = errors.New("transaction set conflicts with an existing transaction set")
    24  )
    25  
    26  // relatedObjectIDs determines all of the object ids related to a transaction.
    27  func relatedObjectIDs(ts []types.Transaction) []ObjectID {
    28  	oidMap := make(map[ObjectID]struct{})
    29  	for _, t := range ts {
    30  		for _, sci := range t.SiacoinInputs {
    31  			oidMap[ObjectID(sci.ParentID)] = struct{}{}
    32  		}
    33  		for i := range t.SiacoinOutputs {
    34  			oidMap[ObjectID(t.SiacoinOutputID(uint64(i)))] = struct{}{}
    35  		}
    36  		for i := range t.FileContracts {
    37  			oidMap[ObjectID(t.FileContractID(uint64(i)))] = struct{}{}
    38  		}
    39  		for _, fcr := range t.FileContractRevisions {
    40  			oidMap[ObjectID(fcr.ParentID)] = struct{}{}
    41  		}
    42  		for _, sp := range t.StorageProofs {
    43  			oidMap[ObjectID(sp.ParentID)] = struct{}{}
    44  		}
    45  		for _, sfi := range t.SiafundInputs {
    46  			oidMap[ObjectID(sfi.ParentID)] = struct{}{}
    47  		}
    48  		for i := range t.SiafundOutputs {
    49  			oidMap[ObjectID(t.SiafundOutputID(uint64(i)))] = struct{}{}
    50  		}
    51  	}
    52  
    53  	var oids []ObjectID
    54  	for oid := range oidMap {
    55  		oids = append(oids, oid)
    56  	}
    57  	return oids
    58  }
    59  
    60  // requiredFeesToExtendTpool returns the amount of fees required to extend the
    61  // transaction pool to fit another transaction set. The amount returned has the
    62  // unit 'currency per byte'.
    63  func (tp *TransactionPool) requiredFeesToExtendTpool() types.Currency {
    64  	// If the transaction pool is nearly empty, it can be extended even if there
    65  	// are no fees.
    66  	if tp.transactionListSize < TransactionPoolSizeForFee {
    67  		return types.ZeroCurrency
    68  	}
    69  
    70  	// Calculate the fee required to bump out the size of the transaction pool.
    71  	ratioToTarget := float64(tp.transactionListSize) / TransactionPoolSizeTarget
    72  	feeFactor := math.Pow(ratioToTarget, TransactionPoolExponentiation)
    73  	return types.SiacoinPrecision.MulFloat(feeFactor).Div64(1000) // Divide by 1000 to get SC / kb
    74  }
    75  
    76  // checkTransactionSetComposition checks if the transaction set is valid given
    77  // the state of the pool. It does not check that each individual transaction
    78  // would be legal in the next block, but does check things like miner fees and
    79  // IsStandard.
    80  func (tp *TransactionPool) checkTransactionSetComposition(ts []types.Transaction) (uint64, error) {
    81  	// Check that the transaction set is not already known.
    82  	setID := TransactionSetID(crypto.HashObject(ts))
    83  	_, exists := tp.transactionSets[setID]
    84  	if exists {
    85  		return 0, modules.ErrDuplicateTransactionSet
    86  	}
    87  
    88  	// All checks after this are expensive.
    89  	//
    90  	// TODO: There is no DoS prevention mechanism in place to prevent repeated
    91  	// expensive verifications of invalid transactions that are created on the
    92  	// fly.
    93  
    94  	// Check that all transactions follow 'Standard.md' guidelines.
    95  	setSize, err := isStandardTransactionSet(ts)
    96  	if err != nil {
    97  		return 0, err
    98  	}
    99  
   100  	return setSize, nil
   101  }
   102  
   103  // handleConflicts detects whether the conflicts in the transaction pool are
   104  // legal children of the new transaction pool set or not.
   105  func (tp *TransactionPool) handleConflicts(ts []types.Transaction, conflicts []TransactionSetID, txnFn func([]types.Transaction) (modules.ConsensusChange, error)) error {
   106  	// Create a list of all the transaction ids that compose the set of
   107  	// conflicts.
   108  	conflictMap := make(map[types.TransactionID]TransactionSetID)
   109  	for _, conflict := range conflicts {
   110  		conflictSet := tp.transactionSets[conflict]
   111  		for _, conflictTxn := range conflictSet {
   112  			conflictMap[conflictTxn.ID()] = conflict
   113  		}
   114  	}
   115  
   116  	// Discard all duplicate transactions from the input transaction set.
   117  	var dedupSet []types.Transaction
   118  	for _, t := range ts {
   119  		_, exists := conflictMap[t.ID()]
   120  		if exists {
   121  			continue
   122  		}
   123  		dedupSet = append(dedupSet, t)
   124  	}
   125  	if len(dedupSet) == 0 {
   126  		return modules.ErrDuplicateTransactionSet
   127  	}
   128  	// If transactions were pruned, it's possible that the set of
   129  	// dependencies/conflicts has also reduced. To minimize computational load
   130  	// on the consensus set, we want to prune out all of the conflicts that are
   131  	// no longer relevant. As an example, consider the transaction set {A}, the
   132  	// set {B}, and the new set {A, C}, where C is dependent on B. {A} and {B}
   133  	// are both conflicts, but after deduplication {A} is no longer a conflict.
   134  	// This is recursive, but it is guaranteed to run only once as the first
   135  	// deduplication is guaranteed to be complete.
   136  	if len(dedupSet) < len(ts) {
   137  		oids := relatedObjectIDs(dedupSet)
   138  		var conflicts []TransactionSetID
   139  		for _, oid := range oids {
   140  			conflict, exists := tp.knownObjects[oid]
   141  			if exists {
   142  				conflicts = append(conflicts, conflict)
   143  			}
   144  		}
   145  		return tp.handleConflicts(dedupSet, conflicts, txnFn)
   146  	}
   147  
   148  	// Merge all of the conflict sets with the input set (input set goes last
   149  	// to preserve dependency ordering), and see if the set as a whole is both
   150  	// small enough to be legal and valid as a set. If no, return an error. If
   151  	// yes, add the new set to the pool, and eliminate the old set. The output
   152  	// diff objects can be repeated, (no need to remove those). Just need to
   153  	// remove the conflicts from tp.transactionSets.
   154  	var superset []types.Transaction
   155  	supersetMap := make(map[TransactionSetID]struct{})
   156  	for _, conflict := range conflictMap {
   157  		supersetMap[conflict] = struct{}{}
   158  	}
   159  	for conflict := range supersetMap {
   160  		superset = append(superset, tp.transactionSets[conflict]...)
   161  	}
   162  	superset = append(superset, dedupSet...)
   163  
   164  	// Check the composition of the transaction set, including fees and
   165  	// IsStandard rules (this is a new set, the rules must be rechecked).
   166  	setSize, err := tp.checkTransactionSetComposition(superset)
   167  	if err != nil {
   168  		return err
   169  	}
   170  
   171  	// Check that the transaction set has enough fees to justify adding it to
   172  	// the transaction list.
   173  	requiredFees := tp.requiredFeesToExtendTpool().Mul64(setSize)
   174  	var setFees types.Currency
   175  	for _, txn := range superset {
   176  		for _, fee := range txn.MinerFees {
   177  			setFees = setFees.Add(fee)
   178  		}
   179  	}
   180  	if requiredFees.Cmp(setFees) > 0 {
   181  		// TODO: check if there is an existing set with lower fees that we can
   182  		// kick out.
   183  		return errLowMinerFees
   184  	}
   185  
   186  	// Check that the transaction set is valid.
   187  	cc, err := txnFn(superset)
   188  	if err != nil {
   189  		return modules.NewConsensusConflict("provided transaction set has prereqs, but is still invalid: " + err.Error())
   190  	}
   191  
   192  	// Remove the conflicts from the transaction pool.
   193  	for conflict := range supersetMap {
   194  		conflictSet := tp.transactionSets[conflict]
   195  		tp.transactionListSize -= len(encoding.Marshal(conflictSet))
   196  		delete(tp.transactionSets, conflict)
   197  		delete(tp.transactionSetDiffs, conflict)
   198  	}
   199  
   200  	// Add the transaction set to the pool.
   201  	setID := TransactionSetID(crypto.HashObject(superset))
   202  	tp.transactionSets[setID] = superset
   203  	for _, diff := range cc.SiacoinOutputDiffs {
   204  		tp.knownObjects[ObjectID(diff.ID)] = setID
   205  	}
   206  	for _, diff := range cc.FileContractDiffs {
   207  		tp.knownObjects[ObjectID(diff.ID)] = setID
   208  	}
   209  	for _, diff := range cc.SiafundOutputDiffs {
   210  		tp.knownObjects[ObjectID(diff.ID)] = setID
   211  	}
   212  	tp.transactionSetDiffs[setID] = &cc
   213  	tsetSize := len(encoding.Marshal(superset))
   214  	tp.transactionListSize += tsetSize
   215  
   216  	// debug logging
   217  	if build.DEBUG {
   218  		txLogs := ""
   219  		for i, t := range superset {
   220  			txLogs += fmt.Sprintf("superset transaction %v size: %vB\n", i, len(encoding.Marshal(t)))
   221  		}
   222  		tp.log.Debugf("accepted transaction superset %v, size: %vB\ntpool size is %vB after accpeting transaction superset\ntransactions: \n%v\n", setID, tsetSize, tp.transactionListSize, txLogs)
   223  	}
   224  
   225  	return nil
   226  }
   227  
   228  // acceptTransactionSet verifies that a transaction set is allowed to be in the
   229  // transaction pool, and then adds it to the transaction pool.
   230  func (tp *TransactionPool) acceptTransactionSet(ts []types.Transaction, txnFn func([]types.Transaction) (modules.ConsensusChange, error)) error {
   231  	if len(ts) == 0 {
   232  		return errEmptySet
   233  	}
   234  
   235  	// Remove all transactions that have been confirmed in the transaction set.
   236  	oldTS := ts
   237  	ts = []types.Transaction{}
   238  	for _, txn := range oldTS {
   239  		if !tp.transactionConfirmed(tp.dbTx, txn.ID()) {
   240  			ts = append(ts, txn)
   241  		}
   242  	}
   243  	// If no transactions remain, return a dublicate error.
   244  	if len(ts) == 0 {
   245  		return modules.ErrDuplicateTransactionSet
   246  	}
   247  
   248  	// Check the composition of the transaction set.
   249  	setSize, err := tp.checkTransactionSetComposition(ts)
   250  	if err != nil {
   251  		return err
   252  	}
   253  
   254  	// Check that the transaction set has enough fees to justify adding it to
   255  	// the transaction list.
   256  	requiredFees := tp.requiredFeesToExtendTpool().Mul64(setSize)
   257  	var setFees types.Currency
   258  	for _, txn := range ts {
   259  		for _, fee := range txn.MinerFees {
   260  			setFees = setFees.Add(fee)
   261  		}
   262  	}
   263  	if requiredFees.Cmp(setFees) > 0 {
   264  		// TODO: check if there is an existing set with lower fees that we can
   265  		// kick out.
   266  		return errLowMinerFees
   267  	}
   268  
   269  	// Check for conflicts with other transactions, which would indicate a
   270  	// double-spend. Legal children of a transaction set will also trigger the
   271  	// conflict-detector.
   272  	oids := relatedObjectIDs(ts)
   273  	var conflicts []TransactionSetID
   274  	for _, oid := range oids {
   275  		conflict, exists := tp.knownObjects[oid]
   276  		if exists {
   277  			conflicts = append(conflicts, conflict)
   278  		}
   279  	}
   280  	if len(conflicts) > 0 {
   281  		return tp.handleConflicts(ts, conflicts, txnFn)
   282  	}
   283  	cc, err := txnFn(ts)
   284  	if err != nil {
   285  		return modules.NewConsensusConflict("provided transaction set is standalone and invalid: " + err.Error())
   286  	}
   287  
   288  	// Add the transaction set to the pool.
   289  	setID := TransactionSetID(crypto.HashObject(ts))
   290  	tp.transactionSets[setID] = ts
   291  	for _, oid := range oids {
   292  		tp.knownObjects[oid] = setID
   293  	}
   294  	tp.transactionSetDiffs[setID] = &cc
   295  	tsetSize := len(encoding.Marshal(ts))
   296  	tp.transactionListSize += tsetSize
   297  	for _, txn := range ts {
   298  		if _, exists := tp.transactionHeights[txn.ID()]; !exists {
   299  			tp.transactionHeights[txn.ID()] = tp.blockHeight
   300  		}
   301  	}
   302  
   303  	// debug logging
   304  	if build.DEBUG {
   305  		txLogs := ""
   306  		for i, t := range ts {
   307  			txLogs += fmt.Sprintf("transaction %v size: %vB\n", i, len(encoding.Marshal(t)))
   308  		}
   309  		tp.log.Debugf("accepted transaction set %v, size: %vB\ntpool size is %vB after accpeting transaction set\ntransactions: \n%v\n", setID, tsetSize, tp.transactionListSize, txLogs)
   310  	}
   311  	return nil
   312  }
   313  
   314  // AcceptTransactionSet adds a transaction to the unconfirmed set of
   315  // transactions. If the transaction is accepted, it will be relayed to
   316  // connected peers.
   317  //
   318  // TODO: Break into component sets when the set gets accepted.
   319  func (tp *TransactionPool) AcceptTransactionSet(ts []types.Transaction) error {
   320  	// assert on consensus set to get special method
   321  	cs, ok := tp.consensusSet.(interface {
   322  		LockedTryTransactionSet(fn func(func(txns []types.Transaction) (modules.ConsensusChange, error)) error) error
   323  	})
   324  	if !ok {
   325  		return errors.New("consensus set does not support LockedTryTransactionSet method")
   326  	}
   327  
   328  	return cs.LockedTryTransactionSet(func(txnFn func(txns []types.Transaction) (modules.ConsensusChange, error)) error {
   329  		tp.log.Debugln("Beginning broadcast of transaction set")
   330  		tp.mu.Lock()
   331  		defer tp.mu.Unlock()
   332  		err := tp.acceptTransactionSet(ts, txnFn)
   333  		if err != nil {
   334  			tp.log.Debugln("Transaction set broadcast has failed:", err)
   335  			return err
   336  		}
   337  		go tp.gateway.Broadcast("RelayTransactionSet", ts, tp.gateway.Peers())
   338  		// Notify subscribers of an accepted transaction set
   339  		tp.updateSubscribersTransactions()
   340  		tp.log.Debugln("Transaction set broadcast appears to have succeeded")
   341  		return nil
   342  	})
   343  }
   344  
   345  // relayTransactionSet is an RPC that accepts a transaction set from a peer. If
   346  // the accept is successful, the transaction will be relayed to the gateway's
   347  // other peers.
   348  func (tp *TransactionPool) relayTransactionSet(conn modules.PeerConn) error {
   349  	if err := tp.tg.Add(); err != nil {
   350  		return err
   351  	}
   352  	defer tp.tg.Done()
   353  	err := conn.SetDeadline(time.Now().Add(relayTransactionSetTimeout))
   354  	if err != nil {
   355  		return err
   356  	}
   357  	// Automatically close the channel when tg.Stop() is called.
   358  	finishedChan := make(chan struct{})
   359  	defer close(finishedChan)
   360  	go func() {
   361  		select {
   362  		case <-tp.tg.StopChan():
   363  		case <-finishedChan:
   364  		}
   365  		conn.Close()
   366  	}()
   367  
   368  	var ts []types.Transaction
   369  	err = encoding.ReadObject(conn, &ts, types.BlockSizeLimit)
   370  	if err != nil {
   371  		return err
   372  	}
   373  
   374  	return tp.AcceptTransactionSet(ts)
   375  }