github.com/ZuluSpl0it/Sia@v1.3.7/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  	"github.com/NebulousLabs/Sia/build"
    13  	"github.com/NebulousLabs/Sia/crypto"
    14  	"github.com/NebulousLabs/Sia/encoding"
    15  	"github.com/NebulousLabs/Sia/modules"
    16  	"github.com/NebulousLabs/Sia/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  	if err != nil {
   175  		return err
   176  	}
   177  	var setFees types.Currency
   178  	for _, txn := range superset {
   179  		for _, fee := range txn.MinerFees {
   180  			setFees = setFees.Add(fee)
   181  		}
   182  	}
   183  	if requiredFees.Cmp(setFees) > 0 {
   184  		// TODO: check if there is an existing set with lower fees that we can
   185  		// kick out.
   186  		return errLowMinerFees
   187  	}
   188  
   189  	// Check that the transaction set is valid.
   190  	cc, err := txnFn(superset)
   191  	if err != nil {
   192  		return modules.NewConsensusConflict("provided transaction set has prereqs, but is still invalid: " + err.Error())
   193  	}
   194  
   195  	// Remove the conflicts from the transaction pool.
   196  	for conflict := range supersetMap {
   197  		conflictSet := tp.transactionSets[conflict]
   198  		tp.transactionListSize -= len(encoding.Marshal(conflictSet))
   199  		delete(tp.transactionSets, conflict)
   200  		delete(tp.transactionSetDiffs, conflict)
   201  	}
   202  
   203  	// Add the transaction set to the pool.
   204  	setID := TransactionSetID(crypto.HashObject(superset))
   205  	tp.transactionSets[setID] = superset
   206  	for _, diff := range cc.SiacoinOutputDiffs {
   207  		tp.knownObjects[ObjectID(diff.ID)] = setID
   208  	}
   209  	for _, diff := range cc.FileContractDiffs {
   210  		tp.knownObjects[ObjectID(diff.ID)] = setID
   211  	}
   212  	for _, diff := range cc.SiafundOutputDiffs {
   213  		tp.knownObjects[ObjectID(diff.ID)] = setID
   214  	}
   215  	tp.transactionSetDiffs[setID] = &cc
   216  	tsetSize := len(encoding.Marshal(superset))
   217  	tp.transactionListSize += tsetSize
   218  
   219  	// debug logging
   220  	if build.DEBUG {
   221  		txLogs := ""
   222  		for i, t := range superset {
   223  			txLogs += fmt.Sprintf("superset transaction %v size: %vB\n", i, len(encoding.Marshal(t)))
   224  		}
   225  		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)
   226  	}
   227  
   228  	return nil
   229  }
   230  
   231  // acceptTransactionSet verifies that a transaction set is allowed to be in the
   232  // transaction pool, and then adds it to the transaction pool.
   233  func (tp *TransactionPool) acceptTransactionSet(ts []types.Transaction, txnFn func([]types.Transaction) (modules.ConsensusChange, error)) error {
   234  	if len(ts) == 0 {
   235  		return errEmptySet
   236  	}
   237  
   238  	// Remove all transactions that have been confirmed in the transaction set.
   239  	oldTS := ts
   240  	ts = []types.Transaction{}
   241  	for _, txn := range oldTS {
   242  		if !tp.transactionConfirmed(tp.dbTx, txn.ID()) {
   243  			ts = append(ts, txn)
   244  		}
   245  	}
   246  	// If no transactions remain, return a dublicate error.
   247  	if len(ts) == 0 {
   248  		return modules.ErrDuplicateTransactionSet
   249  	}
   250  
   251  	// Check the composition of the transaction set.
   252  	setSize, err := tp.checkTransactionSetComposition(ts)
   253  	if err != nil {
   254  		return err
   255  	}
   256  
   257  	// Check that the transaction set has enough fees to justify adding it to
   258  	// the transaction list.
   259  	requiredFees := tp.requiredFeesToExtendTpool().Mul64(setSize)
   260  	if err != nil {
   261  		return err
   262  	}
   263  	var setFees types.Currency
   264  	for _, txn := range ts {
   265  		for _, fee := range txn.MinerFees {
   266  			setFees = setFees.Add(fee)
   267  		}
   268  	}
   269  	if requiredFees.Cmp(setFees) > 0 {
   270  		// TODO: check if there is an existing set with lower fees that we can
   271  		// kick out.
   272  		return errLowMinerFees
   273  	}
   274  
   275  	// Check for conflicts with other transactions, which would indicate a
   276  	// double-spend. Legal children of a transaction set will also trigger the
   277  	// conflict-detector.
   278  	oids := relatedObjectIDs(ts)
   279  	var conflicts []TransactionSetID
   280  	for _, oid := range oids {
   281  		conflict, exists := tp.knownObjects[oid]
   282  		if exists {
   283  			conflicts = append(conflicts, conflict)
   284  		}
   285  	}
   286  	if len(conflicts) > 0 {
   287  		return tp.handleConflicts(ts, conflicts, txnFn)
   288  	}
   289  	cc, err := txnFn(ts)
   290  	if err != nil {
   291  		return modules.NewConsensusConflict("provided transaction set is standalone and invalid: " + err.Error())
   292  	}
   293  
   294  	// Add the transaction set to the pool.
   295  	setID := TransactionSetID(crypto.HashObject(ts))
   296  	tp.transactionSets[setID] = ts
   297  	for _, oid := range oids {
   298  		tp.knownObjects[oid] = setID
   299  	}
   300  	tp.transactionSetDiffs[setID] = &cc
   301  	tsetSize := len(encoding.Marshal(ts))
   302  	tp.transactionListSize += tsetSize
   303  	for _, txn := range ts {
   304  		if _, exists := tp.transactionHeights[txn.ID()]; !exists {
   305  			tp.transactionHeights[txn.ID()] = tp.blockHeight
   306  		}
   307  	}
   308  
   309  	// debug logging
   310  	if build.DEBUG {
   311  		txLogs := ""
   312  		for i, t := range ts {
   313  			txLogs += fmt.Sprintf("transaction %v size: %vB\n", i, len(encoding.Marshal(t)))
   314  		}
   315  		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)
   316  	}
   317  	return nil
   318  }
   319  
   320  // AcceptTransactionSet adds a transaction to the unconfirmed set of
   321  // transactions. If the transaction is accepted, it will be relayed to
   322  // connected peers.
   323  //
   324  // TODO: Break into component sets when the set gets accepted.
   325  func (tp *TransactionPool) AcceptTransactionSet(ts []types.Transaction) error {
   326  	// assert on consensus set to get special method
   327  	cs, ok := tp.consensusSet.(interface {
   328  		LockedTryTransactionSet(fn func(func(txns []types.Transaction) (modules.ConsensusChange, error)) error) error
   329  	})
   330  	if !ok {
   331  		return errors.New("consensus set does not support LockedTryTransactionSet method")
   332  	}
   333  
   334  	return cs.LockedTryTransactionSet(func(txnFn func(txns []types.Transaction) (modules.ConsensusChange, error)) error {
   335  		tp.log.Debugln("Beginning broadcast of transaction set")
   336  		tp.mu.Lock()
   337  		defer tp.mu.Unlock()
   338  		err := tp.acceptTransactionSet(ts, txnFn)
   339  		if err != nil {
   340  			tp.log.Debugln("Transaction set broadcast has failed:", err)
   341  			return err
   342  		}
   343  		go tp.gateway.Broadcast("RelayTransactionSet", ts, tp.gateway.Peers())
   344  		// Notify subscribers of an accepted transaction set
   345  		tp.updateSubscribersTransactions()
   346  		tp.log.Debugln("Transaction set broadcast appears to have succeeded")
   347  		return nil
   348  	})
   349  }
   350  
   351  // relayTransactionSet is an RPC that accepts a transaction set from a peer. If
   352  // the accept is successful, the transaction will be relayed to the gateway's
   353  // other peers.
   354  func (tp *TransactionPool) relayTransactionSet(conn modules.PeerConn) error {
   355  	if err := tp.tg.Add(); err != nil {
   356  		return err
   357  	}
   358  	defer tp.tg.Done()
   359  	err := conn.SetDeadline(time.Now().Add(relayTransactionSetTimeout))
   360  	if err != nil {
   361  		return err
   362  	}
   363  	// Automatically close the channel when tg.Stop() is called.
   364  	finishedChan := make(chan struct{})
   365  	defer close(finishedChan)
   366  	go func() {
   367  		select {
   368  		case <-tp.tg.StopChan():
   369  		case <-finishedChan:
   370  		}
   371  		conn.Close()
   372  	}()
   373  
   374  	var ts []types.Transaction
   375  	err = encoding.ReadObject(conn, &ts, types.BlockSizeLimit)
   376  	if err != nil {
   377  		return err
   378  	}
   379  
   380  	return tp.AcceptTransactionSet(ts)
   381  }