gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/miner/update.go (about)

     1  package miner
     2  
     3  import (
     4  	"sort"
     5  
     6  	"gitlab.com/SiaPrime/SiaPrime/modules"
     7  	"gitlab.com/SiaPrime/SiaPrime/types"
     8  )
     9  
    10  // addMapElementTxns places the splitSet from a mapElement into the correct
    11  // mapHeap.
    12  func (m *Miner) addMapElementTxns(elem *mapElement) {
    13  	candidateSet := elem.set
    14  
    15  	// Check if heap for highest fee transactions has space.
    16  	if m.blockMapHeap.size+candidateSet.size < types.BlockSizeLimit-5e3 {
    17  		m.pushToBlock(elem)
    18  		return
    19  	}
    20  
    21  	// While the heap cannot fit this set s, and while the (weighted) average
    22  	// fee for the lowest sets from the block is less than the fee for the set
    23  	// s, continue removing from the heap. The block heap doesn't have enough
    24  	// space for this transaction. Check if removing sets from the blockMapHeap
    25  	// will be worth it. bottomSets will hold  the lowest fee sets from the
    26  	// blockMapHeap
    27  	bottomSets := make([]*mapElement, 0)
    28  	var sizeOfBottomSets uint64
    29  	var averageFeeOfBottomSets types.Currency
    30  	for {
    31  		// Check if the candidateSet can fit in the block.
    32  		if m.blockMapHeap.size-sizeOfBottomSets+candidateSet.size < types.BlockSizeLimit-5e3 {
    33  			// Place candidate into block,
    34  			m.pushToBlock(elem)
    35  			// Place transactions removed from block heap into
    36  			// the overflow heap.
    37  			for _, v := range bottomSets {
    38  				m.pushToOverflow(v)
    39  			}
    40  			break
    41  		}
    42  
    43  		// If the blockMapHeap is empty, push all elements removed from it back
    44  		// in, and place the candidate set into the overflow. This should never
    45  		// happen since transaction sets are much smaller than the max block
    46  		// size.
    47  		_, exists := m.blockMapHeap.peek()
    48  		if !exists {
    49  			m.pushToOverflow(elem)
    50  			// Put back in transactions removed.
    51  			for _, v := range bottomSets {
    52  				m.pushToBlock(v)
    53  			}
    54  			// Finished with this candidate set.
    55  			break
    56  		}
    57  		// Add the set to the bottomSets slice. Note that we don't increase
    58  		// sizeOfBottomSets until after calculating the average.
    59  		nextSet := m.popFromBlock()
    60  		bottomSets = append(bottomSets, nextSet)
    61  
    62  		// Calculating fees to compare total fee from those sets removed and the current set s.
    63  		totalFeeFromNextSet := nextSet.set.averageFee.Mul64(nextSet.set.size)
    64  		totalBottomFees := averageFeeOfBottomSets.Mul64(sizeOfBottomSets).Add(totalFeeFromNextSet)
    65  		sizeOfBottomSets += nextSet.set.size
    66  		averageFeeOfBottomSets := totalBottomFees.Div64(sizeOfBottomSets)
    67  
    68  		// If the average fee of the bottom sets from the block is higher than
    69  		// the fee from this candidate set, put the candidate into the overflow
    70  		// MapHeap.
    71  		if averageFeeOfBottomSets.Cmp(candidateSet.averageFee) == 1 {
    72  			// CandidateSet goes into the overflow.
    73  			m.pushToOverflow(elem)
    74  			// Put transaction sets from bottom back into the blockMapHeap.
    75  			for _, v := range bottomSets {
    76  				m.pushToBlock(v)
    77  			}
    78  			// Finished with this candidate set.
    79  			break
    80  		}
    81  	}
    82  }
    83  
    84  // addNewTxns adds new unconfirmed transactions to the miner's transaction
    85  // selection and updates the splitSet and mapElement state of the miner.
    86  func (m *Miner) addNewTxns(diff *modules.TransactionPoolDiff) {
    87  	// Get new splitSets (in form of mapElement)
    88  	newElements := m.getNewSplitSets(diff)
    89  
    90  	// Place each elem in one of the MapHeaps.
    91  	for i := 0; i < len(newElements); i++ {
    92  		// Add splitSet to miner's global state using pointer and ID stored in
    93  		// the mapElement and then add the mapElement to the miner's global
    94  		// state.
    95  		m.splitSets[newElements[i].id] = newElements[i].set
    96  		for _, tx := range newElements[i].set.transactions {
    97  			m.splitSetIDFromTxID[tx.ID()] = newElements[i].id
    98  		}
    99  		m.addMapElementTxns(newElements[i])
   100  	}
   101  }
   102  
   103  // deleteMapElementTxns removes a splitSet (by id) from the miner's mapheaps and
   104  // readjusts the mapheap for the block if needed.
   105  func (m *Miner) deleteMapElementTxns(id splitSetID) {
   106  	_, inBlockMapHeap := m.blockMapHeap.selectID[id]
   107  	_, inOverflowMapHeap := m.overflowMapHeap.selectID[id]
   108  
   109  	// If the transaction set is in the overflow, we can just delete it.
   110  	if inOverflowMapHeap {
   111  		m.overflowMapHeap.removeSetByID(id)
   112  	} else if inBlockMapHeap {
   113  		// Remove from blockMapHeap.
   114  		m.blockMapHeap.removeSetByID(id)
   115  		m.removeSplitSetFromUnsolvedBlock(id)
   116  
   117  		// Promote sets from overflow heap to block if possible.
   118  		for overflowElem, canPromote := m.peekAtOverflow(); canPromote && m.blockMapHeap.size+overflowElem.set.size < types.BlockSizeLimit-5e3; {
   119  			promotedElem := m.popFromOverflow()
   120  			m.pushToBlock(promotedElem)
   121  		}
   122  	}
   123  }
   124  
   125  // deleteReverts deletes transactions from the miner's transaction selection
   126  // which are no longer in the transaction pool.
   127  func (m *Miner) deleteReverts(diff *modules.TransactionPoolDiff) {
   128  	// Delete the sets that are no longer useful. That means recognizing which
   129  	// of your splits belong to the missing sets.
   130  	for _, id := range diff.RevertedTransactions {
   131  		// Look up all of the split sets associated with the set being reverted,
   132  		// and delete them. Then delete the lookups from the list of full sets
   133  		// as well.
   134  		splitSetIndexes := m.fullSets[id]
   135  		for _, ss := range splitSetIndexes {
   136  			m.deleteMapElementTxns(splitSetID(ss))
   137  			delete(m.splitSets, splitSetID(ss))
   138  		}
   139  		delete(m.fullSets, id)
   140  	}
   141  }
   142  
   143  // fixSplitSetOrdering maintains the relative ordering of transactions from a
   144  // split set within the block.
   145  func (m *Miner) fixSplitSetOrdering(id splitSetID) {
   146  	set, _ := m.splitSets[id]
   147  	setTxs := set.transactions
   148  	var setTxIDs []types.TransactionID
   149  	var setTxIndices []int // These are the indices within the unsolved block.
   150  
   151  	// No swapping necessary if there are less than 2 transactions in the set.
   152  	if len(setTxs) < 2 {
   153  		return
   154  	}
   155  
   156  	// Iterate over all transactions in the set and store their txIDs and their
   157  	// indices within the unsoved block.
   158  	for i := 0; i < len(setTxs); i++ {
   159  		txID := setTxs[i].ID()
   160  		setTxIDs = append(setTxIDs, txID)
   161  		setTxIndices = append(setTxIndices, m.unsolvedBlockIndex[txID])
   162  	}
   163  
   164  	// Sort the indices and maintain the sets relative ordering in the block by
   165  	// changing their positions if necessary. The ordering within the set should
   166  	// be exactly the order in which the sets appear in the block.
   167  	sort.Ints(setTxIndices)
   168  	for i := 0; i < len(setTxIDs); i++ {
   169  		index := m.unsolvedBlockIndex[setTxIDs[i]]
   170  		expectedIndex := setTxIndices[i]
   171  		// Put the transaction in the correct position in the block.
   172  		if index != expectedIndex {
   173  			m.persist.UnsolvedBlock.Transactions[expectedIndex] = setTxs[i]
   174  			m.unsolvedBlockIndex[setTxIDs[i]] = expectedIndex
   175  		}
   176  	}
   177  }
   178  
   179  // getNewSplitSets creates split sets from a transaction pool diff, returns them
   180  // in a slice of map elements. Does not update the miner's global state.
   181  func (m *Miner) getNewSplitSets(diff *modules.TransactionPoolDiff) []*mapElement {
   182  	// Split the new sets and add the splits to the list of transactions we pull
   183  	// form.
   184  	newElements := make([]*mapElement, 0)
   185  	for _, newSet := range diff.AppliedTransactions {
   186  		// Split the sets into smaller sets, and add them to the list of
   187  		// transactions the miner can draw from.
   188  		// TODO: Split the one set into a bunch of smaller sets using the cp4p
   189  		// splitter.
   190  		m.setCounter++
   191  		m.fullSets[newSet.ID] = []int{m.setCounter}
   192  		var size uint64
   193  		var totalFees types.Currency
   194  		for i := range newSet.IDs {
   195  			size += newSet.Sizes[i]
   196  			for _, fee := range newSet.Transactions[i].MinerFees {
   197  				totalFees = totalFees.Add(fee)
   198  			}
   199  		}
   200  		// We will check to see if this splitSet belongs in the block.
   201  		s := &splitSet{
   202  			size:         size,
   203  			averageFee:   totalFees.Div64(size),
   204  			transactions: newSet.Transactions,
   205  		}
   206  
   207  		elem := &mapElement{
   208  			set:   s,
   209  			id:    splitSetID(m.setCounter),
   210  			index: 0,
   211  		}
   212  		newElements = append(newElements, elem)
   213  	}
   214  	return newElements
   215  }
   216  
   217  // peekAtBlock checks top of the blockMapHeap, and returns the top element (but
   218  // does not remove it from the heap). Returns false if the heap is empty.
   219  func (m *Miner) peekAtBlock() (*mapElement, bool) {
   220  	return m.blockMapHeap.peek()
   221  }
   222  
   223  // peekAtOverflow checks top of the overflowMapHeap, and returns the top element
   224  // (but does not remove it from the heap). Returns false if the heap is empty.
   225  func (m *Miner) peekAtOverflow() (*mapElement, bool) {
   226  	return m.overflowMapHeap.peek()
   227  }
   228  
   229  // popFromBlock pops an element from the blockMapHeap, removes it from the
   230  // miner's unsolved block, and maintains proper set ordering within the block.
   231  func (m *Miner) popFromBlock() *mapElement {
   232  	elem := m.blockMapHeap.pop()
   233  	m.removeSplitSetFromUnsolvedBlock(elem.id)
   234  	return elem
   235  }
   236  
   237  // popFromBlock pops an element from the overflowMapHeap.
   238  func (m *Miner) popFromOverflow() *mapElement {
   239  	return m.overflowMapHeap.pop()
   240  }
   241  
   242  // pushToBlock pushes a mapElement onto the blockMapHeap and appends it to the
   243  // unsolved block in the miner's global state.
   244  func (m *Miner) pushToBlock(elem *mapElement) {
   245  	m.blockMapHeap.push(elem)
   246  	transactions := elem.set.transactions
   247  
   248  	// Place the transactions from this set into the block and store their indices.
   249  	for i := 0; i < len(transactions); i++ {
   250  		m.unsolvedBlockIndex[transactions[i].ID()] = len(m.persist.UnsolvedBlock.Transactions)
   251  		m.persist.UnsolvedBlock.Transactions = append(m.persist.UnsolvedBlock.Transactions, transactions[i])
   252  	}
   253  }
   254  
   255  // pushToOverflow pushes a mapElement onto the overflowMapHeap.
   256  func (m *Miner) pushToOverflow(elem *mapElement) {
   257  	m.overflowMapHeap.push(elem)
   258  }
   259  
   260  // ProcessConsensusChange will update the miner's most recent block.
   261  func (m *Miner) ProcessConsensusChange(cc modules.ConsensusChange) {
   262  	m.mu.Lock()
   263  	defer m.mu.Unlock()
   264  
   265  	// Update the miner's understanding of the block height.
   266  	for _, block := range cc.RevertedBlocks {
   267  		// Only doing the block check if the height is above zero saves hashing
   268  		// and saves a nontrivial amount of time during IBD.
   269  		if m.persist.Height > 0 || block.ID() != types.GenesisID {
   270  			m.persist.Height--
   271  		} else if m.persist.Height != 0 {
   272  			// Sanity check - if the current block is the genesis block, the
   273  			// miner height should be set to zero.
   274  			m.log.Critical("Miner has detected a genesis block, but the height of the miner is set to ", m.persist.Height)
   275  			m.persist.Height = 0
   276  		}
   277  	}
   278  	for _, block := range cc.AppliedBlocks {
   279  		// Only doing the block check if the height is above zero saves hashing
   280  		// and saves a nontrivial amount of time during IBD.
   281  		if m.persist.Height > 0 || block.ID() != types.GenesisID {
   282  			m.persist.Height++
   283  		} else if m.persist.Height != 0 {
   284  			// Sanity check - if the current block is the genesis block, the
   285  			// miner height should be set to zero.
   286  			m.log.Critical("Miner has detected a genesis block, but the height of the miner is set to ", m.persist.Height)
   287  			m.persist.Height = 0
   288  		}
   289  	}
   290  
   291  	// Update the unsolved block.
   292  	m.persist.UnsolvedBlock.ParentID = cc.AppliedBlocks[len(cc.AppliedBlocks)-1].ID()
   293  	m.persist.Target = cc.ChildTarget
   294  	m.persist.UnsolvedBlock.Timestamp = cc.MinimumValidChildTimestamp
   295  
   296  	// There is a new parent block, the source block should be updated to keep
   297  	// the stale rate as low as possible.
   298  	if cc.Synced {
   299  		m.newSourceBlock()
   300  	}
   301  	m.persist.RecentChange = cc.ID
   302  }
   303  
   304  // ReceiveUpdatedUnconfirmedTransactions will replace the current unconfirmed
   305  // set of transactions with the input transactions.
   306  func (m *Miner) ReceiveUpdatedUnconfirmedTransactions(diff *modules.TransactionPoolDiff) {
   307  	m.mu.Lock()
   308  	defer m.mu.Unlock()
   309  
   310  	m.deleteReverts(diff)
   311  	m.addNewTxns(diff)
   312  }
   313  
   314  // removeSplitSetFromUnsolvedBlock removes a split set from the miner's unsolved
   315  // block.
   316  func (m *Miner) removeSplitSetFromUnsolvedBlock(id splitSetID) {
   317  	transactions := m.splitSets[id].transactions
   318  	// swappedTxs stores transaction IDs for all transactions that are swapped
   319  	// during the process of removing this splitSet.
   320  	swappedTxs := make(map[types.TransactionID]struct{})
   321  
   322  	// Remove each transaction from this set from the block and track the
   323  	// transactions that were moved during that action.
   324  	for i := 0; i < len(transactions); i++ {
   325  		txID := m.removeTxFromUnsolvedBlock(transactions[i].ID())
   326  		swappedTxs[txID] = struct{}{}
   327  	}
   328  
   329  	// setsFixed keeps track of the splitSets which contain swapped transactions
   330  	// and have been checked for having the correct set ordering.
   331  	setsFixed := make(map[splitSetID]struct{})
   332  	// Iterate over all swapped transactions and fix the ordering of their set
   333  	// if necessary.
   334  	for txID := range swappedTxs {
   335  		setID, _ := m.splitSetIDFromTxID[txID]
   336  		_, thisSetFixed := setsFixed[setID]
   337  
   338  		// If this set was already fixed, or if the transaction is from the set
   339  		// being removed we can move on to the next transaction.
   340  		if thisSetFixed || setID == id {
   341  			continue
   342  		}
   343  
   344  		// Fix the set ordering and add the splitSet to the set of fixed sets.
   345  		m.fixSplitSetOrdering(setID)
   346  		setsFixed[setID] = struct{}{}
   347  	}
   348  }
   349  
   350  // removeTxFromUnsolvedBlock removes the given transaction by either swapping it
   351  // with the transaction at the end of the slice or, if the transaction to be
   352  // removed is the last transaction in the block, just shrinking the slice. It
   353  // returns the transaction ID of the last element in the block prior to the
   354  // swap/removal taking place.
   355  func (m *Miner) removeTxFromUnsolvedBlock(id types.TransactionID) types.TransactionID {
   356  	index, _ := m.unsolvedBlockIndex[id]
   357  	length := len(m.persist.UnsolvedBlock.Transactions)
   358  	// Remove this transactionID from the map of indices.
   359  	delete(m.unsolvedBlockIndex, id)
   360  
   361  	// If the transaction is already the last transaction in the block, we can
   362  	// remove it by just shrinking the block.
   363  	if index == length-1 {
   364  		m.persist.UnsolvedBlock.Transactions = m.persist.UnsolvedBlock.Transactions[:length-1]
   365  		return id
   366  	}
   367  
   368  	lastTx := m.persist.UnsolvedBlock.Transactions[length-1]
   369  	lastTxID := lastTx.ID()
   370  	// Swap with the last transaction in the slice, change the miner state to
   371  	// match the new index, and shrink the slice by 1 space.
   372  	m.persist.UnsolvedBlock.Transactions[index] = lastTx
   373  	m.unsolvedBlockIndex[lastTxID] = index
   374  	m.persist.UnsolvedBlock.Transactions = m.persist.UnsolvedBlock.Transactions[:length-1]
   375  	return lastTxID
   376  }