github.com/nebulouslabs/sia@v1.3.7/modules/transactionpool/update.go (about)

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