github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/modules/miningpool/update.go (about)

     1  package pool
     2  
     3  import (
     4  	"time"
     5  
     6  	"SiaPrime/crypto"
     7  
     8  	"SiaPrime/modules"
     9  	"SiaPrime/types"
    10  )
    11  
    12  // addMapElementTxns places the splitSet from a mapElement into the correct
    13  // mapHeap.
    14  func (p *Pool) addMapElementTxns(elem *mapElement) {
    15  	candidateSet := elem.set
    16  
    17  	// Check if heap for highest fee transactions has space.
    18  	if p.blockMapHeap.size+candidateSet.size < types.BlockSizeLimit-5e3 {
    19  		p.pushToTxnList(elem)
    20  		return
    21  	}
    22  
    23  	// While the heap cannot fit this set s, and while the (weighted) average
    24  	// fee for the lowest sets from the block is less than the fee for the set
    25  	// s, continue removing from the heap. The block heap doesn't have enough
    26  	// space for this transaction. Check if removing sets from the blockMapHeap
    27  	// will be worth it. bottomSets will hold  the lowest fee sets from the
    28  	// blockMapHeap
    29  	bottomSets := make([]*mapElement, 0)
    30  	var sizeOfBottomSets uint64
    31  	var averageFeeOfBottomSets types.Currency
    32  	for {
    33  		// Check if the candidateSet can fit in the block.
    34  		if p.blockMapHeap.size-sizeOfBottomSets+candidateSet.size < types.BlockSizeLimit-5e3 {
    35  			// Place candidate into block,
    36  			p.pushToTxnList(elem)
    37  
    38  			// Place transactions removed from block heap into
    39  			// the overflow heap.
    40  			for _, v := range bottomSets {
    41  				p.pushToOverflow(v)
    42  			}
    43  			break
    44  		}
    45  
    46  		// If the blockMapHeap is empty, push all elements removed from it back
    47  		// in, and place the candidate set into the overflow. This should never
    48  		// happen since transaction sets are much smaller than the max block
    49  		// size.
    50  		_, exists := p.blockMapHeap.peek()
    51  		if !exists {
    52  			p.pushToOverflow(elem)
    53  			// Put back in transactions removed.
    54  			for _, v := range bottomSets {
    55  				p.pushToTxnList(v)
    56  			}
    57  			// Finished with this candidate set.
    58  			break
    59  		}
    60  		// Add the set to the bottomSets slice. Note that we don't increase
    61  		// sizeOfBottomSets until after calculating the average.
    62  		nextSet := p.popFromBlock()
    63  		bottomSets = append(bottomSets, nextSet)
    64  
    65  		// Calculating fees to compare total fee from those sets removed and the current set s.
    66  		totalFeeFromNextSet := nextSet.set.averageFee.Mul64(nextSet.set.size)
    67  		totalBottomFees := averageFeeOfBottomSets.Mul64(sizeOfBottomSets).Add(totalFeeFromNextSet)
    68  		sizeOfBottomSets += nextSet.set.size
    69  		averageFeeOfBottomSets := totalBottomFees.Div64(sizeOfBottomSets)
    70  
    71  		// If the average fee of the bottom sets from the block is higher than
    72  		// the fee from this candidate set, put the candidate into the overflow
    73  		// MapHeap.
    74  		if averageFeeOfBottomSets.Cmp(candidateSet.averageFee) == 1 {
    75  			// CandidateSet goes into the overflow.
    76  			p.pushToOverflow(elem)
    77  			// Put transaction sets from bottom back into the blockMapHeap.
    78  			for _, v := range bottomSets {
    79  				p.pushToTxnList(v)
    80  			}
    81  			// Finished with this candidate set.
    82  			break
    83  		}
    84  	}
    85  }
    86  
    87  // addNewTxns adds new unconfirmed transactions to the pool's transaction
    88  // selection and updates the splitSet and mapElement state of the pool.
    89  func (p *Pool) addNewTxns(diff *modules.TransactionPoolDiff) {
    90  	// Get new splitSets (in form of mapElement)
    91  	newElements := p.getNewSplitSets(diff)
    92  
    93  	// Place each elem in one of the MapHeaps.
    94  	for i := 0; i < len(newElements); i++ {
    95  		// Add splitSet to pool's global state using pointer and ID stored in
    96  		// the mapElement and then add the mapElement to the pool's global
    97  		// state.
    98  		p.splitSets[newElements[i].id] = newElements[i].set
    99  		p.addMapElementTxns(newElements[i])
   100  	}
   101  }
   102  
   103  // deleteMapElementTxns removes a splitSet (by id) from the pool's mapheaps and
   104  // readjusts the mapheap for the block if needed.
   105  func (p *Pool) deleteMapElementTxns(id splitSetID) {
   106  	_, inBlockMapHeap := p.blockMapHeap.selectID[id]
   107  	_, inOverflowMapHeap := p.overflowMapHeap.selectID[id]
   108  
   109  	// If the transaction set is in the overflow, we can just delete it.
   110  	if inOverflowMapHeap {
   111  		p.overflowMapHeap.removeSetByID(id)
   112  	} else if inBlockMapHeap {
   113  		// Remove from blockMapHeap.
   114  		p.blockMapHeap.removeSetByID(id)
   115  		p.removeSplitSetFromTxnList(id)
   116  
   117  		// Promote sets from overflow heap to block if possible.
   118  		for overflowElem, canPromote := p.overflowMapHeap.peek(); canPromote && p.blockMapHeap.size+overflowElem.set.size < types.BlockSizeLimit-5e3; {
   119  			promotedElem := p.popFromOverflow()
   120  			p.pushToTxnList(promotedElem)
   121  		}
   122  	}
   123  	delete(p.splitSets, id)
   124  }
   125  
   126  // deleteReverts deletes transactions from the mining pool's transaction selection
   127  // which are no longer in the transaction pool.
   128  func (p *Pool) deleteReverts(diff *modules.TransactionPoolDiff) {
   129  	// Delete the sets that are no longer useful. That means recognizing which
   130  	// of your splits belong to the missing sets.
   131  	for _, id := range diff.RevertedTransactions {
   132  		// Look up all of the split sets associated with the set being reverted,
   133  		// and delete them. Then delete the lookups from the list of full sets
   134  		// as well.
   135  		splitSetIndexes := p.fullSets[id]
   136  		for _, ss := range splitSetIndexes {
   137  			p.deleteMapElementTxns(splitSetID(ss))
   138  			delete(p.splitSets, splitSetID(ss))
   139  		}
   140  		delete(p.fullSets, id)
   141  	}
   142  }
   143  
   144  // getNewSplitSets creates split sets from a transaction pool diff, returns them
   145  // in a slice of map elements. Does not update the pool's global state.
   146  func (p *Pool) getNewSplitSets(diff *modules.TransactionPoolDiff) []*mapElement {
   147  	// Split the new sets and add the splits to the list of transactions we pull
   148  	// form.
   149  	newElements := make([]*mapElement, 0)
   150  	for _, newSet := range diff.AppliedTransactions {
   151  		// Split the sets into smaller sets, and add them to the list of
   152  		// transactions the pool can draw from.
   153  		// TODO: Split the one set into a bunch of smaller sets using the cp4p
   154  		// splitter.
   155  		p.setCounter++
   156  		p.fullSets[newSet.ID] = []int{p.setCounter}
   157  		var size uint64
   158  		var totalFees types.Currency
   159  		for i := range newSet.IDs {
   160  			size += newSet.Sizes[i]
   161  			for _, fee := range newSet.Transactions[i].MinerFees {
   162  				totalFees = totalFees.Add(fee)
   163  			}
   164  		}
   165  		// We will check to see if this splitSet belongs in the block.
   166  		s := &splitSet{
   167  			size:         size,
   168  			averageFee:   totalFees.Div64(size),
   169  			transactions: newSet.Transactions,
   170  		}
   171  
   172  		elem := &mapElement{
   173  			set:   s,
   174  			id:    splitSetID(p.setCounter),
   175  			index: 0,
   176  		}
   177  		newElements = append(newElements, elem)
   178  	}
   179  	return newElements
   180  }
   181  
   182  // peekAtOverflow checks top of the overflowMapHeap, and returns the top element
   183  // (but does not remove it from the heap). Returns false if the heap is empty.
   184  func (p *Pool) peekAtOverflow() (*mapElement, bool) {
   185  	return p.overflowMapHeap.peek()
   186  }
   187  
   188  // popFromBlock pops an element from the blockMapHeap, removes it from the
   189  // miner's unsolved block, and maintains proper set ordering within the block.
   190  func (p *Pool) popFromBlock() *mapElement {
   191  	elem := p.blockMapHeap.pop()
   192  	p.removeSplitSetFromTxnList(elem.id)
   193  	return elem
   194  }
   195  
   196  // popFromBlock pops an element from the overflowMapHeap.
   197  func (p *Pool) popFromOverflow() *mapElement {
   198  	return p.overflowMapHeap.pop()
   199  }
   200  
   201  // pushToTxnList inserts a blockElement into the list of transactions that
   202  // populates the unsolved block.
   203  func (p *Pool) pushToTxnList(elem *mapElement) {
   204  	p.blockMapHeap.push(elem)
   205  	transactions := elem.set.transactions
   206  
   207  	// Place the transactions from this set into the block and store their indices.
   208  	for i := 0; i < len(transactions); i++ {
   209  		p.blockTxns.appendTxn(&transactions[i])
   210  	}
   211  }
   212  
   213  // pushToOverflow pushes a mapElement onto the overflowMapHeap.
   214  func (p *Pool) pushToOverflow(elem *mapElement) {
   215  	p.overflowMapHeap.push(elem)
   216  }
   217  
   218  // ProcessConsensusChange will update the pool's most recent block.
   219  func (p *Pool) ProcessConsensusChange(cc modules.ConsensusChange) {
   220  	p.mu.Lock()
   221  	defer p.mu.Unlock()
   222  
   223  	p.log.Printf("CCID %v (height %v): %v applied blocks, %v reverted blocks", crypto.Hash(cc.ID).String()[:8], p.persist.GetBlockHeight(), len(cc.AppliedBlocks), len(cc.RevertedBlocks))
   224  	// Update the pool's understanding of the block height.
   225  	for _, block := range cc.RevertedBlocks {
   226  		// Only doing the block check if the height is above zero saves hashing
   227  		// and saves a nontrivial amount of time during IBD.
   228  		if p.persist.GetBlockHeight() > 0 || block.ID() != types.GenesisID {
   229  			p.persist.SetBlockHeight(p.persist.GetBlockHeight() - 1)
   230  		} else if p.persist.GetBlockHeight() != 0 {
   231  			// Sanity check - if the current block is the genesis block, the
   232  			// pool height should be set to zero.
   233  			p.log.Critical("Pool has detected a genesis block, but the height of the pool is set to ", p.persist.GetBlockHeight())
   234  			p.persist.SetBlockHeight(0)
   235  		}
   236  	}
   237  	for _, block := range cc.AppliedBlocks {
   238  		// Only doing the block check if the height is above zero saves hashing
   239  		// and saves a nontrivial amount of time during IBD.
   240  		if p.persist.GetBlockHeight() > 0 || block.ID() != types.GenesisID {
   241  			p.persist.SetBlockHeight(p.persist.GetBlockHeight() + 1)
   242  		} else if p.persist.GetBlockHeight() != 0 {
   243  			// Sanity check - if the current block is the genesis block, the
   244  			// pool height should be set to zero.
   245  			p.log.Critical("Pool has detected a genesis block, but the height of the pool is set to ", p.persist.GetBlockHeight())
   246  			p.persist.SetBlockHeight(0)
   247  		}
   248  	}
   249  
   250  	// Update the unsolved block.
   251  	// TODO do we really need to do this if we're not synced?
   252  	p.persist.mu.Lock()
   253  	p.sourceBlock.ParentID = cc.AppliedBlocks[len(cc.AppliedBlocks)-1].ID()
   254  	p.sourceBlock.Timestamp = cc.MinimumValidChildTimestamp
   255  	p.persist.Target = cc.ChildTarget
   256  	p.persist.RecentChange = cc.ID
   257  	p.persist.mu.Unlock()
   258  	// There is a new parent block, the source block should be updated to keep
   259  	// the stale rate as low as possible.
   260  	if cc.Synced {
   261  		p.log.Printf("Consensus change detected\n")
   262  		// we do this because the new block could have come from us
   263  
   264  		p.newSourceBlock()
   265  		if p.dispatcher != nil {
   266  			//p.log.Printf("Notifying clients\n")
   267  			p.dispatcher.ClearJobAndNotifyClients()
   268  		}
   269  	}
   270  }
   271  
   272  // ReceiveUpdatedUnconfirmedTransactions will replace the current unconfirmed
   273  // set of transactions with the input transactions.
   274  func (p *Pool) ReceiveUpdatedUnconfirmedTransactions(diff *modules.TransactionPoolDiff) {
   275  	p.mu.Lock()
   276  	defer p.mu.Unlock()
   277  
   278  	p.deleteReverts(diff)
   279  	p.addNewTxns(diff)
   280  	since := time.Now().Sub(p.sourceBlockTime).Seconds()
   281  	if since > 30 {
   282  		p.log.Printf("Block update detected\n")
   283  		p.newSourceBlock()
   284  		if p.dispatcher != nil {
   285  			//p.log.Printf("Notifying clients\n")
   286  			p.dispatcher.NotifyClients()
   287  		}
   288  	}
   289  }
   290  
   291  // removeSplitSetFromTxnList removes a split set from the miner's unsolved
   292  // block.
   293  func (p *Pool) removeSplitSetFromTxnList(id splitSetID) {
   294  	transactions := p.splitSets[id].transactions
   295  
   296  	// Remove each transaction from this set from the block and track the
   297  	// transactions that were moved during that action.
   298  	for i := 0; i < len(transactions); i++ {
   299  		p.blockTxns.removeTxn(transactions[i].ID())
   300  	}
   301  }