gitlab.com/jokerrs1/Sia@v1.3.2/modules/transactionpool/update.go (about)

     1  package transactionpool
     2  
     3  import (
     4  	"bytes"
     5  	"sort"
     6  
     7  	"github.com/NebulousLabs/Sia/crypto"
     8  	"github.com/NebulousLabs/Sia/modules"
     9  	"github.com/NebulousLabs/Sia/types"
    10  )
    11  
    12  // findSets takes a bunch of transactions (presumably from a block) and finds
    13  // all of the separate transaction sets within it. Set does not check for
    14  // conflicts.
    15  //
    16  // The algorithm goes through one transaction at a time. All of the outputs of
    17  // that transaction are added to the objMap, pointing to the transaction to
    18  // indicate that the transaction contains those outputs. The transaction is
    19  // assigned an integer id (each transaction will have a unique id) and added to
    20  // the txMap.
    21  //
    22  // The transaction's inputs are then checked against the objMap to see if there
    23  // are any parents of the transaction in the graph. If there are, the
    24  // transaction is added to the parent set instead of its own set. If not, the
    25  // transaction is added as its own set.
    26  //
    27  // The forwards map contains a list of ints indicating when a transaction has
    28  // been merged with a set. When a transaction gets merged with a parent set, its
    29  // integer id gets added to the forwards map, indicating that the transaction is
    30  // no longer in its own set, but instead has been merged with other sets.
    31  //
    32  // Some transactions will have parents from multiple distinct sets. If a
    33  // transaction has parents in multiple distinct sets, those sets get merged
    34  // together and the transaction gets added to the result. One of the sets is
    35  // nominated (arbitrarily) as the official set, and the integer id of the other
    36  // set and the new transaction get forwarded to the official set.
    37  //
    38  // TODO: Set merging currently occurs any time that there is a child. But
    39  // really, it should only occur if the child increases the average fee value of
    40  // the set that it is merging with (which it will if and only if it has a higher
    41  // average fee than that set). If the child has multiple parent sets, it should
    42  // be compared with the parent set that has the lowest fee value. Then, after it
    43  // is merged with that parent, the result should be merged with the next
    44  // lowest-fee parent set if and only if the new set has a higher average fee
    45  // than the parent set. And this continues until either all of the sets have
    46  // been merged, or until the remaining parent sets have higher values.
    47  func findSets(ts []types.Transaction) [][]types.Transaction {
    48  	// txMap marks what set each transaction is in. If two sets get combined,
    49  	// this number will not be updated. The 'forwards' map defined further on
    50  	// will help to discover which sets have been combined.
    51  	txMap := make(map[types.TransactionID]int)
    52  	setMap := make(map[int][]types.Transaction)
    53  	objMap := make(map[ObjectID]types.TransactionID)
    54  	forwards := make(map[int]int)
    55  
    56  	// Define a function to follow and collapse any update chain.
    57  	forward := func(prev int) (ret int) {
    58  		ret = prev
    59  		next, exists := forwards[prev]
    60  		for exists {
    61  			ret = next
    62  			forwards[prev] = next // collapse the forwards function to prevent quadratic runtime of findSets.
    63  			next, exists = forwards[next]
    64  		}
    65  		return ret
    66  	}
    67  
    68  	// Add the transactions to the setup one-by-one, merging them as they belong
    69  	// to a set.
    70  	for i, t := range ts {
    71  		// Check if the inputs depend on any previous transaction outputs.
    72  		tid := t.ID()
    73  		parentSets := make(map[int]struct{})
    74  		for _, obj := range t.SiacoinInputs {
    75  			txid, exists := objMap[ObjectID(obj.ParentID)]
    76  			if exists {
    77  				parentSet := forward(txMap[txid])
    78  				parentSets[parentSet] = struct{}{}
    79  			}
    80  		}
    81  		for _, obj := range t.FileContractRevisions {
    82  			txid, exists := objMap[ObjectID(obj.ParentID)]
    83  			if exists {
    84  				parentSet := forward(txMap[txid])
    85  				parentSets[parentSet] = struct{}{}
    86  			}
    87  		}
    88  		for _, obj := range t.StorageProofs {
    89  			txid, exists := objMap[ObjectID(obj.ParentID)]
    90  			if exists {
    91  				parentSet := forward(txMap[txid])
    92  				parentSets[parentSet] = struct{}{}
    93  			}
    94  		}
    95  		for _, obj := range t.SiafundInputs {
    96  			txid, exists := objMap[ObjectID(obj.ParentID)]
    97  			if exists {
    98  				parentSet := forward(txMap[txid])
    99  				parentSets[parentSet] = struct{}{}
   100  			}
   101  		}
   102  
   103  		// Determine the new counter for this transaction.
   104  		if len(parentSets) == 0 {
   105  			// No parent sets. Make a new set for this transaction.
   106  			txMap[tid] = i
   107  			setMap[i] = []types.Transaction{t}
   108  			// Don't need to add anything for the file contract outputs, storage
   109  			// proof outputs, siafund claim outputs; these outputs are not
   110  			// allowed to be spent until 50 confirmations.
   111  		} else {
   112  			// There are parent sets, pick one as the base and then merge the
   113  			// rest into it.
   114  			parentsSlice := make([]int, 0, len(parentSets))
   115  			for j := range parentSets {
   116  				parentsSlice = append(parentsSlice, j)
   117  			}
   118  			base := parentsSlice[0]
   119  			txMap[tid] = base
   120  			for _, j := range parentsSlice[1:] {
   121  				// Forward any future transactions pointing at this set to the
   122  				// base set.
   123  				forwards[j] = base
   124  				// Combine the transactions in this set with the transactions in
   125  				// the base set.
   126  				setMap[base] = append(setMap[base], setMap[j]...)
   127  				// Delete this set map, it has been merged with the base set.
   128  				delete(setMap, j)
   129  			}
   130  			// Add this transaction to the base set.
   131  			setMap[base] = append(setMap[base], t)
   132  		}
   133  
   134  		// Mark this transaction's outputs as potential inputs to future
   135  		// transactions.
   136  		for j := range t.SiacoinOutputs {
   137  			scoid := t.SiacoinOutputID(uint64(j))
   138  			objMap[ObjectID(scoid)] = tid
   139  		}
   140  		for j := range t.FileContracts {
   141  			fcid := t.FileContractID(uint64(j))
   142  			objMap[ObjectID(fcid)] = tid
   143  		}
   144  		for j := range t.FileContractRevisions {
   145  			fcid := t.FileContractRevisions[j].ParentID
   146  			objMap[ObjectID(fcid)] = tid
   147  		}
   148  		for j := range t.SiafundOutputs {
   149  			sfoid := t.SiafundOutputID(uint64(j))
   150  			objMap[ObjectID(sfoid)] = tid
   151  		}
   152  	}
   153  
   154  	// Compile the final group of sets.
   155  	ret := make([][]types.Transaction, 0, len(setMap))
   156  	for _, set := range setMap {
   157  		ret = append(ret, set)
   158  	}
   159  	return ret
   160  }
   161  
   162  // purge removes all transactions from the transaction pool.
   163  func (tp *TransactionPool) purge() {
   164  	tp.knownObjects = make(map[ObjectID]TransactionSetID)
   165  	tp.transactionSets = make(map[TransactionSetID][]types.Transaction)
   166  	tp.transactionSetDiffs = make(map[TransactionSetID]*modules.ConsensusChange)
   167  	tp.transactionListSize = 0
   168  }
   169  
   170  // ProcessConsensusChange gets called to inform the transaction pool of changes
   171  // to the consensus set.
   172  func (tp *TransactionPool) ProcessConsensusChange(cc modules.ConsensusChange) {
   173  	tp.mu.Lock()
   174  
   175  	tp.log.Printf("CCID %v (height %v): %v applied blocks, %v reverted blocks", crypto.Hash(cc.ID).String()[:8], tp.blockHeight, len(cc.AppliedBlocks), len(cc.RevertedBlocks))
   176  
   177  	// Get the recent block ID for a sanity check that the consensus change is
   178  	// being provided to us correctly.
   179  	resetSanityCheck := false
   180  	recentID, err := tp.getRecentBlockID(tp.dbTx)
   181  	if err == errNilRecentBlock {
   182  		// This almost certainly means that the database hasn't been initialized
   183  		// yet with a recent block, meaning the user was previously running
   184  		// v1.3.1 or earlier.
   185  		tp.log.Println("NOTE: Upgrading tpool database to support consensus change verification.")
   186  		resetSanityCheck = true
   187  	} else if err != nil {
   188  		tp.log.Println("ERROR: Could not access recentID from tpool.")
   189  	}
   190  
   191  	// Update the database of confirmed transactions.
   192  	for _, block := range cc.RevertedBlocks {
   193  		// Sanity check - the id of each reverted block should match the recent
   194  		// parent id.
   195  		if block.ID() != recentID && !resetSanityCheck {
   196  			panic("Consensus change series appears to be inconsistent - we are reverting the wrong block.")
   197  		}
   198  		recentID = block.ParentID
   199  
   200  		if tp.blockHeight > 0 || block.ID() != types.GenesisID {
   201  			tp.blockHeight--
   202  		}
   203  		for _, txn := range block.Transactions {
   204  			err := tp.deleteTransaction(tp.dbTx, txn.ID())
   205  			if err != nil {
   206  				tp.log.Println("ERROR: could not delete a transaction:", err)
   207  			}
   208  		}
   209  
   210  		// Pull the transactions out of the fee summary. For estimating only
   211  		// over 10 blocks, it is extremely likely that there will be more
   212  		// applied blocks than reverted blocks, and if there aren't (a height
   213  		// decreasing reorg), there will be more than 10 applied blocks.
   214  		if len(tp.recentMedians) > 0 {
   215  			// Strip out all of the transactions in this block.
   216  			tp.recentMedians = tp.recentMedians[:len(tp.recentMedians)-1]
   217  		}
   218  	}
   219  	for _, block := range cc.AppliedBlocks {
   220  		// Sanity check - the parent id of each block should match the current
   221  		// block id.
   222  		if block.ParentID != recentID && !resetSanityCheck {
   223  			panic("Consensus change series appears to be inconsistent - we are applying the wrong block.")
   224  		}
   225  		recentID = block.ID()
   226  
   227  		if tp.blockHeight > 0 || block.ID() != types.GenesisID {
   228  			tp.blockHeight++
   229  		}
   230  		for _, txn := range block.Transactions {
   231  			err := tp.putTransaction(tp.dbTx, txn.ID())
   232  			if err != nil {
   233  				tp.log.Println("ERROR: could not add a transaction:", err)
   234  			}
   235  		}
   236  
   237  		// Find the median transaction fee for this block.
   238  		type feeSummary struct {
   239  			fee  types.Currency
   240  			size int
   241  		}
   242  		var fees []feeSummary
   243  		var totalSize int
   244  		txnSets := findSets(block.Transactions)
   245  		for _, set := range txnSets {
   246  			// Compile the fees for this set.
   247  			var feeSum types.Currency
   248  			var sizeSum int
   249  			b := new(bytes.Buffer)
   250  			for _, txn := range set {
   251  				txn.MarshalSia(b)
   252  				sizeSum += b.Len()
   253  				b.Reset()
   254  				for _, fee := range txn.MinerFees {
   255  					feeSum = feeSum.Add(fee)
   256  				}
   257  			}
   258  			feeAvg := feeSum.Div64(uint64(sizeSum))
   259  			fees = append(fees, feeSummary{
   260  				fee:  feeAvg,
   261  				size: sizeSum,
   262  			})
   263  			totalSize += sizeSum
   264  		}
   265  		// Add an extra zero-fee tranasction for any unused block space.
   266  		remaining := int(types.BlockSizeLimit) - totalSize
   267  		fees = append(fees, feeSummary{
   268  			fee:  types.ZeroCurrency,
   269  			size: remaining, // fine if remaining is zero.
   270  		})
   271  		// Sort the fees by value and then scroll until the median.
   272  		sort.Slice(fees, func(i, j int) bool {
   273  			return fees[i].fee.Cmp(fees[j].fee) < 0
   274  		})
   275  		var progress int
   276  		for i := range fees {
   277  			progress += fees[i].size
   278  			// Instead of grabbing the full median, look at the 75%-ile. It's
   279  			// going to be cheaper than the 50%-ile, but it still got into a
   280  			// block.
   281  			if uint64(progress) > types.BlockSizeLimit/4 {
   282  				tp.recentMedians = append(tp.recentMedians, fees[i].fee)
   283  				break
   284  			}
   285  		}
   286  
   287  		// If there are more than 10 blocks recorded in the txnsPerBlock, strip
   288  		// off the oldest blocks.
   289  		for len(tp.recentMedians) > blockFeeEstimationDepth {
   290  			tp.recentMedians = tp.recentMedians[1:]
   291  		}
   292  	}
   293  	// Grab the median of the recent medians. Copy to a new slice so the sorting
   294  	// doesn't screw up the slice.
   295  	safeMedians := make([]types.Currency, len(tp.recentMedians))
   296  	copy(safeMedians, tp.recentMedians)
   297  	sort.Slice(safeMedians, func(i, j int) bool {
   298  		return safeMedians[i].Cmp(safeMedians[j]) < 0
   299  	})
   300  	tp.recentMedianFee = safeMedians[len(safeMedians)/2]
   301  
   302  	// Update all the on-disk structures.
   303  	err = tp.putRecentConsensusChange(tp.dbTx, cc.ID)
   304  	if err != nil {
   305  		tp.log.Println("ERROR: could not update the recent consensus change:", err)
   306  	}
   307  	err = tp.putRecentBlockID(tp.dbTx, recentID)
   308  	if err != nil {
   309  		tp.log.Println("ERROR: could not store recent block id:", err)
   310  	}
   311  	err = tp.putBlockHeight(tp.dbTx, tp.blockHeight)
   312  	if err != nil {
   313  		tp.log.Println("ERROR: could not update the block height:", err)
   314  	}
   315  	err = tp.putFeeMedian(tp.dbTx, medianPersist{
   316  		RecentMedians:   tp.recentMedians,
   317  		RecentMedianFee: tp.recentMedianFee,
   318  	})
   319  	if err != nil {
   320  		tp.log.Println("ERROR: could not update the transaction pool median fee information:", err)
   321  	}
   322  
   323  	// Scan the applied blocks for transactions that got accepted. This will
   324  	// help to determine which transactions to remove from the transaction
   325  	// pool. Having this list enables both efficiency improvements and helps to
   326  	// clean out transactions with no dependencies, such as arbitrary data
   327  	// transactions from the host.
   328  	txids := make(map[types.TransactionID]struct{})
   329  	for _, block := range cc.AppliedBlocks {
   330  		for _, txn := range block.Transactions {
   331  			txids[txn.ID()] = struct{}{}
   332  		}
   333  	}
   334  
   335  	// Save all of the current unconfirmed transaction sets into a list.
   336  	var unconfirmedSets [][]types.Transaction
   337  	for _, tSet := range tp.transactionSets {
   338  		// Compile a new transaction set the removes all transactions duplicated
   339  		// in the block. Though mostly handled by the dependency manager in the
   340  		// transaction pool, this should both improve efficiency and will strip
   341  		// out duplicate transactions with no dependencies (arbitrary data only
   342  		// transactions)
   343  		var newTSet []types.Transaction
   344  		for _, txn := range tSet {
   345  			_, exists := txids[txn.ID()]
   346  			if !exists {
   347  				newTSet = append(newTSet, txn)
   348  			}
   349  		}
   350  		unconfirmedSets = append(unconfirmedSets, newTSet)
   351  	}
   352  
   353  	// Purge the transaction pool. Some of the transactions sets may be invalid
   354  	// after the consensus change.
   355  	tp.purge()
   356  
   357  	// prune transactions older than maxTxnAge.
   358  	for i, tSet := range unconfirmedSets {
   359  		var validTxns []types.Transaction
   360  		for _, txn := range tSet {
   361  			seenHeight, seen := tp.transactionHeights[txn.ID()]
   362  			if tp.blockHeight-seenHeight <= maxTxnAge || !seen {
   363  				validTxns = append(validTxns, txn)
   364  			} else {
   365  				delete(tp.transactionHeights, txn.ID())
   366  			}
   367  		}
   368  		unconfirmedSets[i] = validTxns
   369  	}
   370  
   371  	// Scan through the reverted blocks and re-add any transactions that got
   372  	// reverted to the tpool.
   373  	for i := len(cc.RevertedBlocks) - 1; i >= 0; i-- {
   374  		block := cc.RevertedBlocks[i]
   375  		for _, txn := range block.Transactions {
   376  			// Check whether this transaction has already be re-added to the
   377  			// consensus set by the applied blocks.
   378  			_, exists := txids[txn.ID()]
   379  			if exists {
   380  				continue
   381  			}
   382  
   383  			// Try adding the transaction back into the transaction pool.
   384  			tp.acceptTransactionSet([]types.Transaction{txn}, cc.TryTransactionSet) // Error is ignored.
   385  		}
   386  	}
   387  
   388  	// Add all of the unconfirmed transaction sets back to the transaction
   389  	// pool. The ones that are invalid will throw an error and will not be
   390  	// re-added.
   391  	//
   392  	// Accepting a transaction set requires locking the consensus set (to check
   393  	// validity). But, ProcessConsensusChange is only called when the consensus
   394  	// set is already locked, causing a deadlock problem. Therefore,
   395  	// transactions are readded to the pool in a goroutine, so that this
   396  	// function can finish and consensus can unlock. The tpool lock is held
   397  	// however until the goroutine completes.
   398  	//
   399  	// Which means that no other modules can require a tpool lock when
   400  	// processing consensus changes. Overall, the locking is pretty fragile and
   401  	// more rules need to be put in place.
   402  	for _, set := range unconfirmedSets {
   403  		for _, txn := range set {
   404  			err := tp.acceptTransactionSet([]types.Transaction{txn}, cc.TryTransactionSet)
   405  			if err != nil {
   406  				// The transaction is no longer valid, delete it from the
   407  				// heights map to prevent a memory leak.
   408  				delete(tp.transactionHeights, txn.ID())
   409  			}
   410  		}
   411  	}
   412  
   413  	// Inform subscribers that an update has executed.
   414  	tp.mu.Demote()
   415  	tp.updateSubscribersTransactions()
   416  	tp.mu.DemotedUnlock()
   417  }
   418  
   419  // PurgeTransactionPool deletes all transactions from the transaction pool.
   420  func (tp *TransactionPool) PurgeTransactionPool() {
   421  	tp.mu.Lock()
   422  	tp.purge()
   423  	tp.mu.Unlock()
   424  }